Add atomic writes, magic string constants, unit tests, type hints, and fix navigation

- save_json() now writes to a temp file then uses os.replace() for atomic writes
- Replace hardcoded "batch_data", "history_tree", "prompt_history", "sequence_number"
  strings with constants (KEY_BATCH_DATA, etc.) across all modules
- Add 29 unit tests for history_tree, utils, and json_loader
- Add type hints to public functions in utils.py, json_loader.py, history_tree.py
- Remove ALLOWED_BASE_DIR restriction that blocked navigating outside app CWD
- Fix path text input not updating on navigation by using session state key
- Add unpin button () for removing pinned folders

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 12:44:31 +01:00
parent 326ae25ab2
commit b02bf124fb
15 changed files with 368 additions and 124 deletions

View File

@@ -1,9 +1,9 @@
import streamlit as st
import random
from utils import DEFAULTS, save_json, get_file_mtime
from utils import DEFAULTS, save_json, get_file_mtime, KEY_BATCH_DATA, KEY_PROMPT_HISTORY, KEY_SEQUENCE_NUMBER
def render_single_editor(data, file_path):
is_batch_file = "batch_data" in data or isinstance(data, list)
is_batch_file = KEY_BATCH_DATA in data or isinstance(data, list)
if is_batch_file:
st.info("This is a batch file. Switch to the 'Batch Processor' tab.")
@@ -63,7 +63,7 @@ def render_single_editor(data, file_path):
# Explicitly track standard setting keys to exclude them from custom list
standard_keys = {
"general_prompt", "general_negative", "current_prompt", "negative", "prompt", "seed",
"camera", "flf", "batch_data", "prompt_history", "sequence_number", "ui_reset_token",
"camera", "flf", KEY_BATCH_DATA, KEY_PROMPT_HISTORY, KEY_SEQUENCE_NUMBER, "ui_reset_token",
"model_name", "vae_name", "steps", "cfg", "denoise", "sampler_name", "scheduler"
}
standard_keys.update(lora_keys)
@@ -169,8 +169,8 @@ def render_single_editor(data, file_path):
archive_note = st.text_input("Archive Note")
if st.button("📦 Snapshot to History", use_container_width=True):
entry = {"note": archive_note if archive_note else "Snapshot", **current_state}
if "prompt_history" not in data: data["prompt_history"] = []
data["prompt_history"].insert(0, entry)
if KEY_PROMPT_HISTORY not in data: data[KEY_PROMPT_HISTORY] = []
data[KEY_PROMPT_HISTORY].insert(0, entry)
data.update(entry)
save_json(file_path, data)
st.session_state.last_mtime = get_file_mtime(file_path)
@@ -181,7 +181,7 @@ def render_single_editor(data, file_path):
# --- FULL HISTORY PANEL ---
st.markdown("---")
st.subheader("History")
history = data.get("prompt_history", [])
history = data.get(KEY_PROMPT_HISTORY, [])
if not history:
st.caption("No history yet.")