# R2 C3 step 3a, 2026-09-27: one submit saves every changed pick on a Booth # page (the marks page, the lightbox's verdict aside, the review rail). The # Booth-page half of the operator's report; the verbatim half is # u3_submit_all.toml. Contract: docs/contracts/r2_flow.contract.md, C3 step 3a. # The flight-window and refusal rows came from the heid bug-hunt panel on the # first cut. unit = "r2 submit all" [[mutation]] label = "the other pick forms are ignored (one form, one save, as before)" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_one_submit_on_the_marks_page_saves_every_changed_pick" old = ''' var batch = pickBatch(form);''' new = ''' var batch = null;''' [[mutation]] label = "the pressed pick is sent even when blank (its 400 reloads the rest away)" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_pressing_a_blank_picks_submit_saves_the_others_and_skips_it" old = ''' return others ? picks.filter(function (f) { return dirty(f) && !pending[flightKey(f)]; }) : null;''' new = ''' return others ? picks.filter(function (f) { return (f === form || dirty(f)) && !pending[flightKey(f)]; }) : null;''' [[mutation]] label = "a refusal stops the picks after it" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_refused_pick_in_a_batch_costs_only_itself_and_clears_nothing" old = ''' return post(f, datas[i]).then(function () {''' new = ''' if (refused.length) return; return post(f, datas[i]).then(function () {''' [[mutation]] label = "a refused batch reloads (the refused pick and every draft are lost)" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_refused_pick_in_a_batch_costs_only_itself_and_clears_nothing" old = ''' var shown = saved.length ? refresh(saved, true) : Promise.resolve(true);''' new = ''' if (refused.length) { fail(); return; } var shown = saved.length ? refresh(saved, true) : Promise.resolve(true);''' [[mutation]] label = "a refused pick counts as sent, so its input comes back as the server has it" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_refused_pick_in_a_batch_costs_only_itself_and_clears_nothing" old = ''' }, function (e) { refused.push(askOf(f) + ' (' + e.message + ')'); });''' new = ''' }, function (e) { saved.push({key: flightKey(f), snap: snaps[i]}); refused.push(askOf(f) + ' (' + e.message + ')'); });''' [[mutation]] label = "a form in flight stops counting as another dirty form (a clean press 400s mid-save)" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_pressing_a_clean_pick_during_a_batch_sends_nothing" old = ''' return f.getAttribute('action') === action && isPick(f);''' new = ''' return f.getAttribute('action') === action && isPick(f) && !pending[flightKey(f)];''' [[mutation]] label = "in flight is marked on the node, so a swap's fresh copy can be sent twice" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_pick_in_flight_stays_in_flight_across_another_saves_swap" old = ''' function flightKey(f) { return formKey(f); }''' new = ''' var nth = 0; function flightKey(f) { return f.__fk || (f.__fk = 'n' + (++nth)); }''' [[mutation]] label = "a new save does not clear the last one's words" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_later_save_clears_a_stale_not_saved_line" old = ''' st.textContent = text;''' new = ''' if (!st.textContent) st.textContent = text;''' [[mutation]] label = "a batch whose refresh fails reloads (every unsent draft with it)" file = "booth/templates/base.html" test = "tests/test_flow_browser.py::test_a_batch_whose_refresh_fails_keeps_the_page" old = ''' var shown = saved.length ? refresh(saved, true) : Promise.resolve(true);''' new = ''' var shown = saved.length ? refresh(saved, true).catch(function (e) { reload(); throw e; }) : Promise.resolve(true);''' [[mutation]] 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 = ''' return snap !== null && snap === serial(f);''' new = ''' 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) return recs[i].snap;''' new = ''' 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;'''