feat(#79): IMP-51 image_overrides axis (u1~u11 backend stamp+CLI+CSS inject + frontend drag/resize+persistence + tests)

This commit is contained in:
2026-05-22 21:54:38 +09:00
parent bd8bcf748b
commit 6f1c7367e0
18 changed files with 2311 additions and 32 deletions

View File

@@ -1,19 +1,26 @@
"""IMP-52 (#80) u1 — user_overrides.json persistence layer (backend IO).
Persists the four CLI-wired override axes per MDX so a subsequent render
Persists the CLI-wired override axes per MDX so a subsequent render
auto-restores user choices without re-clicking. Source of truth = MDX-keyed
file (stem of the MDX path), NOT ``data/runs/<run_id>/`` which mints a fresh
run_id per ``/api/run`` invocation.
Schema (4 axes; stable order):
Schema (5 axes; stable order; IMP-51 #79 u1 added ``image_overrides``):
{
"layout": <string|null>,
"zone_geometries": {<zone_id>: {"x": float, "y": float, "w": float, "h": float}},
"zone_sections": {<zone_id>: [<section_id>, ...]},
"frames": {<unit_id>: <template_id>}
"frames": {<unit_id>: <template_id>},
"image_overrides": {<image_id>: {"x": float, "y": float, "w": float, "h": float}}
}
``image_id`` is the stable identifier emitted by the user-content image
stamper (IMP-51 u4) and matched via the selector
``.slide img[data-image-role="user-content"]``. Coordinates are
percent-of-slide (zone-agnostic, slide-absolute) to match the SlideCanvas
edit-mode handle conventions in IMP-51 u8~u11.
``unit_id`` is the convention already used by ``--override-frame`` :
``"+".join(source_section_ids)`` (e.g., ``"03-1"`` or ``"03-1+03-2"``).
@@ -46,10 +53,17 @@ from typing import Any, Optional
_PKG_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_OVERRIDES_ROOT = _PKG_ROOT / "data" / "user_overrides"
# The four in-scope axes. Any other top-level key in the file is preserved
# but ignored by callers — keeps the file forward-compatible with future
# axes (e.g., zone_sizes, image_overrides) without a schema bump here.
KNOWN_AXES: tuple[str, ...] = ("layout", "zone_geometries", "zone_sections", "frames")
# The five in-scope axes (IMP-51 #79 u1 added ``image_overrides``). Any
# other top-level key in the file is preserved but ignored by callers —
# keeps the file forward-compatible with future axes (e.g., zone_sizes)
# without a schema bump here.
KNOWN_AXES: tuple[str, ...] = (
"layout",
"zone_geometries",
"zone_sections",
"frames",
"image_overrides",
)
# Key validation — MDX stem must be safe for filesystem use. Allow
# alphanumerics, underscore, hyphen, and dot in the middle (sample stems
@@ -92,7 +106,7 @@ def load(key: str, root: Optional[Path] = None) -> dict[str, Any]:
Missing file → ``{}``. Corrupt JSON → warning to stderr + ``{}``.
Returns the raw mapping (including any foreign keys); callers should
pick the four KNOWN_AXES they care about.
pick the KNOWN_AXES they care about.
"""
path = override_path(key, root=root)
if not path.exists():