fix: stop image compression blocking the main thread #6

Merged
iec-admin merged 1 commit from claw/compress-perf-d4e8 into main 2026-09-11 23:16:15 +00:00
Contributor

Closes #5. Follow-up to #3.

Picking a large image made the admin look like it had hung — overlay stuck on Preparing…, bar at 0%, page unresponsive.

Cause

  1. The quality ladder was walked at every scale — up to 25 full canvas re-encodes. Measured on a 12 MP incompressible image: 4,721 ms across 11 encodes on a fast laptop.
  2. Nothing yielded to the event loop, so React never repainted and the bar sat frozen at 0%.

Fix

Encoded size tracks pixel count closely at fixed quality, so the overshoot ratio from one attempt estimates the scale the next needs. That converges in 2–3 attempts (capped at 4) instead of walking a ladder. The loop yields between attempts so the UI can paint, and the preparing state now shows an indeterminate pulsing bar rather than a determinate 0%.

Measured

Case Before After
12 MP incompressible (worst case) 4,721 ms / 11 encodes 1,367 ms / 2 encodes
2.4 MB realistic photo 359 ms / 1 encode 418 ms / 1 encode

Quality is unaffected — the realistic photo still lands at full 2560×1920 (324 KB).

Re-verified end-to-end

Driven through the admin UI with Playwright against the real code:

Case Result
12 MB incompressible uploaded — 662,014 bytes, 1404×1053
2.4 MB realistic photo uploaded — 2560×1920
278 KB photo in limits passed through byte-identical
2.9 MB video blocked client-side with a clear message

npm run type-check and npm run build clean.

🤖 Generated with Claude Code

Closes #5. Follow-up to #3. Picking a large image made the admin look like it had hung — overlay stuck on `Preparing…`, bar at 0%, page unresponsive. ## Cause 1. The quality ladder was walked at **every** scale — up to 25 full canvas re-encodes. Measured on a 12 MP incompressible image: 4,721 ms across 11 encodes on a fast laptop. 2. Nothing yielded to the event loop, so React never repainted and the bar sat frozen at 0%. ## Fix Encoded size tracks pixel count closely at fixed quality, so the overshoot ratio from one attempt estimates the scale the next needs. That converges in 2–3 attempts (capped at 4) instead of walking a ladder. The loop yields between attempts so the UI can paint, and the preparing state now shows an indeterminate pulsing bar rather than a determinate 0%. ## Measured | Case | Before | After | |---|---|---| | 12 MP incompressible (worst case) | 4,721 ms / 11 encodes | **1,367 ms / 2 encodes** | | 2.4 MB realistic photo | 359 ms / 1 encode | 418 ms / 1 encode | Quality is unaffected — the realistic photo still lands at full 2560×1920 (324 KB). ## Re-verified end-to-end Driven through the admin UI with Playwright against the real code: | Case | Result | |---|---| | 12 MB incompressible | uploaded — 662,014 bytes, 1404×1053 | | 2.4 MB realistic photo | uploaded — 2560×1920 | | 278 KB photo in limits | passed through byte-identical | | 2.9 MB video | blocked client-side with a clear message | `npm run type-check` and `npm run build` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Picking a large image made the admin look like it had hung — the overlay sat
on "Preparing…" with the bar at 0% and the page unresponsive.

Two causes. The quality ladder was walked at every scale, so a large photo
could take up to 25 full canvas re-encodes; a 12 MP incompressible image
measured 4,721ms across 11 encodes on a fast laptop, and several times that
on a mid-range machine. Nothing yielded to the event loop between them, so
React never repainted and the bar stayed frozen at 0%, which reads as a hang
rather than as work in progress.

Encoded size tracks pixel count closely at a fixed quality, so the overshoot
ratio from one attempt estimates the scale the next needs. Using that instead
of a fixed ladder converges in two or three attempts, capped at four, and the
loop now yields between them so the UI can paint. The preparing state shows
an indeterminate pulsing bar, since re-encoding has no progress to report.

Worst case drops from 4,721ms/11 encodes to 1,367ms/2. A realistic 2.4 MB
photo is unchanged at one encode and still lands at full 2560x1920.

close #5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
iec-admin deleted branch claw/compress-perf-d4e8 2026-09-11 23:16:15 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
martin/ballantyne-www!6
No description provided.