Each frame path row (start/middle/end) now has:
- path input with preview
- strength float (default 1.0)
- switch linked to the corresponding logic index bit
Switches and logic index are bidirectionally synced.
end_frame → logic index → switches mirror chain preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bit 0 = start frame, bit 1 = middle frame, bit 2 = end frame.
Tooltip shows the full 0-7 truth table on hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
direction:rtl caused path characters to render in wrong order.
text-align:right right-aligns the text (shows end of path) without
breaking the character display order.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Temporary field to ease node migration. Initializes to end_frame's
value and stays in sync whenever end_frame changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates DEFAULTS, standard_keys, UI label, and timeline known_keys.
Adds _migrate_key_renames() called on load_json to auto-migrate
existing JSON files with the old key name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
commit() is async but was called without await from sync handlers
(clone_next, clone_end, clone_sub, delete, copy_source, del_custom,
add_param, _sync_entry, _randomize) — causing saves and UI refreshes
to silently never run. Made all handlers async and added await.
Also fixed for i,entry loop shadowing the card's i parameter,
which was causing _render_vace_settings to receive the wrong index.
Removed unawaited render-time commit() in resolutions init block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
The JSON file was storing full snapshot data in every history node,
causing files to grow to multi-GB. Now when DB is enabled:
- sync_to_db receives full tree (extracts snapshots to DB)
- save_json receives slim tree (no snapshot data)
- JSON file stays small, DB is authoritative for snapshots
When DB is disabled, JSON still stores full snapshots (only store).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- _auto_change_note now loads previous snapshot from DB instead of
reading stripped in-memory node (was producing wrong commit messages)
- _render_mass_update now strips snapshots after save (was leaking RAM)
- Only strip snapshots when DB is enabled (preview/restore still works
without DB via in-memory or disk fallback)
- _render_data_preview adds disk fallback matching _restore_node
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>
json.dump in a background thread would crash with "dictionary changed
size during iteration" when the UI mutated data concurrently. Now all
save_json and sync_to_db calls receive a json.loads(json.dumps(data))
snapshot, isolating the serialization from live UI mutations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- load_file and on_select try db.load_full_data first (~0.01s),
fall back to load_json only when DB has no data
- Fix unawaited coroutine warning for auto-load (asyncio.ensure_future)
- Fix radio on_change to properly await async load_file
- Reuse current data cache when source file matches current file,
eliminating a redundant 1.3s JSON parse during render
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>
Logs with perf_counter timing on: file load/save, DB sync, all
render functions, save & snap (with deepcopy/save/sync breakdown),
graphviz cache hit/miss, node restore, and API endpoints.
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>
Store lora name and strength as independent JSON keys instead of the
combined <lora:name:strength> format. Legacy format is auto-migrated
on load. Timeline preview updated to show all 6 LoRA slots.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: save_json + sync_to_db blocked the event loop while
serializing the growing history tree (all snapshots) as JSON,
causing NiceGUI websocket timeout and client reconnect.
- Skip history tree in snapshot deepcopy (copied only to discard)
- Move save_json/sync_to_db to asyncio.to_thread in all callbacks
- Make save_and_snap, commit, sort_by_number, shift_fts,
_add_sequence, apply_mass_update async
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
history_tree.py:
- Cycle protection in generate_graph() parent walk
- KeyError → .get() for malformed node data in commit() and generate_graph()
- UUID collision check with for/else raise in commit() and _migrate_legacy()
- RuntimeError → ValueError for consistent exception handling
tab_timeline_ng.py:
- Re-parent children walks to surviving ancestor for batch deletes
- Branch tip deletion re-points to parent instead of removing branch
- Cycle protection in _walk_branch_nodes and _find_branch_for_node
- Full data.clear() restore instead of merge in _restore_node
- Safe .get('data', {}) in restore and preview
- Reset stale branch selection after node deletion
- json.dumps for safe JS string escaping in graphviz renderer
tab_batch_ng.py:
- NaN/inf rejection in dict_number with math.isfinite()
- _safe_int used in recalc_vace, update_mode_label, frame_to_skip
- Uncaught ValueError from htree.commit() caught with user notification
tab_comfy_ng.py:
- asyncio.get_event_loop() → get_running_loop()
utils.py:
- Atomic writes for save_config and save_snippets
- save_config extra_data can't override explicit last_dir/favorites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- sync_to_db: use ON CONFLICT for duplicate sequence numbers
- history_tree: html.escape() for Graphviz DOT labels
- tab_timeline_ng: cap history_tree_backup to 10 entries
- tab_batch_ng: add _safe_int() helper for VACE settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add bounds check on src_batch index in add_from_source and copy_source
- Guard delete callback against stale index after rapid clicks
- Replace __import__('time').time() with time.time() in sync_to_db
- Use deepcopy(DEFAULTS) consistently in utils.py and main.py
- Use JSON.stringify in JS onConfigure fallback path for key storage
- Read state.show_comfy_monitor for checkbox initial value
- Remove unused KEY_BATCH_DATA import from tab_projects_ng
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use asyncio.to_thread for proxy endpoints to avoid blocking event loop
- Add mode to DEFAULTS so it doesn't silently insert 0
- Use JSON serialization for keys in project_dynamic.js (with comma fallback)
- Validate path exists in change_path, friendly error on duplicate rename
- Remove unused exp param from rename closure
- Use deepcopy for DEFAULTS consistently
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Compares current batch data against the last snapshot to generate
descriptive notes like "Added seq 3; Changed: prompt, seed".
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>
The subsegment-card CSS class was not being applied to subsequence
expansion items. Add the class conditionally and include the teal
accent CSS rules with a 6px left border.
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>
- Sync dict_input/dict_textarea/LoRA inputs on update:model-value
(not just blur) to prevent silent data loss on quick saves
- Split LoRA into name + strength fields, default strength to 1.0
- Stack LoRAs one per line instead of 3-card row
- Collapse "Add New Sequence from Source File" into expansion
- Add file selector to Pane A in dual-pane mode
- Clear secondary pane state on directory change
- Fix file radio resetting to first file on refresh
- Handle bare-list JSON files and inf/nan edge cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dict_number() only wrote to seq[key] on blur, so changing a value
(e.g. via spinner arrows) and immediately clicking Save could race
the save ahead of the blur on the server. Now also syncs on
update:model-value so the dict is always current.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace red accent with amber, add Inter font, introduce 4-level depth
palette via CSS variables, expand padding/gaps, wrap sidebar and content
sections in cards, add section/subsection header typography classes, and
style scrollbars for dark theme. Pure visual changes — no functional or
data-flow modifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dict_number() defaulted to 0 while mode_label used default of 1,
causing visual inconsistency when 'vace schedule' key is missing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>