Skip to content

I-022 — Receipt previewer: fit to window height, vary width by ratio, scroll long receipts

Symptom

The "Click Here" links in the exported Register of Expense Receipts XLSX open the no-login viewer (/v/<shortId>, kind: 'receipt'). The receipt image (Processed and Original) was shown in a fixed A4-portrait iframe, so larger photos — or any photo whose ratio isn't A4 — were cropped into a tiny scroll box, forcing the viewer to scroll around inside it to read the receipt.

Root cause

Receipts are served as image/jpeg bytes by /api/v/[id]/pdf, but /v/[id] rendered them with the same SharedWopcView it uses for WOPC / archive PDFs — an <iframe> hard-locked to aspectRatio: 210 / 297 (A4). That's right for PDFs, wrong for receipt photos of arbitrary size and ratio.

Fix (commit below)

  • components/share/SharedDocumentView.tsxSharedWopcView gains a media?: 'pdf' | 'image' prop. 'image' renders an <img> sized from the image's natural ratio, measured client-side on load + resize:
  • Default — fit to the viewport HEIGHT, width following the natural ratio (max-height: calc(100vh − header − padding), width:auto, object-fit:contain). Tall → narrow column; wide → short banner; small → natural size. No scrolling.
  • Long/narrow receipts — once fitting to height would shrink the width below a readable floor (MIN_READABLE_IMG_WIDTH_PX = 400), the width is pinned at that floor and the image is allowed to run past the bottom of the screen, scrolling vertically (width:400; height:auto; stage overflow-y:auto). The header is position:sticky, so it stays put while the receipt scrolls.
  • PDFs keep the A4 iframe (media:'pdf', the default).
  • pages/v/[id].tsx — pass media="image" for kind === 'receipt'.

Owner's framing (verbatim): "if an image hits the minimum width to accommodate for the height, then we increase height for scrolling instead."

Verification

Rendered the exact image branch (72px header + 16px stage padding + the new client-measured <img> logic) in headless Chromium at 1440×900: - Normal 1000×1400 → FIT-HEIGHT, img 505×707, no scroll. - Wide 2100×680 → FIT-HEIGHT banner, width-capped, no scroll. - Long 560×2600 → SCROLL mode, img pinned 398×1848 (readable width), the stage/page scrolls to reveal the rest; sticky header stays.

Project-pinned tsc — clean. Real-data confirmation is owner-on-deploy (re-export the Receipt register, click a Processed/Original "Click Here").

Decision log

2026-06-30 — ✅ Read AGENTS.md before working this ticket. Source: Records (Infrastructure) · https://claude.ai/code/session_018RDB37kCqfouHdygVXTAtD

Owner reported the receipt previewer was "fixated" while large images needed major scrolling; asked for a height-fit previewer whose width varies with image ratio. Scoped to kind: 'receipt' (the reported case); archive-doc PDFs keep the A4 iframe. Ticket-only (UI fix; no structural T-task).