Skip to content

Read barcodes from a photo or screenshot, for computers without a camera - #13

Draft
Oliver-Johnson wants to merge 4 commits into
masterfrom
claude/project-thread-ia30my
Draft

Oliver-Johnson wants to merge 4 commits into
masterfrom
claude/project-thread-ia30my

Conversation

@Oliver-Johnson

@Oliver-Johnson Oliver-Johnson commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Requested by Oliver · project thread

Checks

  • I have not edited index.html or sw.js without understanding the PWA cache implications (no new cached assets; the reader is the ZXing script the camera scan already loads)
  • The version string in index.html has been bumped (Version 2026-10-07.4, matching sw.js)
  • manifest.json is still valid JSON (unchanged)
  • The app works offline after the change (offline without ZXing cached, Barcode from Image says the reader isn't loaded instead of failing)
  • CI passes (HTML validation, JSON validation, version consistency check)

What this changes

Before: the only way to scan a barcode was a live camera. On a laptop or desktop with no camera, the Scan tab showed an empty black viewfinder and a "check permissions" error, and typing the barcode number into Search found nothing.

After: the Scan tab and a recipe's Scan ingredient dialog have a Barcode from Image button. Pick a phone photo or a screenshot and the product opens exactly as if it had been scanned. On a computer you can also drag a photo onto Log Food or paste a screenshot (Ctrl/Cmd+V), from any Log Food tab. Typing the number under a barcode into Search now shows that product first, tagged Barcode. With no camera, the empty viewfinder is hidden and the status says to use Barcode from Image.

How: the image is decoded with the browser's BarcodeDetector where it works (Android, macOS and ChromeOS Chrome; only for formats it reports, and abandoned after 3 s so a broken implementation can't stall the scan), otherwise with ZXing at a few sizes (up to 3000px, so small barcodes in 4K photos still read) and at 0° and 45°. Quick passes run first; ZXing's slower TRY_HARDER mode catches sideways and blurry codes, but in testing it sometimes misread them as a different valid barcode, so its reads must agree across two passes (or be the only read) before they are looked up. Typed numbers are only looked up when the GTIN check digit is valid, so partial numbers don't trigger lookups.

Files: index.html (feature), sw.js (version), docs What's New, changelog and the barcode scanner guide (new "No camera?" chapter), a smoke test with a photo fixture, and CI installs @zxing/library@0.23.0 next to Playwright so that test can serve the reader locally (all other network stays blocked).

Testing done

  • Decoder tuned on 21 generated photos (EAN-13, UPC-A, EAN-8, Code 128, QR; tilted 12° to 140°, sideways, upside down, perspective, small barcodes in 4032×3024 photos, dim and blurry, no barcode). 19 read correctly, 0 misreads; the two that fail are very blurry and return "couldn't read". Readable photos take 0.1 to 5 s on this machine, an image with no barcode gives up after about 5 s.
  • 29 end-to-end checks in the real app (Chromium, ZXing served locally, Open Food Facts faked): file picker, paste, drag and drop with the overlay, paste of text left alone, unknown product, no barcode, reader not loaded, typed barcode and bad check digit, recipe ingredient by picker and paste, the logged entry keeps its barcode, no-camera message on desktop, no sideways overflow at 320px and Pixel 7, no page errors.
  • A native detector that never answers, answers with no formats, or throws: the photo still reads via ZXing (3 s extra in the first two cases).
  • Smoke suite: 52/52 on Chromium. On the three WebKit devices the new test first failed with "Couldn't open that image": the suite's block-external-requests route also caught the photo's blob: URL, because Playwright's WebKit routes blob URLs (it only skips data: and about:, see wkPage.js) and a blob URL has no hostname. The route now only checks http(s). Test-only; real browsers were never affected.

Notes for reviewer

Draft until you've tried it on beta: the same change is merged into beta in #14. The Log Food layout PR (#11) uses v2026-10-07.3 and also touches the Scan tab, so if this merges first, #11 needs a version bump.

🤖 Generated with Claude Code

https://claude.ai/code/session_017wFUsVAFFzEVnLmxe6TiLk

Desktops rarely have a camera, so the Scan tab and a recipe's Scan
ingredient dialog get a "Barcode from Image" button. On a computer a photo
can also be dropped onto Log Food or a screenshot pasted. The image is
decoded with the native BarcodeDetector where the browser has one, else
ZXing at a few sizes and angles; slower tryHarder reads must agree across
two passes, which stops blurry codes being misread as another product.

A barcode number typed into Search (valid GTIN check digit) is looked up
directly and shown first. With no camera, the Scan tab hides the empty
viewfinder and points to the image option instead of a permissions error.

Adds a smoke test with a photo fixture; CI installs @zxing/library so the
test can serve it locally. Version 2026-10-07.3, docs updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wFUsVAFFzEVnLmxe6TiLk
claude added 3 commits October 7, 2026 09:32
The new smoke test timed out on all three WebKit devices in CI while
passing on Chromium. WebKit can expose BarcodeDetector without a working
backend, and detect() never settling would leave the image scan stuck.
Only use the native detector for formats it reports, and give up on it
after 3 seconds and fall back to ZXing. The test now reports the scan
status if the sheet doesn't open. Version 2026-10-07.4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wFUsVAFFzEVnLmxe6TiLk
With the status now reported, the WebKit failure reads "Couldn't open
that image": Playwright's WebKit routes blob: URLs (it only skips data:
and about:), and a blob URL has no hostname, so the block-everything-
external route aborted the photo's object URL. Only http(s) requests are
checked now. Real browsers were never affected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wFUsVAFFzEVnLmxe6TiLk
Log Food opens on Search on desktop once the desktop pages land (#11/#12),
which hides the Barcode from Image button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wFUsVAFFzEVnLmxe6TiLk

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants