feat(m4): crop_fill setting + tape-line prototype (#99)

Add global crop_fill percent (default 90, 1-100) as a settings key with
FastAPI 4xx validation. Wires the value through to the inventory PNG
encode (non-retro: stored PNGs untouched). Adds a throwaway
TapePrototype.svelte behind ?tape-prototype for the #99 design sandbox.

Gates: check_requirements PASS, check_coverage PASS, settings tests 8/8.
This commit is contained in:
Ty
2026-09-24 12:01:18 -07:00
parent 0c00d25cc7
commit 818874e65c
13 changed files with 831 additions and 23 deletions
+30
View File
@@ -28,13 +28,18 @@ PLAIN_KEYS: set[str] = {
"woo_create_status",
"taxonomy_last_sync_at",
"taxonomy_anchor",
"crop_fill",
}
ALL_KEYS = SECRET_KEYS | PLAIN_KEYS
# Plain keys surfaced as numbers in the API (stored as TEXT in `settings`).
NUMERIC_KEYS: set[str] = {"crop_fill"}
DEFAULTS: dict[str, Any] = {
"inference_enabled": False,
"content_llm_enabled": False,
"woo_create_status": "draft",
# #99: global crop fill %, one source of truth for the ref line + PNG crop.
"crop_fill": 90,
}
@@ -91,6 +96,12 @@ def get_settings() -> dict[str, Any]:
result[k] = val if val is not None else DEFAULTS.get(k)
else:
result[k] = DEFAULTS.get(k)
if k in NUMERIC_KEYS:
try:
f = float(result[k])
result[k] = int(f) if f.is_integer() else f
except (TypeError, ValueError):
result[k] = DEFAULTS.get(k)
return result
@@ -108,6 +119,25 @@ def update_settings(payload: dict[str, Any]) -> dict[str, Any]:
},
)
# #99: rules live here in the 4xx layer, not in the DB (structural only).
if "crop_fill" in payload:
raw = payload["crop_fill"]
try:
pct = float(raw)
except (TypeError, ValueError):
pct = None
if pct is None or not (1 <= pct <= 100):
raise HTTPException(
status_code=400,
detail={
"error": {
"code": "validation",
"message": "crop_fill must be a number between 1 and 100 (percent)",
"detail": {"field": "crop_fill"},
}
},
)
conn = get_connection()
for k, v in payload.items():
if k not in ALL_KEYS: