Implements ProjectResolution with TDD: fetches a [width, height] pair
from a resolution series by loop index, clamping out-of-bounds indices
to the last entry and returning (512, 512) defaults on error or missing key.
Also registers the node in mappings and updates TestNodeMappings count to 4.
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>
Hidden widget sync passes sequence_number as string, causing format
code errors downstream. Cast to int before use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
API returns {"files": [{"name": "...", "data_type": "..."}]}, not a
plain array. Extract file names from the nested structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- file_name is now a combo dropdown populated from the API when
manager_url and project_name are set
- ProjectSource outputs sequence_number (INT) for downstream use
- Refreshes file list when project_name or manager_url changes
- Updated tests for new output and error-default behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When ProjectKey configures before ProjectSource, _getSourceLabels
returns empty. replaceWithCombo now always keeps the saved value in
the options list so it survives the race condition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The key_name combo now only updates its dropdown options from the API
but never changes the user's selected value. Only the output value
refreshes on execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The async _refreshKeys call on every mouse click caused a race condition
where clicking the key_name dropdown would trigger a re-fetch that
overwrote the user's selection. Keys are now only refreshed on source
label change and workflow load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ProjectKey.fetch_key now returns empty defaults instead of raising
RuntimeError on API errors. Added logging to _syncFromSource and
fetch_key to trace the seq=4001 vs seq=4 mismatch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ProjectKey onMouseDown now triggers _syncFromSource + _refreshKeys so
clicking a relay always picks up source config changes. Added console
logs to notifyRelays and _refreshKeys for diagnosing sync issues.
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>
When ProjectSource widgets (url, project, file, sequence_number)
change, all ProjectKey nodes referencing that source now re-sync
and refresh their key dropdown immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Combo widgets show "undefined" when values list is empty. Now ensures
at least one entry (empty string placeholder) and picks a valid default.
Also populates source labels immediately on node creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- INT widgets (sequence_number) now properly hidden using origType +
hidden flag pattern from fast_saver.js
- source_label and key_name are now replaced with real combo widgets
via addWidget("combo") instead of just setting type="combo" on
STRING widgets, which didn't produce working dropdowns
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ProjectKey fetches live API data, so it must re-execute on every queue.
Added comment explaining why source_label exists but is unused in Python.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The history_tree_backup (created by _delete_nodes) was storing full
snapshot data in every backed-up node — 185MB+ per backup entry.
This caused the JSON file to re-bloat to 300MB on every save.
Now:
- _delete_nodes backs up tree metadata only (no snapshot data)
- Load paths strip snapshot data from existing backup entries
- Prevents both disk and RAM bloat from backup accumulation
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>
- 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>
- _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>
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>
- 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>
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>
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>
Adding all 33 DEFAULTS keys to sequences caused the ComfyUI loader
to exceed its 32 output slot limit, triggering tuple index out of
range errors. Only lora strength migration remains on load/sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensures mode and all other DEFAULTS keys are present in DB
sequences without bloating API read responses with unexpected keys.
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>
Ensures all sequences have required keys like mode, cfg, etc.
from DEFAULTS via setdefault so they persist on next save.
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>
Prevents 'parent slot deleted' RuntimeError when the timeline
tab is destroyed while the timer is still firing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>