feat(#90): IMP-56 u1-u19 catch-up before final close (post-u20 push fix)
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 20s

u1: text_overrides axis in user_overrides_io
u2: structure_overrides axis in user_overrides_io
u3: vite allowlist for new endpoints
u4: text_override_resolver
u5: Step 12 text_overrides apply in phase_z2_pipeline
u6: structure_override_resolver
u7: text_path_stamper
u8: SlideCanvas text-edit capture
u9: SlideCanvas structure-edit overlay
u10: userOverridesApi service extension
u11: designAgent types extension
u12: slidePlanUtils restore
u13: user_overrides endpoint tests
u14: user_overrides restore tests
u15: pipeline fallback tests
u16: edit-mode state + gating tests
u17: slide_base print mode CSS
u18: /api/connect endpoint (vite)
u19: /api/export endpoint (vite)

Recovery scope: 29 files (12 modified + 17 new). u20 already pushed in
9439575; this commit lands u1-u19 that were authored but not committed
before #90 was externally closed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 06:12:13 +09:00
parent 943957562f
commit 4da22adb43
29 changed files with 4937 additions and 78 deletions

View File

@@ -3,9 +3,13 @@
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-55 #93 u1 extended to 7 axes by adding ``manual_section_assignment``):
IMP-55 #93 u1 extended to 7 axes by adding ``manual_section_assignment``;
IMP-56 #90 u1 extended to 8 axes by adding ``text_overrides``;
IMP-56 #90 u2 extended to 9 axes by adding ``structure_overrides`` —
scope LOCKED to ``{slot_order, hidden_slots}``; frame swap stays on the
existing ``frames`` axis):
1. Round-trip ``save`` → ``load`` (7 KNOWN_AXES + foreign top-level keys).
1. Round-trip ``save`` → ``load`` (9 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 /
@@ -123,25 +127,50 @@ def _full_payload() -> dict:
},
"slide_css": "<style>.slide .frame-process-product .label { font-size: 14px; }</style>",
"manual_section_assignment": True,
"text_overrides": {"zone-top": {"slot_title.0": "edited headline"}},
"structure_overrides": {
"zone-top": {
"slot_order": ["slot_title", "slot_body"],
"hidden_slots": ["slot_caption"],
}
},
}
def test_known_axes_includes_image_overrides():
"""IMP-51 #79 u1 — ``image_overrides`` is a known axis (now 7 total)."""
"""IMP-51 #79 u1 — ``image_overrides`` is a known axis (now 9 total)."""
assert "image_overrides" in KNOWN_AXES
assert len(KNOWN_AXES) == 7
assert len(KNOWN_AXES) == 9
def test_known_axes_includes_slide_css():
"""IMP-45 #74 u1 — ``slide_css`` is a known axis (7 total)."""
"""IMP-45 #74 u1 — ``slide_css`` is a known axis (9 total)."""
assert "slide_css" in KNOWN_AXES
assert len(KNOWN_AXES) == 7
assert len(KNOWN_AXES) == 9
def test_known_axes_includes_manual_section_assignment():
"""IMP-55 #93 u1 — bool intent marker is a known axis (7 total)."""
"""IMP-55 #93 u1 — bool intent marker is a known axis (9 total)."""
assert "manual_section_assignment" in KNOWN_AXES
assert len(KNOWN_AXES) == 7
assert len(KNOWN_AXES) == 9
def test_known_axes_includes_text_overrides():
"""IMP-56 #90 u1 — ``text_overrides`` is a known axis (9 total)."""
assert "text_overrides" in KNOWN_AXES
assert len(KNOWN_AXES) == 9
def test_known_axes_includes_structure_overrides():
"""IMP-56 #90 u2 — ``structure_overrides`` is a known axis (9 total).
Scope is locked to ``{slot_order, hidden_slots}`` only; frame swap
stays on the existing ``frames`` axis (verified at resolver u6 + Step
12 apply u7). The IO layer itself only enforces the axis name + the
foreign-key preservation invariant.
"""
assert "structure_overrides" in KNOWN_AXES
assert len(KNOWN_AXES) == 9
def test_save_then_load_round_trip(tmp_path):
@@ -170,6 +199,8 @@ def test_save_partial_payload_preserves_other_axes(tmp_path):
assert loaded["image_overrides"] == _full_payload()["image_overrides"]
assert loaded["slide_css"] == _full_payload()["slide_css"]
assert loaded["manual_section_assignment"] is True
assert loaded["text_overrides"] == _full_payload()["text_overrides"]
assert loaded["structure_overrides"] == _full_payload()["structure_overrides"]
def test_save_partial_image_overrides_preserves_other_axes(tmp_path):
@@ -193,6 +224,8 @@ def test_save_partial_image_overrides_preserves_other_axes(tmp_path):
assert loaded["frames"] == _full_payload()["frames"]
assert loaded["slide_css"] == _full_payload()["slide_css"]
assert loaded["manual_section_assignment"] is True
assert loaded["text_overrides"] == _full_payload()["text_overrides"]
assert loaded["structure_overrides"] == _full_payload()["structure_overrides"]
def test_save_axis_replaces_not_deep_merges(tmp_path):
@@ -265,12 +298,15 @@ def test_save_writes_pretty_sorted_json_for_diffability(tmp_path):
key = "03"
save(key, _full_payload(), root=tmp_path)
raw = (tmp_path / "03.json").read_text(encoding="utf-8")
# sort_keys=True → KNOWN_AXES come out alphabetically
# sort_keys=True → KNOWN_AXES come out alphabetically. ``structure_overrides``
# (IMP-56 #90 u2) sorts between ``slide_css`` and ``text_overrides``.
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_structure_overrides = raw.index('"structure_overrides"')
pos_text_overrides = raw.index('"text_overrides"')
pos_zg = raw.index('"zone_geometries"')
pos_zs = raw.index('"zone_sections"')
assert (
@@ -279,6 +315,8 @@ def test_save_writes_pretty_sorted_json_for_diffability(tmp_path):
< pos_layout
< pos_manual
< pos_slide_css
< pos_structure_overrides
< pos_text_overrides
< pos_zg
< pos_zs
)