fix(inplace,embed): input set back mid-flight, report inputs, ambiguous anchors

Four items owed after S5b, reported by design-dev during the anti-slop run:

- carry() measured a sent-then-changed form against its OLD DEFAULTS. An
  answer set back mid-flight to the value the page first showed read as
  untouched, and the swap put the just-saved value over it. A form sent and
  then changed is now measured against its sent snapshot (sentSet.snapOf).
- The embed's clean-batch reload saw only our own forms. A report's own
  inputs lost whatever the operator had typed into them. Unsaved text in
  any control we don't own now holds the reload, and the page says so.
- Two r2b.toml rows ("D3 a stored theme...", "D3 forced light...") matched
  twice, so they proved only by where the first match fell. Both are
  re-anchored, and scripts/mutation_check.py now refuses any anchor that
  matches more than once. A new tool control covers that.
- The r2_flow contract's C3 steps 2 and 4 now say what S5b superseded. U3
  gains the report-input rule.

Mutation rows: u3_submit_all +1, r2_submit_all +1. Four rows were
re-anchored onto the moved lines.
This commit is contained in:
vh
2026-09-28 16:52:42 -07:00
parent a22a00b82e
commit 377e652670
13 changed files with 194 additions and 32 deletions
+1 -1
View File
@@ -126,7 +126,7 @@ new = ''''''
label = 'C3 client: an unsent radio is not carried across a swap'
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_an_unsaved_choice_survives_a_save_elsewhere_and_a_double_click_writes_once"
old = ''' if (el.checked !== el.defaultChecked) t.checked = el.checked;'''
old = ''' if (el.checked !== on) t.checked = el.checked;'''
new = ''''''
[[mutation]]
+13 -4
View File
@@ -96,15 +96,24 @@ label = "a sent form counts as sent even when it changed after the press"
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_a_change_to_a_sent_pick_during_the_flight_is_kept"
old = '''
if (recs[i].key === k && recs[i].snap === serial(f)) return true;'''
return snap !== null && snap === serial(f);'''
new = '''
if (recs[i].key === k) return true;'''
return snap !== null;'''
[[mutation]]
label = "no form counts as sent (a saved note's text carries back as a draft)"
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_a_saved_notes_box_comes_back_empty"
old = '''
if (recs[i].key === k && recs[i].snap === serial(f)) return true;'''
if (recs[i].key === k) return recs[i].snap;'''
new = '''
if (false) return true;'''
if (false) return recs[i].snap;'''
[[mutation]]
label = "a sent-then-changed form is measured against its OLD defaults (a value set back mid-flight is lost)"
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_a_sent_pick_set_back_to_its_first_value_mid_flight_is_kept"
old = '''
var snap = isSent.snapOf ? isSent.snapOf(el.form) : null;'''
new = '''
var snap = null;'''
+4
View File
@@ -384,8 +384,10 @@ label = "D3 a stored theme is not applied at load (a reload forgets it)"
file = "booth/templates/base.html"
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
old = '''
var t = localStorage.getItem('booth.theme');
if (t === 'light' || t === 'dark') d.setAttribute('data-theme', t);'''
new = '''
var t = localStorage.getItem('booth.theme');
if (false) d.setAttribute('data-theme', t);'''
[[mutation]]
@@ -402,8 +404,10 @@ label = "D3 forced light is not in the sheet"
file = "booth/templates/_svos_tokens.css"
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
old = '''
/* ... or the viewer forced light. Same declarations as above, by construction. */
:root[data-theme="light"] {'''
new = '''
/* ... or the viewer forced light. Same declarations as above, by construction. */
:root[data-theme="light-OFF"] {'''
[[mutation]]
+12 -3
View File
@@ -67,7 +67,7 @@ label = "a refusal reloads the page and clears what was entered"
file = "booth/static/embed.js"
test = "tests/test_embed_browser.py::test_a_refused_ask_costs_only_itself_and_clears_nothing"
old = '''
if (!failed.length && !changed) { location.reload(); return; }'''
if (!failed.length && !changed && !host) { location.reload(); return; }'''
new = '''
location.reload(); return;'''
@@ -76,9 +76,9 @@ label = "only dirtiness blocks the reload (a refused form set back to its first
file = "booth/static/embed.js"
test = "tests/test_embed_browser.py::test_a_refusal_blocks_the_reload_even_when_nothing_reads_dirty"
old = '''
if (!failed.length && !changed) { location.reload(); return; }'''
if (!failed.length && !changed && !host) { location.reload(); return; }'''
new = '''
if (!changed) { location.reload(); return; }'''
if (!changed && !host) { location.reload(); return; }'''
[[mutation]]
label = "a refusal is never said"
@@ -139,3 +139,12 @@ test = "tests/test_embed_browser.py::test_the_empty_status_line_stays_hidden_und
old = '''
".bk-ask-status[hidden]{display:none}",'''
new = ''''''
[[mutation]]
label = "the reload ignores the report's own inputs (text typed into the host page is lost)"
file = "booth/static/embed.js"
test = "tests/test_embed_browser.py::test_a_clean_batch_does_not_reload_over_text_typed_into_the_report"
old = '''
if (!failed.length && !changed && !host) { location.reload(); return; }'''
new = '''
if (!failed.length && !changed) { location.reload(); return; }'''