Skip to content

Bank Transactions stats cache key ignores the date range, so tiles disagree with the table

Why (Accounting (Diagnostics), 2026-06-29)

Found while reviewing the Codex commit batch (owner asked: "look thru the GitHub commits … fix possible bugs that were over looked"). T-131 made the Bank Transactions table follow the Accounting page's global Period (or the local date picker), passing startDate / endDate into both the list and the stats fetch. The stats summary tiles could then show numbers for a different period than the table beneath them.

Diagnosis

getTransactionStats({ startDate, endDate, subsidiaryId }) (lib/accounting/transactions.ts) filters the transaction set by the date range via listTransactions(options), but built its cache key with CacheKeys.transactionStats(options?.subsidiaryId) β€” date range omitted.

So for one subsidiary, the first range computed in a 60s window wins, and every other range gets that cached result until the TTL expires. Switching the Period dropdown (or the local date filter) updated the table but could leave the tiles on the previous range's totals.

What shipped

  • lib/cache/index.ts β€” CacheKeys.transactionStats now takes optional startKey / endKey and appends them after the tx-stats:<sub> prefix: tx-stats:<sub>:<start>:<end>. The date segment is only added when present, so the all-time key stays tx-stats:<sub>.
  • lib/accounting/transactions.ts β€” getTransactionStats passes the range (startDate.toISOString() / endDate.toISOString()) into the key.

The 20+ invalidateCache(CacheKeys.transactionStats(sub)) / invalidateCache(CacheKeys.transactionStats()) mutation hooks are unchanged: invalidateCache matches by substring (key.includes(pattern)), and the range-less prefix tx-stats:<sub> is a prefix of every date variant, so a mutation still purges all of that subsidiary's cached ranges.

Verification

  • βœ… Attestation (Accounting (Diagnostics)): read AGENTS.md; checked the board by scope, not UID β€” T-131 shipped the period-follow behavior but no task covered the stats-cache key collision it exposed. Tracking T-138.
  • Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Unit test added asserting different ranges produce different keys and that the range-less invalidation prefix is a substring of every date variant.
  • NODE_OPTIONS=--max-old-space-size=4096 npx tsc --noEmit clean; npx vitest run green.

Blast Radius

Stats-cache keying only. The compute path, the API contract, and all invalidation call sites are unchanged; the fix narrows cache identity so tiles and table always reflect the same date scope.

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.

  • (no git-discoverable commit references this UID or its PRs β€” append real SHAs here as identified.)