Files
houseplan-card/docs/images/screenshots.json
T
Codex 7c31725ac9 feat: warn about huge backdrops and offer a safe reduced copy (#39)
A picked raster is now classified from its HEADER BYTES ONLY before anything
heavy happens: src/backdrop-probe.ts parses PNG IHDR (+colour type/tRNS for
alpha), JPEG SOF and WebP VP8/VP8L/VP8X at fixed offsets, never using a file
field as an allocation size; hostile or truncated headers collapse to
'unknown', which warns without numbers instead of passing silently. The
thresholds live in that module as the single calibration point
(WARN_DECODED_BYTES 128 MiB ≈ 32 MP, HARD_DIMENSION 16384 — the browser
canvas cap, DOWNSCALE_TARGET_PX 4096), derived from the desktop-Chromium
matrix now committed as demo/benchmark_backdrop_decode.mjs with a
conservative tablet margin documented in the spec.

The shared pick flow (src/backdrop-pick.ts) feeds BOTH lazy runtimes — the
editor space dialog and the onboarding first-space dialog — so the guard
cannot drift between them, and nothing of it enters the eager View graph.
Warn shows the real numbers and three actions; the reduced copy decodes
EXIF-aware, keeps aspect and alpha (PNG stays PNG, opaque becomes JPEG
q0.9) and flows through the ordinary planFile → upload path. Hard has two
phases with one outcome: beyond 16384 px only Cancel; a failed or timed-out
(10 s) reduce closes with a toast, clean staging and NO silent fallback to
the original the user just declined. SVG never reaches the probe. The safe
path swaps the manual byte-loop base64 for FileReader — half the JS-heap
peak on every upload, byte-identical output (parity asserted in the smoke).

Proofs: header-table units incl. a fuzz set of hostile headers and ±1
threshold bounds; smoke_backdrop_guard on the real bundle — zero decode
calls before the choice, byte parity of keep-original, a real 6200 px
reduce to 4096 for both alpha and opaque branches, cancel-only hard
dialog, both phase-2 failures (reject and hang under the test-only timeout
override), re-pick after refusal, SVG bypass; four registry mutants
(probe-always-safe, alpha-dropped, hard-demoted, phase-2 silent fallback).
Spec anchor corrected alongside: the server plan limit is 8 MB
(MAX_PLAN_BYTES), attachments are the 50 MB path — an 8 MB JPEG is easily
80-160 MP decoded, so the client-side guard stays the primary defence.

Issue: #39
User-Visible: yes
2026-08-29 10:13:09 +03:00

122 lines
4.0 KiB
JSON

{
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": null,
"sourceFingerprint": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
"view-desktop": {
"file": "01-view-desktop.png",
"viewport": {
"width": 1180,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "78d328498bd17a9ee1ec44b389d244b61fca9ac52c9aad6631aa85c53233fec0"
},
"view-touch": {
"file": "02-view-touch.png",
"viewport": {
"width": 390,
"height": 760
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
},
"space-create": {
"file": "03-space-create.png",
"viewport": {
"width": 900,
"height": 850
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
},
"room-contour-close": {
"file": "04-room-contour-close.png",
"viewport": {
"width": 1180,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
},
"plan-context-tray": {
"file": "05-plan-context-tray.png",
"viewport": {
"width": 1180,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
},
"device-editor": {
"file": "06-device-editor.png",
"viewport": {
"width": 1180,
"height": 1100
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "8affadfce926fe2f6cf344c33f611c44fcbc79c1969f69c3a6935ff2e99c36c3"
},
"device-display-preview": {
"file": "06-device-display-preview.png",
"viewport": {
"width": 1180,
"height": 1100
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "6f628abe6b89d7c4b352e6d73ac0b126526f6fdba5927bb774c5b884e484ca19"
},
"background-editor": {
"file": "07-background-editor.png",
"viewport": {
"width": 1180,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "a1d1a905f1410f73231d2ea5d1dfbd8ae79c2fe8b0a79cff87ebb977ac7a3103"
},
"room-card": {
"file": "08-room-card.png",
"viewport": {
"width": 1180,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
},
"device-info": {
"file": "09-device-info.png",
"viewport": {
"width": 1000,
"height": 900
},
"theme": "dark",
"language": "en",
"sourceSha256": "d1601738e1c948689333e54d500881e860ba0f1dcaa1559aeacf5e1114d469f5",
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
}
}
}