Fix 4 bugs: SQL conflict handling, HTML escaping, backup cap, safe int cast
- 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>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import html
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -154,13 +155,14 @@ class HistoryTree:
|
|||||||
full_note = n.get('note', 'Step')
|
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 = (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']))
|
ts = time.strftime('%b %d %H:%M', time.localtime(n['timestamp']))
|
||||||
|
|
||||||
# Branch label for tip nodes
|
# Branch label for tip nodes
|
||||||
branch_label = ""
|
branch_label = ""
|
||||||
if nid in tip_to_branches:
|
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
|
# COLORS — per-branch tint, overridden for HEAD and tips
|
||||||
b_name = node_to_branch.get(nid)
|
b_name = node_to_branch.get(nid)
|
||||||
@@ -190,7 +192,7 @@ class HistoryTree:
|
|||||||
+ '</TABLE>>'
|
+ '</TABLE>>'
|
||||||
)
|
)
|
||||||
|
|
||||||
safe_tooltip = full_note.replace('"', "'")
|
safe_tooltip = full_note.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
dot.append(f' "{nid}" [label={label}, tooltip="{safe_tooltip}"];')
|
dot.append(f' "{nid}" [label={label}, tooltip="{safe_tooltip}"];')
|
||||||
|
|
||||||
if n["parent"] and n["parent"] in self.nodes:
|
if n["parent"] and n["parent"] in self.nodes:
|
||||||
|
|||||||
@@ -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):
|
def _render_vace_settings(i, seq, batch_list, data, file_path, state, refresh_list):
|
||||||
# VACE Schedule (needed early for both columns)
|
# 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
|
# Mode reference dialog
|
||||||
with ui.dialog() as ref_dlg, ui.card():
|
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')
|
'outlined').classes('w-full q-mt-sm')
|
||||||
|
|
||||||
# VACE Length + output calculation
|
# VACE Length + output calculation
|
||||||
input_a = int(seq.get('input_a_frames', 16))
|
input_a = _safe_int(seq.get('input_a_frames', 16), 16)
|
||||||
input_b = int(seq.get('input_b_frames', 16))
|
input_b = _safe_int(seq.get('input_b_frames', 16), 16)
|
||||||
stored_total = int(seq.get('vace_length', 49))
|
stored_total = _safe_int(seq.get('vace_length', 49), 49)
|
||||||
mode_idx = int(seq.get('vace schedule', 1))
|
mode_idx = _safe_int(seq.get('vace schedule', 1), 1)
|
||||||
|
|
||||||
if mode_idx == 0:
|
if mode_idx == 0:
|
||||||
base_length = max(stored_total - input_a, 1)
|
base_length = max(stored_total - input_a, 1)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ def _delete_nodes(htree, data, file_path, node_ids):
|
|||||||
if 'history_tree_backup' not in data:
|
if 'history_tree_backup' not in data:
|
||||||
data['history_tree_backup'] = []
|
data['history_tree_backup'] = []
|
||||||
data['history_tree_backup'].append(copy.deepcopy(htree.to_dict()))
|
data['history_tree_backup'].append(copy.deepcopy(htree.to_dict()))
|
||||||
|
data['history_tree_backup'] = data['history_tree_backup'][-10:]
|
||||||
for nid in node_ids:
|
for nid in node_ids:
|
||||||
htree.nodes.pop(nid, None)
|
htree.nodes.pop(nid, None)
|
||||||
for b, tip in list(htree.branches.items()):
|
for b, tip in list(htree.branches.items()):
|
||||||
|
|||||||
3
utils.py
3
utils.py
@@ -210,7 +210,8 @@ def sync_to_db(db, project_name: str, file_path: Path, data: dict) -> None:
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
db.conn.execute(
|
db.conn.execute(
|
||||||
"INSERT INTO sequences (data_file_id, sequence_number, data, updated_at) "
|
"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),
|
(df_id, seq_num, json.dumps(item), now),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user