- Wrap per-plugin subprocess.run in try/except TimeoutExpired/OSError so
one plugin's failure appends a result entry and continues the loop
rather than collapsing the whole batch into a 500
- Validate double_sided_copies divisibility against chain_length
(horizontal axis) or parallel (vertical axis) after the range check;
reads effective axis from the current request or stored config
- Exclude double_sided_fields from the generic key-merge loop so
double_sided_enabled/copies/axis are never written as root-level keys
- Fix tools.html copy: "then restores the stash" removed — git_pull
stashes changes but never pops them
- Check r.ok and d.status in loadGitInfo before building the panel;
backend error messages now surface instead of silently showing a
false-clean state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Tools/Utilities tab to the web interface with one-click
maintenance buttons that previously required SSH:
- Git status panel (branch, dirty state, recent commits)
- Pull latest (rebase) and force reset to origin/main
- Reinstall base requirements (pip, with output)
- Reinstall per-plugin requirements (pass/fail per plugin)
- Clear __pycache__ directories
- Quick-access restart for display and web services
Also exposes the hzeller row_address_type option (0–4) in the
Display settings tab. The backend already read this value from
config; the UI, API field list, and validation were missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>