# U7 — every falsifier the navigation unit claims, and the change it forbids.
#
# Generated from the session harness that proved them, not retyped. Each `old`
# must match the source byte-for-byte; a drifted anchor is REPORTED, never
# silently skipped — a table that stops matching stops proving anything.
unit = "the rail, the filters, the grid keyboard, and the groups"
[[mutation]]
label = "INV-2 sort the grid by (group, rel) so groups render contiguously"
file = "booth/app.py"
test = "tests/test_navigation.py::test_grouping_never_reorders_the_grid"
old = '''
shown = buckets[active]'''
new = '''
shown = sorted(buckets[active], key=lambda i: (i["group"] or "", i["name"]))'''
[[mutation]]
label = "INV-3a drop the >=2 groups guard (a rail with one row)"
file = "booth/app.py"
test = "tests/test_navigation.py::test_no_group_rail_when_there_is_only_one_group"
old = '''
if len(sizes) < 2 or sizes[len(sizes) // 2] <= 1:'''
new = '''
if len(sizes) < 1 or sizes[len(sizes) // 2] <= 1:'''
[[mutation]]
label = "INV-3b drop the median guard (a rail that is a second copy of the grid)"
file = "booth/app.py"
test = "tests/test_navigation.py::test_no_group_rail_when_every_item_is_its_own_group"
old = '''
if len(sizes) < 2 or sizes[len(sizes) // 2] <= 1:'''
new = '''
if len(sizes) < 2:'''
[[mutation]]
label = "groups derived from the FULL gallery, not the rendered list"
file = "booth/app.py"
test = "tests/test_navigation.py::test_groups_describe_the_filtered_grid"
old = '''
"groups": _groups(shown),'''
new = '''
"groups": _groups(gallery),'''
[[mutation]]
label = "the anchor names the group key instead of the tile id"
file = "booth/app.py"
test = "tests/test_navigation.py::test_every_group_anchor_lands_on_a_rendered_tile"
old = '''
{"key": k, "n": len(v), "anchor": f"item-{v[0]['url']}"}'''
new = '''
{"key": k, "n": len(v), "anchor": f"group-{k}"}'''
[[mutation]]
label = "the rail orders groups alphabetically instead of by first member"
file = "booth/app.py"
test = "tests/test_navigation.py::test_group_order_is_the_position_of_the_first_member"
old = '''
for k, v in by_group.items()'''
new = '''
for k, v in sorted(by_group.items())'''
[[mutation]]
label = "the rail orders groups by count, which the docstring also claims differs"
file = "booth/app.py"
test = "tests/test_navigation.py::test_group_order_is_the_position_of_the_first_member"
old = '''
for k, v in by_group.items()'''
new = '''
for k, v in sorted(by_group.items(), key=lambda kv: -len(kv[1]))'''
[[mutation]]
label = "_group_of reverts to the contract's original strip-trailing-digits rule"
file = "booth/items.py"
test = "tests/test_items.py::test_group_of_takes_the_first_segment"
old = '''
segs = _SEG.split(stem)
if len(segs) == 1:
return re.sub(r"\d+$", "", stem) or None
return segs[0] or None'''
new = '''
m = re.match(r"^(.*?)[-_. ]?\d+$", stem)
return (m.group(1) or None) if m else (stem or None)'''
[[mutation]]
label = "INV-1 a route body derives the group inline"
file = "booth/app.py"
test = "tests/test_navigation.py::test_no_route_body_derives_a_group"
old = '''
by_group: dict[str, list[dict]] = {}'''
new = '''
_ = _group_of # noqa
by_group: dict[str, list[dict]] = {}'''
[[mutation]]
label = "the rail markup is emitted with |safe"
file = "booth/templates/booth.html"
test = "tests/test_navigation.py::test_a_hostile_filename_cannot_break_out_of_the_rail"
old = '''
href="#{{ g.anchor }}">{{ g.key }} {{ g.n }}'''
new = '''
href="#{{ g.anchor }}">{{ g.key|safe }} {{ g.n }}'''
[[mutation]]
label = "a flat all-digit stem yields the empty string instead of None"
file = "booth/items.py"
test = "tests/test_navigation.py::test_a_group_key_is_never_the_empty_string"
old = '''
return re.sub(r"\d+$", "", stem) or None'''
new = '''
return re.sub(r"\d+$", "", stem)'''
[[mutation]]
label = "the template renders the group row whenever there is any group at all"
file = "booth/templates/booth.html"
test = "tests/test_navigation.py::test_no_group_rail_when_every_item_is_its_own_group"
old = '''
{% if rail.groups %}'''
new = '''
{% if rail.groups is not none %}'''
[[mutation]]
label = "the anchor is built from the raw name instead of the encoded url"
file = "booth/app.py"
test = "tests/test_navigation.py::test_a_group_anchor_survives_a_filename_that_percent_decodes"
old = '''
{"key": k, "n": len(v), "anchor": f"item-{v[0]['url']}"}'''
new = '''
{"key": k, "n": len(v), "anchor": f"item-{v[0]['name']}"}'''
[[mutation]]
label = "the anchor names the LAST member instead of the first"
file = "booth/app.py"
test = "tests/test_navigation.py::test_a_group_anchor_names_the_FIRST_member"
old = '''
{"key": k, "n": len(v), "anchor": f"item-{v[0]['url']}"}'''
new = '''
{"key": k, "n": len(v), "anchor": f"item-{v[-1]['url']}"}'''
[[mutation]]
label = "a group row over-reports its own size"
file = "booth/app.py"
test = "tests/test_navigation.py::test_a_group_row_reports_its_own_size"
old = '''
{"key": k, "n": len(v), "anchor": f"item-{v[0]['url']}"}'''
new = '''
{"key": k, "n": len(v) + 1, "anchor": f"item-{v[0]['url']}"}'''
[[mutation]]
label = "the informativeness guard reads the LARGEST group, not the middle"
file = "booth/app.py"
test = "tests/test_navigation.py::test_the_informativeness_guard_reads_the_middle_not_the_largest"
old = '''
if len(sizes) < 2 or sizes[len(sizes) // 2] <= 1:'''
new = '''
if len(sizes) < 2 or sizes[-1] <= 1:'''
[[mutation]]
label = "the rail is gated on the FILTERED list, removing the way back"
file = "booth/templates/booth.html"
test = "tests/test_navigation.py::test_a_filter_that_matches_nothing_leaves_a_way_back"
old = '''
{% elif all_items %}'''
new = '''
{% elif items %}'''
[[mutation]]
label = "the keyboard flag selector names a class nothing emits"
file = "booth/templates/booth.html"
test = "tests/test_navigation.py::test_the_keyboard_flag_targets_a_real_button"
old = '''
case 'f': click('.flagtoggle button');'''
new = '''
case 'f': click('.flagbtn, [name="target"]');'''
[[mutation]]
label = "an unrepresentable filename is let through and 500s the booth"
file = "booth/items.py"
test = "tests/test_items.py::test_one_unrepresentable_filename_costs_its_own_tile_not_the_booth"
old = '''
try:
quote(rel, safe="/")
except UnicodeEncodeError:'''
new = '''
try:
pass
except UnicodeEncodeError:'''
[[mutation]]
label = "the grid cursor starts at tile 0, so an arrow undoes a group jump"
file = "booth/templates/booth.html"
test = "tests/test_embed_browser.py::test_an_arrow_after_a_group_jump_does_not_scroll_back"
old = '''
case 'ArrowRight': focus(at < 0 ? fromViewport() : at + 1);'''
new = '''
case 'ArrowRight': focus(at + 1);'''
[[mutation]]
label = "the link board drops its href scheme guard"
file = "booth/links.py"
test = "tests/test_booth.py::test_the_link_board_refuses_to_render_a_script_href"
old = '''
return parts.scheme.lower() in ("http", "https")'''
new = '''
return True'''