From c4d107206fc638e4d66e21e3df8e4d5c9ed04b28 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 28 Feb 2026 21:38:37 +0100 Subject: [PATCH] Fix 4 bugs from third code review - Fix delete_proj not persisting cleared current_project to config: page reload after deleting active project restored deleted name, silently breaking all DB sync - Fix sync_to_db crash on non-dict batch_data items: add isinstance guard matching import_json_file - Fix output_types ignored in load_dynamic: parse declared types and use to_int()/to_float() to coerce values, so downstream ComfyUI nodes receive correct types even when API returns strings - Fix backward-compat comma-split for types not trimming whitespace: legacy workflows with "STRING, INT" got types " INT" breaking ComfyUI connection type-matching Co-Authored-By: Claude Opus 4.6 --- project_loader.py | 18 ++++++++++++++++-- tab_projects_ng.py | 4 ++++ tests/test_project_loader.py | 16 ++++++++++++++++ utils.py | 2 ++ web/project_dynamic.js | 4 ++-- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/project_loader.py b/project_loader.py index c7a2cc9..cc52d2c 100644 --- a/project_loader.py +++ b/project_loader.py @@ -142,10 +142,24 @@ class ProjectLoaderDynamic: except (json.JSONDecodeError, TypeError): keys = [k.strip() for k in output_keys.split(",") if k.strip()] + # Parse types for coercion + types = [] + if output_types: + try: + types = json.loads(output_types) + except (json.JSONDecodeError, TypeError): + types = [t.strip() for t in output_types.split(",")] + results = [] - for key in keys: + for i, key in enumerate(keys): val = data.get(key, "") - if isinstance(val, bool): + declared_type = types[i] if i < len(types) else "" + # Coerce based on declared output type when possible + if declared_type == "INT": + results.append(to_int(val)) + elif declared_type == "FLOAT": + results.append(to_float(val)) + elif isinstance(val, bool): results.append(str(val).lower()) elif isinstance(val, int): results.append(val) diff --git a/tab_projects_ng.py b/tab_projects_ng.py index afa8422..32494ac 100644 --- a/tab_projects_ng.py +++ b/tab_projects_ng.py @@ -119,6 +119,10 @@ def render_projects_tab(state: AppState): state.db.delete_project(name) if state.current_project == name: state.current_project = '' + state.config['current_project'] = '' + save_config(state.current_dir, + state.config.get('favorites', []), + state.config) ui.notify(f'Deleted project "{name}"', type='positive') render_project_list.refresh() diff --git a/tests/test_project_loader.py b/tests/test_project_loader.py index 41399ca..58e76ee 100644 --- a/tests/test_project_loader.py +++ b/tests/test_project_loader.py @@ -100,6 +100,22 @@ class TestProjectLoaderDynamic: assert result[0] == "comma_val" assert result[1] == "ok" + def test_load_dynamic_type_coercion(self): + """output_types should coerce values to declared types.""" + import json as _json + data = {"seed": "42", "cfg": "1.5", "prompt": "hello"} + node = ProjectLoaderDynamic() + keys_json = _json.dumps(["seed", "cfg", "prompt"]) + types_json = _json.dumps(["INT", "FLOAT", "STRING"]) + with patch("project_loader._fetch_data", return_value=data): + result = node.load_dynamic( + "http://localhost:8080", "proj1", "batch_i2v", 1, + output_keys=keys_json, output_types=types_json + ) + assert result[0] == 42 # string "42" coerced to int + assert result[1] == 1.5 # string "1.5" coerced to float + assert result[2] == "hello" # string stays string + def test_load_dynamic_empty_keys(self): node = ProjectLoaderDynamic() with patch("project_loader._fetch_data", return_value={"prompt": "hello"}): diff --git a/utils.py b/utils.py index 809d58f..805ec79 100644 --- a/utils.py +++ b/utils.py @@ -202,6 +202,8 @@ def sync_to_db(db, project_name: str, file_path: Path, data: dict) -> None: if isinstance(batch_data, list): db.conn.execute("DELETE FROM sequences WHERE data_file_id = ?", (df_id,)) for item in batch_data: + if not isinstance(item, dict): + continue seq_num = int(item.get(KEY_SEQUENCE_NUMBER, 0)) now = __import__('time').time() db.conn.execute( diff --git a/web/project_dynamic.js b/web/project_dynamic.js index ed830a8..f8e7634 100644 --- a/web/project_dynamic.js +++ b/web/project_dynamic.js @@ -154,7 +154,7 @@ app.registerExtension({ let types = []; if (otWidget?.value) { try { types = JSON.parse(otWidget.value); } catch (_) { - types = otWidget.value.split(","); + types = otWidget.value.split(",").map(t => t.trim()).filter(Boolean); } } @@ -162,7 +162,7 @@ app.registerExtension({ // On load, LiteGraph already restored serialized outputs with links. // Rename and set types to match stored state (preserves links). for (let i = 0; i < this.outputs.length && i < keys.length; i++) { - this.outputs[i].name = keys[i].trim(); + this.outputs[i].name = keys[i]; if (types[i]) this.outputs[i].type = types[i]; }