aimusic / ComfyUI /custom_nodes /koolook /docs /maintainers /workflows-sidebar.md
vidfom's picture
Upload folder using huggingface_hub (part 7)
e4ab0d4 verified
|
Raw
History Blame Contribute Delete
15.2 kB

Workflows sidebar — quick reference

The "Workflows" section of the Kforge Labs sidebar tab; implementation lives in web/sidebar/, with the workflow store + ops in workflows_store.js and the tree rendering in tree.js.

Save / load loop

  1. Click 💾 (whole canvas) or (selection only) in the Workflows action bar.
  2. Modal: Directory (flat path picker) → Base on existing (optional) → ActionWorkflow name (shown only when needed).
  3. Saved entry appears at the bottom of its directory in the tree.
  4. Click any workflow row to load it onto a fresh tab; the tab title takes the workflow's name.

The directory dropdown lists every directory path in the tree as a flat list (UP-scale, UP-scale / Type-A, Depth, …). The "+ New directory…" option creates a new top-level directory. Subdirectories are created via right-click on an existing directory (see below).

Action semantics

Action Resulting name Archive behavior
New name typed auto-archives only if the typed name happens to collide
Use existing name (archive previous) base always archives the existing entry
Modify existing name base, edited no archive (different name)

The archive trigger is a name collision with any existing active entry, regardless of which action was selected. The Action dropdown is UX guidance; the underlying logic flows from the resulting (name, dir) pair.

Modules — splice a saved cluster into your live canvas

A saved workflow tagged with the literal tag module behaves like a reusable building block: clicking it inserts its nodes into the existing canvas (placed at the viewport center, freshly id-remapped, internal links re-created, the inserted nodes left selected) instead of replacing the graph the way a normal Load does.

Typical use cases this is designed for:

  • EXR output stack — a Save EXR + path-builder + format selector cluster the user wants to drop into any render-time workflow.
  • Depth-only render — a depth-map node + preview wired up, ready to be spliced into a wider pipeline.
  • Wan 2.2 prompt module — the EasyWan22Prompt node + a CLIP encoder
    • any conditioning glue, saved once, dropped into ten different workflows.

How it works:

