Records — Service-Invoice ZIP silently drops rows with no file
Why (owner, 2026-06-25)¶
"When I highlight all 71 service invoices, only 5 was included in the zip"
The Service Invoices sub-tab's bulk-download (right-click → "Download N
as ZIP" or the toolbar Download button) silently filtered out every
selected row whose fileUrl was null. For service-invoice rows that's
common — GCP / Workspace billing rows can exist in the feed BEFORE the
NAS scrapes the matching PDF to Drive. The user selected 71 rows and
got a 5-file ZIP back with no explanation.
What shipped¶
components/records/ExpenseRecordsTab.tsx
triggerSelectionDownloadfor theservice-invoicesub-kind now splits the selection into eligible (hasfileUrl) and skipped (no file filed yet) up front.- If ALL selected rows have no file: a clear
message.errorreports the count and aborts (no empty ZIP, no progress toast). - If SOME selected rows have no file: a
Modal.confirmexplains why (typically GCP/Workspace months whose invoice hasn't been scraped yet) AND shows the eligible count on the confirm button (Download N). User must explicitly accept the truncated set. - If every selected row has a file: behaviour is unchanged — straight
to
runBatchDownload.
The pre-existing per-row progress toast + 429 retry semantics are untouched.
Why this and not "just include all of them"¶
The PDF stream endpoint /api/accounting/receipts/<id>?redirect=pdf
returns 404 for rows without a storagePath, so a 71-attempt batch
would emit 66 failure entries into the toast — noisy, AND the user
would still get a 5-file ZIP. Surfacing the discrepancy in a single
modal up-front is clearer, costs no network traffic, and lets the user
cancel if it's not what they expected.
Decision log¶
2026-06-25 — T-122 opened + shipped¶
- ✅ Attestation (Records (Infrastructure)): read
AGENTS.md; no open task covered the silent-filter case (T-117 added the feature, but didn't surface the partial-eligibility UX). Sequel to T-117. - Source: Records (Infrastructure) · https://claude.ai/code/session_018RDB37kCqfouHdygVXTAtD
- Owner direction (verbatim, 2026-06-25): quoted above.
- Tests + tsc:
npx tsc --noEmitclean. Live render verification left to the owner — open the Records page → Receipts tab → Service Inv sub-tab → select rows that span both filed + unfiled service invoices → trigger Download. Expect a confirm modal with the eligible count when a subset has no file, an error when none do, and silent download when all do. - Blast radius: single component change, behind a per-selection branch; no shared helper / API touched. Receipts sub-kind branch unchanged.
2026-06-28 — follow-up: filed GCS-backed service invoices still failed inside the browser ZIP¶
- ✅ Attestation (Codex local session): read
AGENTS.md; checked the board by scope, not UID — this is the same "selected 71, ZIP only contains 5" Service Invoice ZIP scope, not a new task. Tracking T-122. - Source: Codex local session ·
/Users/gutchumi/dev/ArtifactoftheEstablisher-codex-wopc-pdfjs - Owner report (verbatim, 2026-06-28):
"I have also mentioned previously that I could only download 5 service invoices when I've actually selected 70 on the Records page > Service Invoices tab"
- Correction to the earlier diagnosis: the first fix only handled
rows with no uploaded file. The Chrome log showed a second failure:
filed legacy-GCS service invoices were fetched by the browser ZIP
worker through
/api/accounting/receipts/<id>?redirect=pdf; that endpoint redirected to a signedstorage.googleapis.comURL, and the browser then blocked it with CORS. Drive-backed rows streamed same-origin and worked, which is why only a small subset entered the ZIP. - What changed:
pages/api/accounting/receipts/[id].tsnow supports?stream=pdf, which streams both Drive and GCS bytes through the app origin.components/records/ExpenseRecordsTab.tsxuses that streaming URL for Service Invoice ZIP items, while keeping the old redirect path available for normal iframe/open-file usage. - Verification:
git diff --checkclean;NODE_OPTIONS=--max-old-space-size=4096 npx tsc --noEmitclean. - Blast radius: Records → Receipts → Service Invoice bulk ZIP only,
plus the receipt API's new same-origin streaming branch. Existing
?redirect=pdflinks remain supported.
2026-06-29 — follow-up: GCP rows used billing month instead of File Archive document id¶
- ✅ Attestation (Codex local session): read
AGENTS.md; checked the board by scope, not UID — this remains the same Records → Service Invoices bulk-ZIP symptom, not a new task. Tracking T-122. - Source: Codex local session ·
/Users/gutchumi/dev/ArtifactoftheEstablisher-codex-wopc-pdfjs - Owner handoff (verbatim, 2026-06-29):
"I had Claude to tackle the below issue, but I'm out of token with them. Can you possibly pick it up?"
- Correction to the second fix: switching the generated URL to
?stream=pdffixed CORS for valid document ids, but the Service Invoice ZIP still built that URL fromr.recordId. For GCP rows,recordIdis the billing month (for example202606), while the filed PDF lives under a separate File Archive document id already embedded inr.fileUrl. - What changed: ZIP item URLs now use
r.fileUrlas the source of truth. Same-origin/api/accounting/receipts/<docId>?redirect=pdflinks are rewritten to?stream=pdffor browser ZIP fetches; raw signed URLs are left untouched for legacy rows. - Verification:
git diff --checkclean;NODE_OPTIONS=--max-old-space-size=4096 npx tsc --noEmitclean. - Blast radius: Records → Receipts → Service Invoices bulk ZIP only.
The row open/preview links still use the original
fileUrl.
Commit index (backfilled 2026-07-01, best-effort · Coaching (Diagnostic))¶
Candidate related commits, auto-backfilled from git on main: commits whose message references this task's UID or a PR number it cites. Not verified — this squash-merged history can't yield a precise per-task list, so rows tagged (mentions only) name the task in passing (may be tangential) and untagged work commits may be missing. Treat as a starting point: verify, prune tangential rows, and append any real ones per the AGENTS.md "record every related SHA" policy.
bc1af242026-06-25 — fix(records): service-invoice ZIP UX (T-122) + share viewer WOPC/coaching (T-123)9be22b92026-06-25 — Merge: T-122 service-invoice ZIP UX + T-123 share viewer WOPC/coaching fixes99f9c362026-06-25 — feat(records,wpp): unified Bank-Tx letterhead (T-124) + WPP CSV → XLSX (T-125) (mentions only)a98745b2026-06-25 — Merge main + renumber T-122 → T-126 (renumber-on-merge, parallel-agent T-122/123/124/125 collision)