Replace HistoryTree (DAG with branches, Graphviz rendering) with a flat
chronological SnapshotTimeline. New UI features: split-view layout,
snapshot compare/diff, cherry-pick restore of individual sequences or
fields, auto-snapshots with debounce, and pin/filter support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverses the previous merge migration. Lora data is now stored as
separate keys: 'lora 1 high' (STRING name) and 'lora 1 high strength'
(FLOAT). This allows ProjectKey relay nodes to output name and strength
as properly typed separate values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add load_json to tab_timeline_ng imports (NameError on disk fallback)
- Wrap save_history_tree in BEGIN/COMMIT transaction (was autocommitting
each statement, risking partial writes on failure)
- Clean up orphaned history_snapshots in sync_to_db when nodes are
removed from the tree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
History tree nodes stored full data snapshots in memory (5-50MB each),
accumulating with every save. Now:
- New `history_snapshots` DB table stores node data separately
- `save_history_tree` and `sync_to_db` extract snapshots before saving
- In-memory tree nodes only hold metadata (id, parent, note, timestamp)
- Restore and preview load snapshots from DB on demand
- `save_and_snap` uses json roundtrip instead of deepcopy (1 copy not 2)
- `_src_cache` moved to AppState, cleared on file switch
- `strip_snapshots()` method on HistoryTree for explicit cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ORDER BY sequence_number put all subs (>=1000) at the end. Now groups
by parent (seq/1000), main first, then subs in order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces slow JSON file parsing with fast DB queries for file loading.
Returns the same dict structure as load_json (top-level + batch_data +
history_tree).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The COMMIT without BEGIN failed in autocommit mode, crashing ProjectDB
init and leaving _shared_db as None ("Database not initialized").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Runs at DB startup, only updates rows that have stale separate strength
keys. No-op once all data is migrated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Combines separate lora name and strength keys into "name:strength" format,
removing 6 strength keys to free output slots for mode. Migration handles
legacy <lora:> wrapper, separate strength keys, and already-merged formats.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- tab_batch_ng.py: async create_batch with to_thread save/sync
- tab_raw_ng.py: async do_save with to_thread, replace deepcopy
with dict comprehension for display data
- main.py: async create_new with to_thread save
- tab_projects_ng.py: replace per-project count_data_files with
single list_projects_with_file_counts JOIN query
- db.py: add list_projects_with_file_counts method
Zero blocking I/O calls remain in UI callbacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adding all DEFAULTS keys to API responses shifted output slot
indices, breaking existing ComfyUI workflow links (tuple index
out of range). Only lora migration remains on the DB read path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensures the project loader API always returns all expected keys
like mode, cfg, etc. even if they were missing from stored data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Force float coercion in both migration paths (load_json and DB read)
and override type detection in get_sequence_keys so lora strength
keys always report as FLOAT regardless of JSON deserialization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add _migrate_lora_keys to get_sequence() so the /api endpoints
always return separate lora name and strength keys, even for
data stored in the old <lora:name:strength> format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all save_json/load_json/sync_to_db/DB calls off the event loop
with asyncio.to_thread to prevent UI freezes. Cache graphviz SVG by
DOT source hash (bounded LRU of 20). Replace DELETE-all/re-INSERT in
sync_to_db with UPSERT + targeted DELETE. Add DB indexes, COUNT query,
and reduce graph poll interval to 0.5s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sequences: add rename button with name shown in expansion header
- Subsequences: cycle through 6 distinct border colors by sub-index
- Projects: add rename and change path buttons with DB methods
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove 3 redundant hardcoded nodes (Standard/VACE/LoRA), keeping only the
Dynamic node. Add total_sequences INT output (slot 0) for loop counting.
Add structured error handling: _fetch_json returns typed error dicts,
load_dynamic raises RuntimeError with descriptive messages, JS shows
red border/title on errors. Add 500ms debounced auto-refresh on widget
changes. Add 404s for missing project/file in API endpoints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If the original error (e.g., disk full) also prevents ROLLBACK from
executing, catch and suppress the ROLLBACK failure so the original
exception propagates cleanly and the connection isn't left in a
permanently broken state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HIGH:
- Fix JS TypeError on empty API response: validate keys/types are arrays
before using them; add HTTP status check (resp.ok)
- Fix BEGIN IMMEDIATE conflict: set isolation_level=None (autocommit) on
SQLite connection so explicit transactions work without implicit ones
MEDIUM:
- Fix import_json_file non-atomic: wrap entire operation in BEGIN/COMMIT
with ROLLBACK on error — no more partial imports
- Fix crash on non-dict batch_data items: skip non-dict elements
- Fix comma-in-key corruption: store keys/types as JSON arrays in hidden
widgets instead of comma-delimited strings (backward-compat fallback)
- Fix blocking I/O in API routes: change async def to def so FastAPI
auto-threads the synchronous SQLite calls
LOW:
- Fix missing ?. on app.graph.setDirtyCanvas in refreshDynamicOutputs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix NameError: pass state to _render_vace_settings (tab_batch_ng.py)
- Fix non-atomic sync_to_db: use BEGIN IMMEDIATE transaction with rollback
- Fix create_secondary() missing db/current_project/db_enabled fields
- Fix URL encoding: percent-encode project/file names in API URLs
- Fix import_json_file crash on re-import: upsert instead of insert
- Fix dual DB instances: share single ProjectDB between UI and API routes
- Also fixes top_level metadata never being updated on existing data_files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- db.py: ProjectDB class with SQLite schema (projects, data_files,
sequences, history_trees), WAL mode, CRUD, import, and query helpers
- api_routes.py: REST API endpoints on NiceGUI/FastAPI for ComfyUI
to query project data over the network
- project_loader.py: ComfyUI nodes (ProjectLoaderDynamic, Standard,
VACE, LoRA) that fetch data from NiceGUI REST API via HTTP
- web/project_dynamic.js: Frontend JS for dynamic project loader node
- tab_projects_ng.py: Projects management tab in NiceGUI UI
- state.py: Added db, current_project, db_enabled fields
- main.py: DB init, API route registration, projects tab
- utils.py: sync_to_db() dual-write helper
- tab_batch_ng.py, tab_raw_ng.py, tab_timeline_ng.py: dual-write
sync calls after save_json when project DB is enabled
- __init__.py: Merged project node class mappings
- tests/test_db.py: 30 tests for database layer
- tests/test_project_loader.py: 17 tests for ComfyUI connector nodes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>