Files
malkuthro-ComfyUI-Koolook/web
malkuthroandClaude Opus 4.7 bebc6f618d feat(sidebar): right-click menu pack — Duplicate, Tags, + New directory move (#59)
* feat(sidebar): right-click menu pack — Duplicate, Tags, + New directory move

Three sibling sidebar slices bundled because they all extend the same
workflow-row right-click menu in `web/sidebar/tree.js` and share a small
schema/store surface in `workflows_store.js`. Refactor cleanup is held
back for a separate PR (issue #47) so this diff stays a "no behavior
change to existing flows" feature add.

Changes
-------
- Right-click "Duplicate…" → name modal with `<name> (copy)` default;
  deep-clones the source graph into a new entry in the same directory
  via `saveWorkflowEntry`. The duplicate inherits the source's tags so
  the user's categorization carries over. Same-name duplicates fall
  through to the existing archive-on-collision behavior. Closes #58.
- Right-click "Tags…" → chip-row modal with add/remove. Each edit
  fires its own `persistMutation` so changes survive a mid-edit
  close. New `showTagsModal` in `modals.js` mirrors the existing
  `showInputModal` / `showConfirmModal` shape (escape-key teardown,
  overlay-click dismissal centralized in `makeModalShell`).
- New Tags sidebar section between Workflows. Flat list of tag folders;
  each tag holds the workflows carrying it (sorted A→Z). Click loads
  the workflow from its real path. Archived workflows are filtered
  out of the section so the active view stays clean — their tags
  persist on the entry, so a restore brings them back. Search matches
  tag name OR workflow name.
- Right-click "+ New directory…" / "+ New subdirectory under <path>…"
  in the Move-to flow. Both create a fresh directory and move the
  workflow into it as one atomic mutation; if the move can't land,
  the new directory is rolled back so the cache never leaks an
  empty orphan. Closes #57.

Schema additions (back-compatible)
----------------------------------
- Workflow entries gain optional `tags: string[]`. `normalizeDirNode`
  trims, drops empties, and dedupes case-sensitively, so old entries
  without a tags field load as `tags: []` and the rest of the code can
  assume the field always exists. Pre-v0.3 stores load fine.
- New exports in `workflows_store.js`: `getWorkflowTags`, `addTag`,
  `removeTag`. `getWorkflowGraph` is now also exported so the duplicate
  flow can deep-clone without reaching into the cache directly.

Why one PR for three issues
---------------------------
All three slices touch `workflowRowContextMenu` in `tree.js` and the
mutator API in `workflows_store.js`. A coherent UX-feature scope ("next
batch of workflow menu enhancements") reviews more cheaply as one
diff than three near-identical PRs. Issue #47 (refactor / tidiness
pass) stays separate because mixing refactor and feature in the same
diff defeats the point of the split — that work is queued for a
follow-up PR.

Testing
-------
- `python tools/preflight_release.py --skip manager-meta -v` PASS
  (static / dispatch / workflows fixtures, all 3 stages).
- `node --input-type=module --check` passes on each modified JS file.
- Browser smoke test: pending — needs a ComfyUI session for the
  user to verify the three menu actions and the Tags section against
  a real workflow store. The CI gates are Python-only and don't catch
  JS regressions in the sidebar by themselves.

Related
-------
- Closes #57 (move-to: + New directory…)
- Closes #58 (Duplicate)
- Implements the right-click + section parts of #56 (Tags); the issue
  body's `addTag` / `removeTag` / `listAllTags()` ops landed under
  slightly different names (`addTag`, `removeTag`, `gatherTags`).

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

* fix(sidebar): address review findings on PR #59

Self-review pass against the validated agent findings. Five fixes total
across the three issues' surface, holding refactor/file-size concerns for
follow-up #47 widening.

Fixes
-----
- **HIGH** `normalizeDirNode` now rejects non-string `tags` elements
  outright instead of `String(raw).trim()` coercing them. `null` /
  `undefined` / `true` / `123` no longer silently surface as the literal
  tag strings `"null"` / `"undefined"` / etc. — `stats.dropped`
  increments instead so the existing console warning catches malformed
  migrations. Closes review HIGH-1.
- **HIGH** Tags modal now distinguishes "no tags yet" from "workflow
  no longer exists." The `getCurrentTags` adapter no longer collapses
  `null` to `[]`, and `renderChips` renders an explicit gone-state
  with disabled add input + descriptive placeholder. Prevents users
  typing into a doomed modal when a concurrent action (another tab,
  drag-and-drop) deletes the workflow under them. Closes review HIGH-3.
- **MEDIUM** Duplicate flow re-validates source existence at submit-time
  inside the `mutate` callback. Without it, a concurrent delete of the
  source between menu-open and submit would let `saveWorkflowEntry`'s
  `ensureDirectoryAtPath` silently re-create the parent directory and
  paste a clone of the captured graph — effectively undoing someone
  else's delete. Now returns `false` on missing source, surfacing
  "Could not duplicate — <name> no longer exists." Closes review
  MEDIUM-6.
- **MEDIUM** Duplicate flow now routes inherited tags through the
  public `addTag` mutator instead of writing `dir.workflows[newName].tags`
  directly. Keeps the tree.js UI layer outside the store's internal
  shape. The whole sequence still rides on one `persistMutation`
  snapshot — commit failure rolls back save + tag adds together.
  Closes review MEDIUM-5.
- **LOW** `gatherTags` comment block clarifies that grouping is
  case-sensitive but search filter is case-insensitive substring.
  The comment previously said "Comparison is case-sensitive" which
  was technically true for grouping but misleading on its own.

Skipped with rationale
----------------------
- **HIGH-2 modal cache desync on persist failure** — agent analysis
  was wrong on trace. `renderChips` reads from cache, X-click does
  not optimistically remove the chip from DOM, and `restore()`
  reverts cache to pre-mutation. DOM and cache stay consistent across
  every commit-failure path. Cross-tab updates aren't reflected, but
  that's a project-wide cache-coherency concern, not specific to this
  PR.
- **HIGH-4 tree.js size** — real architectural concern but follow-up
  only. Track on #47 widening (or a sibling extraction issue).
  Adding extraction in this PR would mix refactor with feature.
- **MEDIUM-7 tag API asymmetry** — agent claimed `addTag`'s parameter
  reassignment differed from neighbors, but verified the existing
  convention IS parameter reassignment (`addDirectory`, `renameDirectory`,
  `renameWorkflow`, `moveDirectory` all do `name = (name || "").trim();`).
  The defensive-copy difference between `getWorkflowTags` and
  `getWorkflowGraph` is intentional.
- **MEDIUM-8 top-level "Archive" name** — pre-existing semantic shared
  by toolbar Create-directory and save-modal "+ New directory."
  Restricting only the right-click new-dir entry would be inconsistent.
  Filing as a separate cross-cutting issue is the cleaner path.
- **LOW magic strings / modals.js size** — both queued for #47.

Validation
----------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on all three modified JS files — clean.

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-05 08:59:13 +02:00
..