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

@@ -219,20 +219,23 @@ function vitePluginStorageProxy(): Plugin {
export const USER_OVERRIDES_KEY_RE = /^[A-Za-z0-9_][A-Za-z0-9_.\-]*$/;
// The four in-scope axes — exact mirror of KNOWN_AXES in
// The five in-scope axes — exact mirror of KNOWN_AXES in
// src/user_overrides_io.py. Any payload key outside this allowlist is
// silently dropped by the PUT handler (u4) so the on-disk schema cannot
// drift from the backend pipeline (u2) contract. Foreign top-level keys
// already on disk are preserved verbatim (see mergeUserOverrides).
// IMP-51 (#79) u2: added `image_overrides` (image_id → {x,y,w,h}
// percent-of-slide coordinates).
export const KNOWN_USER_OVERRIDES_AXES = [
"layout",
"zone_geometries",
"zone_sections",
"frames",
"image_overrides",
] as const;
export type KnownUserOverridesAxis = (typeof KNOWN_USER_OVERRIDES_AXES)[number];
// 1MB cap on PUT bodies. Override files in practice are < 10KB (4 axes,
// 1MB cap on PUT bodies. Override files in practice are < 10KB (5 axes,
// each a small dict). The cap is a safety net against runaway client
// loops, not a real schema constraint.
const USER_OVERRIDES_PUT_MAX_BYTES = 1_000_000;
@@ -319,9 +322,9 @@ export function handleGetUserOverrides(
// IMP-52 u4 — pure merge function. Mirrors src/user_overrides_io.save():
// • Only KNOWN_USER_OVERRIDES_AXES present in `partial` are mutated.
// • Axes absent from `partial` are preserved verbatim from `existing`.
// • Foreign top-level keys in `existing` (future axes like zone_sizes
// or image_overrides) are preserved verbatim — allowlist guards what
// the PUT writes, NOT what the file already holds.
// • Foreign top-level keys in `existing` (future axes like zone_sizes)
// are preserved verbatim — allowlist guards what the PUT writes, NOT
// what the file already holds.
// • `partial[axis] = null` is the explicit clear sentinel (remove key).
// • Any non-axis keys in `partial` are silently dropped (allowlist).
export function mergeUserOverrides(