Add Tuš klub weekly coupons to the card view #1

Merged
maks merged 5 commits from worktree-tus-klub-coupons into master 2026-04-25 11:52:53 +02:00
Owner

Summary

  • New /api/coupons/{retailer} endpoint: scrapes the Tuš klub HTML catalog, pairs front/back images with each barcode, caches parsed metadata + rendered images server-side, refreshed
    lazily with a 6h TTL.
  • New CouponStrip component on the Tuš card view: horizontally-scrolling thumbnail strip; tap a thumbnail to open a full-screen viewer that flips between front and back; T&C text shown
    beneath the image when present.
  • Offline checkout works — image blobs are stored in a new IDB coupon_images object store (DB v4) and clearAll() clears it on logout. Stale-while-revalidate via
    couponsCache.svelte.js paints from cache instantly, then refreshes from the server.
  • In-flight dedup + a destroyed flag in the loader prevent the stale-then-fresh emit from racing duplicate fetches or leaking object URLs after unmount.
  • i18n strings added for SL + EN.

Test plan

  • Online: open a Tuš klub card → strip renders thumbnails; tap opens viewer; tap viewer image flips front/back; close button dismisses.
  • Offline: same card after first online load and full reload (devtools → Offline) → strip and viewer still render from IDB.
  • Cold profile (clear IDB → reopen) → first load hits the network, second open is instant.
  • Race check: open the card with cache primed → only one network fetch per (hash, side) regardless of stale+fresh loadCoupons emits.
  • pytest backend/tests/test_coupons.py (12 tests) passes.
  • cd frontend && npm run test — 115 tests pass, including couponsCache.test.js.
## Summary - New `/api/coupons/{retailer}` endpoint: scrapes the Tuš klub HTML catalog, pairs front/back images with each barcode, caches parsed metadata + rendered images server-side, refreshed lazily with a 6h TTL. - New `CouponStrip` component on the Tuš card view: horizontally-scrolling thumbnail strip; tap a thumbnail to open a full-screen viewer that flips between front and back; T&C text shown beneath the image when present. - Offline checkout works — image blobs are stored in a new IDB `coupon_images` object store (DB v4) and `clearAll()` clears it on logout. Stale-while-revalidate via `couponsCache.svelte.js` paints from cache instantly, then refreshes from the server. - In-flight dedup + a `destroyed` flag in the loader prevent the stale-then-fresh emit from racing duplicate fetches or leaking object URLs after unmount. - i18n strings added for SL + EN. ## Test plan - [ ] Online: open a Tuš klub card → strip renders thumbnails; tap opens viewer; tap viewer image flips front/back; close button dismisses. - [ ] Offline: same card after first online load and full reload (devtools → Offline) → strip and viewer still render from IDB. - [ ] Cold profile (clear IDB → reopen) → first load hits the network, second open is instant. - [ ] Race check: open the card with cache primed → only one network fetch per `(hash, side)` regardless of stale+fresh `loadCoupons` emits. - [ ] `pytest backend/tests/test_coupons.py` (12 tests) passes. - [ ] `cd frontend && npm run test` — 115 tests pass, including `couponsCache.test.js`.
The weekly coupon list at tus.si/katalog/tedenski-kuponi/ is public (no
auth, no per-coupon dates in the HTML — anything on the page is currently
valid), so this is a separate subsystem from RetailerIntegration rather
than a new entry in its registry: no credentials, no linking flow, no
activation step. The backend scrapes the katalog on a 6h TTL, mirrors to
/data/coupons/tus_klub.json for restart survival, and proxies the coupon
images so we don't leak checkouts to api.tus.si and so the service worker
can cache them for offline checkout. The frontend shows the current
coupons as a scrollable thumbnail strip under the barcode on any card
with preset_key=tus_klub; tapping a thumbnail opens a full-screen viewer
with front/back flip and the T&C text. Stale cache wins over a hard
error so the strip stays usable even if tus.si is unreachable, and the
strip never blocks the barcode render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cache Tuš coupon images in IDB for offline checkout
Some checks failed
CI / backend (pull_request) Successful in 1m50s
CI / frontend (pull_request) Failing after 36s
CI / pre-commit (pull_request) Failing after 1m15s
12d0ac8ee2
The viewer and thumbnail strip now fetch image blobs through apiBlob
and store them in a new `coupon_images` store, so the strip still
renders when offline at the till. Includes in-flight dedup so the
stale-then-fresh emit from loadCoupons doesn't double-fetch, and a
destroyed flag that revokes object URLs created after unmount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Image proxy now returns Cache-Control: private (response is auth-gated)
- Stream-bound the upstream fetch so _IMAGE_MAX_BYTES actually caps memory
- Wire coupons_cache_dir through Settings instead of hardcoding /data/coupons
- Keep the stale list when a fresh fetch returns 0 entries (likely scraper regression)
- Promote _IMAGE_PATH_RE to a public IMAGE_URL_PATTERN; drop unused CouponOut.from_attributes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address review on Tuš coupon frontend
Some checks failed
CI / backend (pull_request) Successful in 2m25s
CI / frontend (pull_request) Successful in 2m55s
CI / pre-commit (pull_request) Failing after 2m56s
d9808e45da
- CouponStrip viewer closes on Escape
- After a successful network refresh, prune coupon_images entries whose hash is no longer current

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Satisfy pre-commit on Tuš coupon fixture
All checks were successful
CI / backend (pull_request) Successful in 1m49s
CI / frontend (pull_request) Successful in 1m5s
CI / pre-commit (pull_request) Successful in 1m30s
223810f11d
Trim trailing whitespace + normalise line endings on the captured Tuš
katalog fixture, and refresh the secrets baseline (i18n line numbers
shifted; the fixture's minified inline JS plus the hex test coupon hashes
are recorded as known false positives).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maks merged commit f6236f07f6 into master 2026-04-25 11:52:53 +02: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
maks/beeparr!1
No description provided.