Branching: snapshots now track parentId to form a tree structure.
Swapping to an old snapshot and editing forks into a new branch.
Sidebar and timeline show < 1/3 > navigators at fork points to
switch between branches. Pruning protects ancestors and fork points.
Deleting a fork point re-parents its children.
Profiles: save/load named sets of workflows as session profiles.
Backend stores profiles as JSON in data/profiles/. Sidebar has a
collapsible Profiles section with save, load, and delete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
List, prune, and delete operations now use a lightweight in-memory cache
of snapshot metadata (everything except graphData). Only get_full_record()
and update_meta() hit disk after warm-up, keeping sidebar loads and
auto-capture prune cycles fast.
Key changes:
- snapshot_storage.py: cache layer (_cache, _cache_warmed, _extract_meta,
_ensure_cached), new get_full_record() and update_meta() functions,
all existing functions updated to use cache
- snapshot_routes.py: new /get and /update-meta endpoints
- snapshot_manager.js: db_getFullRecord() and db_updateMeta() helpers,
lazy graphData fetch in restore/swap/diff/preview/tooltip, label/notes/
lock use update_meta instead of full put to preserve graphData on disk
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce a SaveSnapshot custom node that triggers snapshot captures
via WebSocket. Node-triggered snapshots are visually distinct in the
sidebar (purple left border + "Node" badge) and managed with their
own independent rolling limit (maxNodeSnapshots setting), separate
from auto/manual snapshot pruning. Node snapshots skip hash-dedup
so repeated queue runs always capture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snapshots are now stored as individual JSON files on the server under
data/snapshots/, making them persistent across browsers and resilient
to browser data loss. Existing IndexedDB data is auto-migrated on
first load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>