feat(#93): IMP-55 u1~u12 frontend manual section swap detection (manual_section_assignment bool axis + drag-only marker gate + dual-axis persistence + backend manual-true gate)
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9s

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 08:27:09 +09:00
parent 9062931863
commit 4e281a20d8
13 changed files with 834 additions and 52 deletions

View File

@@ -2,9 +2,10 @@
Covers the persisted axes called out in the Stage 2 plan
(IMP-51 #79 u1 extended this to 5 axes by adding ``image_overrides``;
IMP-45 #74 u1 extended to 6 axes by adding ``slide_css``):
IMP-45 #74 u1 extended to 6 axes by adding ``slide_css``;
IMP-55 #93 u1 extended to 7 axes by adding ``manual_section_assignment``):
1. Round-trip ``save`` → ``load`` (6 KNOWN_AXES + foreign top-level keys).
1. Round-trip ``save`` → ``load`` (7 KNOWN_AXES + foreign top-level keys).
2. Unknown-key passthrough (foreign axes preserved across partial merges).
3. Missing / corrupt / non-object behavior (graceful ``{}`` + stderr warning).
4. Invalid keys (``InvalidOverrideKey`` raised on traversal / separators /
@@ -121,19 +122,26 @@ def _full_payload() -> dict:
"img-1": {"x": 10.0, "y": 20.0, "w": 30.0, "h": 25.0},
},
"slide_css": "<style>.slide .frame-process-product .label { font-size: 14px; }</style>",
"manual_section_assignment": True,
}
def test_known_axes_includes_image_overrides():
"""IMP-51 #79 u1 — ``image_overrides`` is a known axis (now 6 total)."""
"""IMP-51 #79 u1 — ``image_overrides`` is a known axis (now 7 total)."""
assert "image_overrides" in KNOWN_AXES
assert len(KNOWN_AXES) == 6
assert len(KNOWN_AXES) == 7
def test_known_axes_includes_slide_css():
"""IMP-45 #74 u1 — ``slide_css`` is a known axis (6 total)."""
"""IMP-45 #74 u1 — ``slide_css`` is a known axis (7 total)."""
assert "slide_css" in KNOWN_AXES
assert len(KNOWN_AXES) == 6
assert len(KNOWN_AXES) == 7
def test_known_axes_includes_manual_section_assignment():
"""IMP-55 #93 u1 — bool intent marker is a known axis (7 total)."""
assert "manual_section_assignment" in KNOWN_AXES
assert len(KNOWN_AXES) == 7
def test_save_then_load_round_trip(tmp_path):
@@ -161,6 +169,7 @@ def test_save_partial_payload_preserves_other_axes(tmp_path):
assert loaded["frames"] == _full_payload()["frames"]
assert loaded["image_overrides"] == _full_payload()["image_overrides"]
assert loaded["slide_css"] == _full_payload()["slide_css"]
assert loaded["manual_section_assignment"] is True
def test_save_partial_image_overrides_preserves_other_axes(tmp_path):
@@ -183,6 +192,7 @@ def test_save_partial_image_overrides_preserves_other_axes(tmp_path):
assert loaded["zone_sections"] == _full_payload()["zone_sections"]
assert loaded["frames"] == _full_payload()["frames"]
assert loaded["slide_css"] == _full_payload()["slide_css"]
assert loaded["manual_section_assignment"] is True
def test_save_axis_replaces_not_deep_merges(tmp_path):
@@ -226,6 +236,24 @@ def test_save_preserves_foreign_top_level_keys(tmp_path):
assert loaded["schema_version"] == pre_seed["schema_version"]
def test_save_manual_section_assignment_round_trips_both_booleans(tmp_path):
"""IMP-55 #93 u1 — bool axis round-trips true/false and clears on None.
Asserts the bool is preserved literally (not coerced to int / string) so
the backend pipeline (u9) can branch on ``is True`` without false-positive
matches from truthy-but-not-True values seeded by older callers.
"""
key = "03"
save(key, {"manual_section_assignment": True}, root=tmp_path)
assert load(key, root=tmp_path)["manual_section_assignment"] is True
save(key, {"manual_section_assignment": False}, root=tmp_path)
assert load(key, root=tmp_path)["manual_section_assignment"] is False
save(key, {"manual_section_assignment": None}, root=tmp_path)
assert "manual_section_assignment" not in load(key, root=tmp_path)
def test_save_creates_parent_directory(tmp_path):
nested = tmp_path / "deep" / "nest"
assert not nested.exists()
@@ -241,6 +269,7 @@ def test_save_writes_pretty_sorted_json_for_diffability(tmp_path):
pos_frames = raw.index('"frames"')
pos_image_overrides = raw.index('"image_overrides"')
pos_layout = raw.index('"layout"')
pos_manual = raw.index('"manual_section_assignment"')
pos_slide_css = raw.index('"slide_css"')
pos_zg = raw.index('"zone_geometries"')
pos_zs = raw.index('"zone_sections"')
@@ -248,6 +277,7 @@ def test_save_writes_pretty_sorted_json_for_diffability(tmp_path):
pos_frames
< pos_image_overrides
< pos_layout
< pos_manual
< pos_slide_css
< pos_zg
< pos_zs