diff --git a/history_tree.py b/history_tree.py index 4e35861..093d028 100644 --- a/history_tree.py +++ b/history_tree.py @@ -1,3 +1,4 @@ +import html import time import uuid from typing import Any @@ -154,13 +155,14 @@ class HistoryTree: full_note = n.get('note', 'Step') display_note = (full_note[:max_note_len] + '..') if len(full_note) > max_note_len else full_note + display_note = html.escape(display_note) ts = time.strftime('%b %d %H:%M', time.localtime(n['timestamp'])) # Branch label for tip nodes branch_label = "" if nid in tip_to_branches: - branch_label = ", ".join(tip_to_branches[nid]) + branch_label = html.escape(", ".join(tip_to_branches[nid])) # COLORS — per-branch tint, overridden for HEAD and tips b_name = node_to_branch.get(nid) @@ -190,7 +192,7 @@ class HistoryTree: + '>' ) - safe_tooltip = full_note.replace('"', "'") + safe_tooltip = full_note.replace('\\', '\\\\').replace('"', '\\"') dot.append(f' "{nid}" [label={label}, tooltip="{safe_tooltip}"];') if n["parent"] and n["parent"] in self.nodes: diff --git a/tab_batch_ng.py b/tab_batch_ng.py index 857986f..f905cb3 100644 --- a/tab_batch_ng.py +++ b/tab_batch_ng.py @@ -608,7 +608,13 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state, def _render_vace_settings(i, seq, batch_list, data, file_path, state, refresh_list): # VACE Schedule (needed early for both columns) - sched_val = max(0, min(int(seq.get('vace schedule', 1)), len(VACE_MODES) - 1)) + def _safe_int(val, default=0): + try: + return int(float(val)) + except (ValueError, TypeError, OverflowError): + return default + + sched_val = max(0, min(_safe_int(seq.get('vace schedule', 1), 1), len(VACE_MODES) - 1)) # Mode reference dialog with ui.dialog() as ref_dlg, ui.card(): @@ -678,10 +684,10 @@ def _render_vace_settings(i, seq, batch_list, data, file_path, state, refresh_li 'outlined').classes('w-full q-mt-sm') # VACE Length + output calculation - input_a = int(seq.get('input_a_frames', 16)) - input_b = int(seq.get('input_b_frames', 16)) - stored_total = int(seq.get('vace_length', 49)) - mode_idx = int(seq.get('vace schedule', 1)) + input_a = _safe_int(seq.get('input_a_frames', 16), 16) + input_b = _safe_int(seq.get('input_b_frames', 16), 16) + stored_total = _safe_int(seq.get('vace_length', 49), 49) + mode_idx = _safe_int(seq.get('vace schedule', 1), 1) if mode_idx == 0: base_length = max(stored_total - input_a, 1) diff --git a/tab_timeline_ng.py b/tab_timeline_ng.py index d0467f4..521d4da 100644 --- a/tab_timeline_ng.py +++ b/tab_timeline_ng.py @@ -13,6 +13,7 @@ def _delete_nodes(htree, data, file_path, node_ids): if 'history_tree_backup' not in data: data['history_tree_backup'] = [] data['history_tree_backup'].append(copy.deepcopy(htree.to_dict())) + data['history_tree_backup'] = data['history_tree_backup'][-10:] for nid in node_ids: htree.nodes.pop(nid, None) for b, tip in list(htree.branches.items()): diff --git a/utils.py b/utils.py index 75269ab..0ec9fff 100644 --- a/utils.py +++ b/utils.py @@ -210,7 +210,8 @@ def sync_to_db(db, project_name: str, file_path: Path, data: dict) -> None: now = time.time() db.conn.execute( "INSERT INTO sequences (data_file_id, sequence_number, data, updated_at) " - "VALUES (?, ?, ?, ?)", + "VALUES (?, ?, ?, ?) " + "ON CONFLICT(data_file_id, sequence_number) DO UPDATE SET data=excluded.data, updated_at=excluded.updated_at", (df_id, seq_num, json.dumps(item), now), )