Closes the remaining checkboxes on #47 (Sidebar follow-ups deferred
from PR #45). Bundles seven items in one PR per the new "default to
bundling small slices" policy: two user-visible UX improvements + five
internal hygiene fixes.
User-visible (Changed)
----------------------
- **Save selection toast differentiates "no selection" from "selection
points at deleted nodes."** `serializeSelection` returns a
discriminated `{ kind: "empty" | "stale" | "ok", graph? }` instead
of plain `null` / graph. The "stale" case (selection set non-empty
but every id refers to a removed node — common after undo/redo)
now toasts "Selected node(s) no longer exist. Click a node on the
canvas to re-select." instead of the misleading generic message.
- **Curated-defaults seed failure now toasts.** Previously
console-only; users without DevTools never saw it. The most common
cause (localStorage quota / private-mode rejection) is something
the user can act on.
Internal (no behavior change)
-----------------------------
- Lifted save-modal action sentinels (`new` / `use_existing` /
`modify_existing`) into `ACTION_*` named constants at the top of
`showSaveWorkflowModal`, alongside the pre-existing `NEW_TOP` /
`SAVE_HERE` cascade sentinels.
- Section-id constants (`SECTION_ID_NODES`, `SECTION_ID_WORKFLOWS`,
`SECTION_ID_TAGS`) lifted to module scope in `tree.js`. `pinExpanded`
in the save flow now references the constant instead of the literal
`"workflows"` string, so a future section-id rename only needs the
constant updated.
- `modalLabel(text)` factory in `modals.js` replaces 7× repetition of
the four-line `createElement("label")` + `className` + `textContent`
+ `appendChild` pattern.
- `USERDATA_OVERWRITE_QUERY = "?overwrite=true"` named constant in
`workflows_store.js`. ComfyUI's userdata API requires this flag to
allow POST over an existing file; pinning the contract as a
constant makes it visible at a glance.
- `buildFolder` no longer round-trips folder-expansion state through
`wrapper.dataset.expanded`. State lives in a closure variable;
`pathStates` remains the canonical cross-render store.
- `subcategoryFor` fallback path now logs `console.warn` with the
offending category + `categoryRoot`. Reachable only via a logic
error (stale `REPOS` config); previously silent.
- Save-modal `onSave` callback contract renamed `dir` → `dirPath` to
match codebase-wide convention (`dirPath` for arrays of segments,
`dirName` for single segments, `dir` for resolved DirNode objects).
Caller updates in `tree.js` paired.
Skipped from #47 (with rationale)
---------------------------------
- The broader `el(tag, props, children)` DOM helper from the issue
body. Adopting it consistently would touch every modal/folder/leaf
row construction site (~40+ replacements) for marginal readability
win. The narrower `modalLabel(text)` is the meaningful slice
because it replaces an exact 4-line repetition. If `el()` becomes
desirable later, file as a focused refactor PR.
Verification
------------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on every modified JS file —
clean.
- Synced to live ComfyUI install via `scripts/sync_to_dev.py` for
smoke-test on Mac.
Closes#47.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>