Drafts rebase onto the live settings; the review speaks in words

A draft started before someone else's Apply used to carry that older config and would have put
the newer changes back. Now every read of the draft replays only the draft's own changes onto the
live settings (base config from history), so the review lists exactly what you changed. Values
read in words (windows as days and hours), long ones fold open with was/now, commands are no
longer red, and warnings the live config already had are not repeated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 19:44:51 -04:00
co-authored by Claude Fable 5.1
parent edfd218ac9
commit 0eee778a92
6 changed files with 181 additions and 22 deletions
+23
View File
@@ -268,6 +268,29 @@ class TestServer(unittest.TestCase):
self.assertNotIn("HyperDeck_1", gone[0]["candidates"]) # used by HD1
self.put("/api/draft", {}, method="DELETE")
def test_a_draft_is_rebased_when_the_live_settings_move_on(self):
from test_apply import FakeLaunchctl
self.site.attempts.clear()
fake = FakeLaunchctl(self.data); self.site.applier.run, self.site.applier.sleep = fake, lambda s: None
live = self.site.load()
mine = json.loads(json.dumps(live)); mine["hosts"]["lighting"]["notes"] = "sam's note"
self.put("/api/draft", {"config": mine}, who="sam") # sam's draft
d = self.site.draft.read()
self.site.draft.clear()
other = json.loads(json.dumps(live)); other["debounce_seconds"] = (live["debounce_seconds"] or 3) + 1 # chris applies something else
self.put("/api/draft", {"config": other}, who="chris")
self.put("/api/apply", {"note": "chris", "emergency": {"word": "LIVE", "reason": "test"}}, method="POST", who="chris")
self.site.applier.thread.join(30)
sp.write_json(self.site.draft.path, d) # sam's draft is back, based on the old settings
st, raw = get(self.port, "/api/draft")
r = json.loads(raw)
self.assertFalse(r["stale"])
self.assertEqual(r["draft"]["config"]["hosts"]["lighting"]["notes"], "sam's note") # sam's own change
self.assertEqual(r["draft"]["config"]["debounce_seconds"], other["debounce_seconds"]) # chris's change kept
st, v = self.put("/api/validate", {})
self.assertEqual(len(v["changes"]), 1); self.assertTrue(v["changes"][0]["text"].startswith("computer Lighting PC › notes"))
self.put("/api/draft", {}, method="DELETE")
def test_probe_is_refused_while_a_fix_runs(self):
json.dump({"fix-x": {"key": "hd2"}}, open(os.path.join(self.data, "fixes-running.json"), "w"))
try: