Fix 25+ bugs across rounds 4-8 of comprehensive code review

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>
This commit is contained in:
2026-03-02 10:23:14 +01:00
parent 04b9ed0e27
commit b042fe4368
5 changed files with 117 additions and 37 deletions

View File

@@ -18,7 +18,10 @@ class HistoryTree:
def _migrate_legacy(self, old_list: list[dict[str, Any]]) -> None:
parent = None
for item in reversed(old_list):
node_id = str(uuid.uuid4())[:8]
for _ in range(10):
node_id = str(uuid.uuid4())[:8]
if node_id not in self.nodes:
break
self.nodes[node_id] = {
"id": node_id, "parent": parent, "timestamp": time.time(),
"data": item, "note": item.get("note", "Legacy Import")
@@ -28,7 +31,13 @@ class HistoryTree:
self.head_id = parent
def commit(self, data: dict[str, Any], note: str = "Snapshot") -> str:
new_id = str(uuid.uuid4())[:8]
# Generate unique node ID with collision check
for _ in range(10):
new_id = str(uuid.uuid4())[:8]
if new_id not in self.nodes:
break
else:
raise ValueError("Failed to generate unique node ID after 10 attempts")
# Cycle detection: walk parent chain from head to verify no cycle
if self.head_id:
@@ -39,7 +48,7 @@ class HistoryTree:
raise ValueError(f"Cycle detected in history tree at node {current}")
visited.add(current)
node = self.nodes.get(current)
current = node["parent"] if node else None
current = node.get("parent") if node else None
active_branch = None
for b_name, tip_id in self.branches.items():
@@ -115,8 +124,12 @@ class HistoryTree:
# Build reverse lookup: node_id -> branch name (walk each branch ancestry)
node_to_branch: dict[str, str] = {}
for b_name, tip_id in self.branches.items():
visited = set()
current = tip_id
while current and current in self.nodes:
if current in visited:
break
visited.add(current)
if current not in node_to_branch:
node_to_branch[current] = b_name
current = self.nodes[current].get('parent')
@@ -192,11 +205,18 @@ class HistoryTree:
+ '</TABLE>>'
)
safe_tooltip = full_note.replace('\\', '\\\\').replace('"', '\\"')
dot.append(f' "{nid}" [label={label}, tooltip="{safe_tooltip}"];')
safe_tooltip = (full_note
.replace('\\', '\\\\')
.replace('"', '\\"')
.replace('\n', ' ')
.replace('\r', '')
.replace(']', '&#93;'))
safe_nid = nid.replace('"', '_')
dot.append(f' "{safe_nid}" [label={label}, tooltip="{safe_tooltip}"];')
if n["parent"] and n["parent"] in self.nodes:
dot.append(f' "{n["parent"]}" -> "{nid}";')
if n.get("parent") and n["parent"] in self.nodes:
safe_parent = n["parent"].replace('"', '_')
dot.append(f' "{safe_parent}" -> "{safe_nid}";')
dot.append("}")
return "\n".join(dot)