Fleet changelogs · dev.ecs0.net
rdmsm4x-changelog-20260830-1255-rdreceipt-date-range-csv-xlsx-export

rdmsm4x-changelog-20260830-1255-rdreceipt-date-range-csv-xlsx-export

Built RDReceipt's date-range CSV/XLSX expense export (STATUS.md item 4), the first feature toward turning a date range into a Concur-ready file.

Scope

One project, one repo. No fleet-wide, system, or configuration changes. No packages installed. No secrets read or written. Not pushed — the worktree is intentionally on a detached HEAD.

Files added

File Purpose
Packages/rdRECEIPT/Sources/rdRECEIPTCore/Models/ExportDateRange.swift Date range with the inclusive-end rule; range → VaultFilter; YYYY-MM-DD parsing
Packages/rdRECEIPT/Sources/rdRECEIPTUI/Export/XLSXExportGenerator.swift Real .xlsx workbook, typed date/number cells, 4 layouts, totals row
Packages/rdRECEIPT/Sources/rdRECEIPTUI/Export/ZipArchiveWriter.swift Minimal stored-method ZIP container + CRC-32, so no dependency is needed
Packages/rdRECEIPT/Tests/rdRECEIPTCoreTests/ExportDateRangeTests.swift 12 tests
Packages/rdRECEIPT/Tests/rdRECEIPTUITests/XLSXExportGeneratorTests.swift 17 tests, incl. an independent ZIP reader

Files modified

Verification evidence

Strongest single piece of evidence: a single-day export for 2026-08-30 returned its 3 receipts, whose timestamps are 16:43–16:51. A naive midnight end-bound would have returned zero — this is the silent wrong-report failure the whole ExportDateRange type exists to prevent, proven against live SQL rather than only in unit tests.

Corrections made to previously recorded facts

The per-target test-count split was wrong in three files. CLAUDE.md, STATUS.md and SESSION-STATE.md all published Core 51 · OCR 66 · Parser 54 · Vault 55 · E2E 248 · UI 31. The total (505) was always correct, but the per-target attribution was read off swift test's six summary lines in printed order — which is completion order, not target order, because the targets run in parallel. True baseline: Core 31 · OCR 55 · Parser 54 · Vault 51 · E2E 248 · UI 66. Corrected by six swift test --filter <target> runs cross-checked against @Test declaration counts per directory. It survived a week because the total reconciled.

Data handling

Test exports were written to a mktemp -d directory with mode 700 (not a world-readable temp path) because they derived from the encrypted vault, and were deleted afterwards along with the sample workbook/CSV. Only aggregate counts and times-of-day were printed — no merchant names, amounts, or notes. .gitignore re-checked: /data/real-corpus/ and *measure*.json still excluded.

Outstanding owner actions

  1. The commit sits on a detached HEAD and is on no branch. It is reachable via aa942a2 and the reflog, but nothing points at it. Create a branch or tag if it should be kept.
  2. Concur field recon is still blocked at sign-in (three Chrome browsers connected, none selected). The export's column schema is generic until that lands — a deliberate, cheap-to-change choice, not a defect.
  3. CSV money formatting drops trailing zeros (412.00 → 412). Left alone deliberately: it is pre-existing across all four layouts and the legacyOracle layout is tied to E2E oracle validation, where core/CONTRACT.md says the fixture wins until a human rules. Fix with the Concur reshape and re-run the fixtures.

How to undo

git -C ~/dev/apps/RDReceipt/.claude/worktrees/rdreceipt-chat-thread-setup-a94cd2 reset --hard aa0875c restores the pre-session state. The five added files are untracked by that commit and would need removing separately. No state outside the repo was changed.