Never undo settings applied on the page: sync from NA Ingest, rebase stale drafts, guard the installer

- config.json is NA Ingest's live settings: the READY hours (Tue 14-22, Wed 9-19, Sun 5-14) and the
  auto-fix hours (Tue 14-22, Wed 9-21, Sun 6:30-14) set on the settings page.
- Settings page: a draft carries what its page started from. A tab that loaded the settings before
  they changed (an Apply elsewhere, a program update) now has only its own edits put on top of the
  live settings; before, it saved its whole older copy and Apply quietly undid the newer values
  (it took ready.prop_host out twice today).
- install.sh refuses (exit 4) to replace live settings that came from an Apply (their fingerprint is
  a history entry's sha_after) unless --replace-config; --keep-config still updates code only.
- Tests pin their own auto-fix and READY hours, so the campus can change its own; the exit-timeout
  test expects 310 s (the Tascam outlet power-cycle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 15:02:17 -04:00
co-authored by Claude Opus 5.5
parent c9ffbc6828
commit 98739bd041
8 changed files with 1702 additions and 1613 deletions
+12 -2
View File
@@ -77,12 +77,15 @@
async function loadAll() {
const [c, s, d, b] = await Promise.all([api("/api/config"), api("/api/schema"), api("/api/draft"), api("/api/builders")]);
state.live = c.config; state.liveSha = c.sha; state.schema = s; state.draft = d.draft; state.draftStale = !!d.stale; state.atemPending = !!d.atem_pending; state.builders = b;
// What this page's draft was made from: a save carries it, so the Mac can put the change on top of
// settings that moved on meanwhile (an Apply elsewhere, a program update) instead of undoing them.
if (state.draft && !state.draft.base && !d.stale) { state.draft.base = c.sha; state.draft.baseConfig = deep(c.config); }
document.getElementById("sitename").textContent = (state.live.site || {}).name || "";
badge();
}
function ensureDraft() {
if (!needEditing()) return false;
if (!state.draft) state.draft = { config: deep(state.live), version: null, by: state.name };
if (!state.draft) state.draft = { config: deep(state.live), version: null, by: state.name, base: state.liveSha, baseConfig: deep(state.live) };
return true;
}
function changed() {
@@ -110,8 +113,15 @@
state.saving = new Promise((done) => {
state.saveTimer = setTimeout(async () => {
try {
const r = await post("/api/draft", { config: state.draft.config, expect: state.draft.version }, "PUT");
const r = await post("/api/draft", { config: state.draft.config, expect: state.draft.version, base: state.draft.base, base_config: state.draft.base ? state.draft.baseConfig : undefined }, "PUT");
state.draft.version = r.draft.version; state.draft.by = r.draft.by; state.draft.started = r.draft.started; state.draft.started_by = r.draft.started_by;
if (r.rebased) { // the settings moved on since this page loaded: keep both
const c = await api("/api/config");
state.live = c.config; state.liveSha = c.sha;
state.draft.config = r.draft.config; state.draft.base = c.sha; state.draft.baseConfig = deep(c.config);
badge(); route();
alertBox("The settings changed on the Mac since this page loaded (an Apply elsewhere, or a program update). Your changes were put on top of the new settings; nothing of either was lost.");
} else if (r.live_sha) { state.draft.base = r.live_sha; }
} catch (e) {
if (e.status === 409) { alertBox(`${e.body.error}: ${(e.body.changed || []).join(", ") || "the draft"}. The page now shows their version.`); state.draft = e.body.draft; route(); }
else if (e.status === 401 || e.status === 403) { alertBox(e.message); state.editing = null; state.name = null; store.set("sp-code", null); showEditing(); }