* 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>