* refactor(web): tree pipeline as section-registry with two-tier API
Implements issue #50. Behavior-preserving — same DOM, same path-state
keys, same render cadence, same context menus. The new engine deepens
the tree module: a 3-function public surface hides the section
iteration, divider insertion, forceExpanded threading, path prefixing,
empty-state placeholders, and pathStates lifecycle.
Public surface (3 functions, all module-private — only renderPanel is
exported):
- addSection(spec) — register a section descriptor
- renderTree({treeEl,query}) — single render entry point
- pinExpanded(paths) — pin folders open for the next render
Internals:
- SECTIONS array (declaration order = render order)
- pinnedPaths Set (consumed every render)
- makeSectionCtx → builds ctx.folder({...}) / ctx.leaf({row}) with
automatic path prefixing (sections write segment-relative paths;
engine produces ${section.id}/<seg>/...)
- validPaths Set populated as folders render; pathStates pruned at
end of each render against validPaths ∪ pinnedPaths — fixes the
unbounded-growth bug from renames/deletes leaving stale keys
Built-in sections:
- "nodes": curated-nodes section, merges gatherNodesByRepo +
gatherUserPickPacks into one displayed group with auto-pack and
user-pick subgroups (same UX as before)
- "workflows": directories with Archive sub-folders and right-click
context menus
Save flow:
- saveAndToast now calls pinExpanded(["workflows", "workflows/${dir}"])
before persistMutation; the persist's notifyWorkflowsChanged fires
a re-render that consumes the pin and opens the destination folder
Adding a new section is now ~12 lines (simple flat list) or ~30 lines
(advanced tree with row interactions) — the engine handles cross-cutting
concerns. Foundation for tags / recents / pinned / templates features.
Closes#50.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): scope pathStates prune to rendered sections + always apply pins
Two HIGH issues from the multi-agent review on PR #52:
1) Pruning was over-broad. When section A is gated empty by a search
filter, its (legitimate) pathStates keys were getting nuked because
they weren't in `validPaths` — clearing the search reverted the
user's prior expansions to startExpanded defaults. Fix: track which
sections actually rendered this pass (`renderedSectionIds`); prune
only keys whose owning section rendered.
2) `pinnedPaths` could leak across renders when the cross-section
search-empty branch took an early return without consuming the pin
set. A pin from one save would later be applied to an unrelated
render. Fix: drop the early return; phase 3 (prune + pin apply) now
always runs.
Also tightens code-quality LOW findings:
- `let pinnedPaths` -> `const pinnedPaths` + `.clear()` after snapshot
via `new Set(pinnedPaths)`.
- Add `@typedef SectionCtx` and `FolderOpts` JSDoc blocks (referenced
by `SectionSpec.build` but missing in the initial commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>