DEV Community

Cover image for Are You a Web Developer or a Browser Expert? 7 Tests I Failed Building a Photo Editor That Never Uploads Your Photos

Are You a Web Developer or a Browser Expert? 7 Tests I Failed Building a Photo Editor That Never Uploads Your Photos

Akhouri Anmol Kumar on November 01, 2026

Description: On-device AI, WASM threads, target-size compression, inpainting and 3D LUTs in a single HTML file. 7 real lessons from buildi...
Collapse
 
puneetkumar2010 profile image
Puneet Kumar Mishra •

Nice Post!!

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Fastest reading 😆 🤣

Collapse
 
puneetkumar2010 profile image
Puneet Kumar Mishra •

nope...
1st --> comment on post
2nd --> read the post

lol

Thread Thread
 
effessdev profile image
EffessDev •

Okay this thread looks good...

Thread Thread
 
puneetkumar2010 profile image
Puneet Kumar Mishra •

yea... very clean

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Lol I can't use gif I'm missing my signature meme

Collapse
 
amorizz profile image
Amorizz •

Test 2 has a storage catch worth one more line. Cache API data is best-effort by default, so under storage pressure the browser can evict the whole origin, 176 MB model included, and the instant second run quietly turns into a fresh download. Calling await navigator.storage.persist() asks for persistent storage, and navigator.storage.estimate() tells you quota and usage before you start the 208 MB object remover. Checking the estimate first beats hitting a QuotaExceededError at cache.put() after every byte already arrived. Chrome grants persist() on its own heuristics, Firefox may ask the user, so treat it as a request.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Yeah you're actually right I never thought of that it is a request

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

Got Python badge in this legendary post: dev.to/akhourianmolkumar/are-you-a...
Hope I'll get java script badge in this one... 🔥🔥🔥

Collapse
 
effessdev profile image
EffessDev •

👀

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

😂

Collapse
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

Intresting 🔥

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

🔥

Collapse
 
maxbuildsdev profile image
Max Kaya •

Test 3's binary search is the right shape, but it has a quirk worth naming: you accept a mid only when blob.size <= targetBytes, and 7 iterations over [0.05, 0.98] leaves ~0.007 quality slack, so the returned blob can sit a few KB under the cap while a higher q would have fit — on a hard 200 KB limit I'd follow the search with one final probe at the accepted q plus a size guard, since re-encoding the same canvas at the same q can differ by a few bytes across engines and the server checks exact bytes. One question on Test 1: did you keep multi-threading behind the crossOriginIsolated check on hosts that do send COOP/COEP, and if so how much of the ONNX runtime's throughput did the single-thread path give up on desktop — wondering whether pinning numThreads=1 everywhere is costing enough that a proxy worker on a header-friendly host is worth the split code path.

Collapse
 
paullovelace profile image
Paul Lovelace •

Test 1 is the one that got me — "falls back gracefully" on desktop turning into a silent hang on a phone is exactly the kind of bug you only find by testing on the actual device. The stall-based timeout in test 2 is the fix I'd steal though; a fixed 30 seconds would kill every slow connection before the model even arrives. Was the phone hang the first one you hit, or did the PNG-going-black surprise land earlier?

Collapse
 
mp3sss31 profile image
mp3sss •

Test 1 hits the exact trap I've seen in Electron: "degrades gracefully on your dev machine" is a claim, not a promise — the equivalent of assuming your renderer behaves identically once COOP/COEP headers are missing or sandbox: true changes what APIs exist. Forcing the one configuration that behaves the same everywhere (ort numThreads=1 here, explicit webPreferences there) is the right call, and checking self.crossOriginIsolated before asking for threads is the same discipline as feature-detecting before touching privileged APIs. The "7 tests" framing is great because every lesson stays falsifiable instead of being a vibe.