screentinker/server
ScreenTinker 04e0e8fdf4 Measure and thumbnail an image from a single decode
Ingest asked for metadata() then writeThumbnail(), which decoded the file twice.
That pairing was free under sharp, whose .metadata() only parses the header, but
every decode here is a full one — ~1s for a 12MP photo — so the naive translation
doubled the most expensive thing on the ingest path.

image-ops.measureAndThumbnail() returns both from one decode. Full ingest of a
12MP photo: 2 decodes/~1.9s -> 1150ms, still with zero event-loop stalls.

The subtlety is the failure contract. In the two-call version width and height
were assigned BEFORE the thumbnail was attempted, so a failed thumbnail still left
usable dimensions on the row — the player needs them to letterbox. Merging naively
would have turned any thumbnail failure into total metadata loss. So a WRITE
failure is reported ({thumbnailWritten:false, thumbnailError}) with the dimensions
intact, and the caller sets thumbnail_path only when the write succeeded, keeping
the phantom-path discipline. A DECODE failure still throws — there is nothing to
report about an unreadable image.

backfill-rotation-dims.js deliberately keeps the separate calls: it probes every
image row but regenerates a thumbnail only for the few whose dimensions changed,
so pairing them there would decode files it has no reason to thumbnail.

Tests count decodes rather than timing them — an exact property, and a wall-clock
comparison would be flaky under load. The count filters for reads of the file under
test: Node's ESM loader also goes through fs.promises.readFile, so a raw call count
picks up jimp's and the WASM codecs' lazy loading and reads 30 instead of 1.

1649/1649 pass. Ingest re-verified across all 7 formats with sharp moved aside.
2026-08-13 11:34:23 -05:00
..
config fix(logging): gate CF-Connecting-IP on a Cloudflare peer, not any trusted proxy 2026-07-26 10:23:04 -05:00
db Merge pull request #254 from ChrisChrome/main 2026-08-11 15:45:39 -05:00
lib Measure and thumbnail an image from a single decode 2026-08-13 11:34:23 -05:00
middleware Merge branch 'fix/recovery-grants' into release/auth-campaign 2026-07-26 16:22:59 -05:00
player Add org-level widget sandbox isolation toggle with warnings 2026-08-10 21:14:42 +00:00
routes Link start cannot be navigated to: a bearer token does not survive it 2026-08-12 14:44:23 -05:00
scripts spike: replace sharp with pure-JS image ops (jimp + jsquash WASM) 2026-08-13 11:09:59 -05:00
services #240: stop the morning wave buying itself a blocking checkpoint 2026-08-06 20:22:21 -05:00
test Measure and thumbnail an image from a single decode 2026-08-13 11:34:23 -05:00
ws Add org-level widget sandbox isolation toggle with warnings 2026-08-10 21:14:42 +00:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js SSO: prove domain ownership by DNS, and fix what the second review found 2026-08-10 19:23:46 -05:00
package-lock.json spike: replace sharp with pure-JS image ops (jimp + jsquash WASM) 2026-08-13 11:09:59 -05:00
package.json spike: replace sharp with pure-JS image ops (jimp + jsquash WASM) 2026-08-13 11:09:59 -05:00
server.js Close the third QA round: limiter bypass, stored XSS, break-glass, org placement 2026-08-11 11:25:11 -05:00
smoke-ui.js Keep the smoke test out of npm test, and update the lockfile 2026-07-28 20:34:34 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00