Surface Behavior
Save modal (selection save) Includes a Save as module checkbox, pre-checked for selection saves. Whole-canvas saves get the same checkbox but unchecked by default. The saved entry gets module: true and the compatibility module tag in the same atomic persistMutation as the save — a commit failure rolls both back.
Workflows section Module-tagged entries get a green pi pi-plus-circle icon and a distinct hover tooltip. Left-click inserts instead of loading. Right-click still offers both Load and Insert into canvasLoad stays available for every row regardless of tag.
Tags section Same flip — a workflow tagged module is treated as a module no matter which tag folder it shows up under.
Archive folder Archived module entries still left-click to Load (they're old versions, treated as "review" rather than "splice"). Right-click still has both Load and Insert.

Implementation pieces:

  • The literal tag string lives in web/sidebar/constants.js as MODULE_TAG. New saves store module state as first-class module: true; the tag is still honored for old entries and for the manual right-click Tags workflow.
  • The non-destructive insert primitive is insertWorkflowOntoCanvas(dirPath, wfName) in web/sidebar/canvas_io.js. It pre-flights every referenced node type against LiteGraph.registered_node_types, aborts cleanly with a toast when any are missing (a partial insert with stub nodes is worse than no insert), then deep-clones the saved graph, normalizes Comfy/LiteGraph link records, configures each node with stale saved link IDs stripped, sets node.id = -1 so app.graph.add() allocates fresh ids that can't collide with the live canvas, and finally recreates internal connections via originNode.connect(...) (which auto-allocates fresh link ids). Selection saves that include ComfyUI subgraph wrapper nodes also carry the required definitions.subgraphs entries so a later native Load has the definitions available.
  • Bbox-of-cluster placement uses CSS pixels (clientWidth/clientHeight), not the HiDPI backing buffer — same correction as the existing placeAtCanvasCenter helper.
  • Selection saves preserve ComfyUI group boxes that overlap selected nodes. Insert translates those saved groups by the same placement delta as the inserted node cluster, so group titles and boxes survive module round trips. This is intentionally limited to groups touching the saved selection; unrelated canvas groups are not copied into partial workflow saves.

To turn an existing saved workflow into a module: right-click → Tags… → add module. (No re-save needed; the row re-renders on the next workflows-changed event.) To un-module: remove the module tag the same way; the tag editor keeps the first-class module flag in sync.

Right-click menus

  • Workflow row: Load / Insert into canvas / Update from selection or canvas / Rename / Duplicate / Tags / Move to archive (or Restore from archive if already archived) / + New directory / + New subdirectory / Delete. Existing-folder moves are handled by drag-and-drop so the menu stays short in large libraries.
  • Directory row (any depth): Create subdirectory… / Rename / Delete (with confirm if non-empty — the message names workflow + subdirectory counts).
  • Archive folder (synthetic — appears only when a directory has archived workflows): Clean up archive runs across the whole selected Archive folder, groups entries by original workflow/setup name, keeps the newest archived entry from the last 5 minutes, last hour, and last day for each group (plus one fallback if none land in those windows), and confirms the keep/delete counts before mutating; Delete archive (N) removes every archived workflow in this directory in one go. Active workflows in the same directory are untouched by both actions.

Drag-and-drop (Tier 1 — moves only; reordering is alphabetical)

Drag Drop on Effect
Workflow row Directory row Move workflow to that directory (no-op if same dir)
Workflow row Archive folder Archive in that directory (move first if cross-dir)
Directory row Directory row Nest the dragged dir as a child of the target

Cycle prevention: dragging a directory onto itself, its current parent, or any of its descendants is rejected. Name collisions (the destination already has a sibling with the same name) are rejected. Reserved name: dropping a directory literally named Archive into a non-root parent is rejected (would shadow the synthetic Archive folder).

Visual feedback: drop targets get a blue outline on hover. Failed drops surface a toast.

Sort within a level remains alphabetical — Tier 1 doesn't introduce a custom-order schema. (A future Tier 2 could.)

Subdirectories

Every directory can host nested subdirectories at arbitrary depth. Each nested directory behaves like a top-level directory: it can hold workflows, its own Archive subfolder (when any of its workflows are archived), and further subdirectories.

  • Create: right-click a directory → "Create subdirectory…"
  • Save into: pick the nested path from the save modal's directory dropdown (UP-scale / Type-A)
  • Move across: drag a workflow row onto the target directory row
  • Reserved name: subdirectory names cannot be Archive (case-insensitive) — that string is reserved for the synthetic Archive folder rendered for archived workflows. Top-level directories named Archive are allowed (no collision at root).

Storage

Where What
/userdata/koolook_workflows.json (ComfyUI install) Primary. Per-install.
localStorage["koolook.workflows.fallback.v1"] Used only if /userdata is unreachable.
localStorage["koolook.workflows.seeded.v1"] "1" once defaults have been seeded for this install.
web/workflow_defaults.json (in repo) Distributed starter pack — seeded once into /userdata on first load with empty workflow data.

Comfy workflow draft quota gotcha

ComfyUI keeps its own browser-side workflow draft cache in localStorage, and the key scheme has changed across frontend versions. All generations share the Comfy.Workflow.Draft prefix:

Generation Keys Written when
V1 (legacy; still written on tab switches as of frontend 1.44) Comfy.Workflow.Drafts (one blob of all drafts) + Comfy.Workflow.DraftOrder (LRU) switching/loading workflows
V1 per-workspace (interim) Comfy.Workflow.Drafts:<ws> + Comfy.Workflow.DraftOrder:<ws> older 1.4x frontends; the 1.44 migration reads these once without deleting them (later frontends added cleanup on successful migration, and an interrupted migration can still leave them behind)
V2 (frontend 1.44+) Comfy.Workflow.DraftIndex.v2:<ws> (index) + Comfy.Workflow.Draft.v2:<ws>:<hash> (one payload per draft) ~512 ms after every graph edit

Once the origin's storage quota is hit, draft saves fail and Comfy shows "Failed to save workflow draft" toasts. Worse, the 1.44+ V2 layer only evicts its own current-workspace drafts on a quota error, and when that isn't enough it latches a session-wide storage unavailable flag — after which every edit toasts until the page is reloaded, regardless of which workflow is open.

Koolook hit the symptom first in v0.3.6 with sidebar workflow loads; the fix was a stable temporary workflow ID derived from the sidebar path/name so the same workflow replaces the same draft entry instead of creating a new one per load. For Koolook frontend extensions that ship large timeline/editor data, the standing rules remain:

  • Do not churn workflow IDs for equivalent exported/imported workflow files.
  • Keep hidden widget payloads plain JSON and strip preview-only blobs before serialization.

The systemic countermeasure is the global guard in web/koolook_draft_guard.js (loaded on every page; behavior covered by tests/js/test_draft_guard.mjs). It matches draft keys by prefix, so a future upstream rename inside the prefix degrades to last-resort eviction instead of going silently stale (which is how the previous exact-name guard, then embedded in the LTX Director extension, died on frontend 1.44). Two mechanisms:

  1. Boot prune — deletes suffixed-V1 families already migrated to V2 (dead weight 1.44-era migrations leave behind), corrupt keys (scoped to the offending key only), oversized entries (>750k chars), and enforces a ~2M-char total draft budget, oldest drafts first.
  2. localStorage.setItem quota guard — on QuotaExceededError for a draft key, evicts the oldest draft across all generations and retries inside setItem, so the V2 layer's own catch never sees the error: the save succeeds, no toast, and the storage-unavailable latch never trips.

The guard never touches Koolook /userdata, snapshots, workflow library entries, render outputs, or any non-draft localStorage key.

JSON shape (recursive — directories lives both at the root AND inside every directory node):

{
  "directories": {
    "<dir>": {
      "workflows": {
        "<name>": {
          "savedAt": "<ISO timestamp>",
          "graph": { /* serialized ComfyUI workflow JSON */ },
          "module": true,
          "tags": ["module"],
          "archived": true
        }
      },
      "directories": {
        "<sub>": {
          "workflows": { /* … */ },
          "directories": { /* recurses */ }
        }
      }
    }
  }
}

archived is optional (false/missing means active). Pre-v0.3 stores without the nested directories field still load fine — normalize treats a missing directories as {} and the rest of the code assumes it always exists after normalization.

Reset (force re-seed from defaults)

Delete the userdata file in the live ComfyUI install:

rm <ComfyUI>/user/default/userdata/koolook_workflows.json

Then in the ComfyUI page DevTools console:

localStorage.removeItem("koolook.workflows.seeded.v1");
localStorage.removeItem("koolook.workflows.fallback.v1");
location.reload();

Things that surprised us — keep in mind

  • Selection saves are partial graphs. Only the selected nodes + links between them survive; links into/out of non-selected nodes are nulled out, so the loaded workflow never has dangling references. Link handling accepts both serialized array links ([id, origin, slot, target, slot, type]) and object-shaped LLink records from graph.links.
  • Selection saves preserve relevant groups. Any ComfyUI group box that overlaps selected nodes is stored with the partial graph and restored on Load or Insert. This supports the published-setup convention where Koolook Input and Koolook Output groups mark the setup's app surface.
  • Subgraph wrappers are special. The saved selection includes transitively referenced definitions.subgraphs, but insert still cannot register a never-loaded subgraph definition because ComfyUI's subgraph registration path lives inside native app.loadGraphData. If Insert reports an unregistered subgraph definition, native-Load the workflow once in that browser session, then retry Insert.
  • Workflow tab name comes from app.loadGraphData(graph, true, true, name, {})'s 4th arg. The tab flips from "Unsaved Workflow (N)" to the saved name, and Ctrl+S pre-fills with that name. (Verified against Comfy-Org/ComfyUI_frontend src/scripts/app.ts.)
  • Folder expansion state persists across re-renders (the pathStates Map in web/sidebar/tree.js). Saving never collapses the directory you were viewing — and a save into a nested path opens every ancestor folder.
  • Archive sub-folder is rendered above active workflows in each directory (and below nested subdirectories), so the latest active workflow sits closest to the bottom of its directory — easy to spot.
  • Directory header counts include all descendants. A parent dir with no direct workflows but multiple subdirectories shows the recursive total.
  • No background auto-pruning of archives. Re-saving the same name can still leave many timestamped archive entries. Right-click the synthetic Archive folder → Clean up archive to triage all archived entries in that folder by original workflow/setup name, keeping the newest archived entry from the last 5 minutes, last hour, and last day for each group. Use Delete archive (N) / per-row Delete when you want explicit removal.
  • Same-pattern distribution as starter_preset.json — see curated-sidebar.md for the seeding semantics that also apply here.