fix(reports): make a drill-down explain the figure that opened it - #150
Merged
Conversation
Clicking a spending row opened a sheet whose total disagreed with the row,
for three separate reasons.
The sheet added up the page of rows it had been handed. That page is capped
at 50, so any category with more transactions than that displayed a total
short of the row above it, with no indication anything was missing. The
total and the match count now come from the server, computed over the whole
matching population; the page is still 50 rows, and now says so honestly
("Showing the most recent 50 of 120").
The query behind the sheet filtered on category and dates alone, so a list
meant to explain a spending figure also contained the transfers, pending
rows and refunds that figure had deliberately excluded. It now builds on
the same `spendingBaseConditions` every Reports tab agrees on, or on its
income counterpart when an income node was clicked — both exported from
`spending-helpers` rather than restated here.
Third, the sheet took its date range from the URL, where the page took its
own from the resolved preset. On a bare `/reports` the URL carries no dates
at all, so the tab showed three months while the drill-down queried
all-time: clicking a $1,891.26 Groceries row opened a sheet reading
$3,746.58. The resolved range and account filter are now passed down from
the page, so the two can no longer diverge.
`getTransactions` keeps its filter-shaped API; the page fetch underneath it
is extracted as `fetchTransactionPage` so the report query can supply its
own conditions without duplicating the row enrichment.
Verified against demo data: Groceries reads $1,891.26 / 24 transactions on
both the row and the sheet, and the Cash Flow Salary node reads $23,100.00
/ 6 on both.
Closes #144
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #144.
Clicking a spending row opened a sheet whose total disagreed with the row, for three separate reasons.
1. The sheet summed only the page it had
drill-down-sheet.tsxadded uprows, which is capped atDRILL_DOWN_LIMIT = 50. Any category with more transactions than that showed a total short of the row above it, silently.getDrillDownTransactionsnow returnstotalandmatchCountcomputed over the whole matching population. The page is still 50 rows and now says so: "Showing the most recent 50 of 120".2. The list was a different population from the figure
The query passed category + dates only, so a list explaining a spending number also contained transfers, pending rows and refunds — exactly the rows that figure excludes.
It now builds on
spendingBaseConditions(the definition #142 made canonical), orincomeBaseConditionswhen an income node was clicked. Both are exported fromspending-helpersrather than restated at the call site. The income rule matches the Sankey's income nodes and the Income vs Expense table — deliberately not the Total Income tile, whose extra uncategorized credits are the open gross-vs-net question in #146.3. The sheet and the page disagreed about the date range
The page resolves its range from the preset; the sheet read
useSearchParamFilters().dateRange, which falls back to2000-01-01→today. On a bare/reportsthe URL carries no dates at all, so the tab showed three months while the drill-down queried all time — a $1,891.26 Groceries row opening a $3,746.58 sheet.The resolved
dateFrom/dateTo/accountIdsare now passed down from the page throughReportTabsto each tab, so the two can no longer diverge. This also fixes the account filter, which the drill-down previously ignored entirely.Refactor
getTransactionskeeps its filter-shaped API. The page fetch underneath is extracted asfetchTransactionPage(householdId, conditions, …)so the report query can supply its own conditions without duplicating the split/row enrichment.Tests
New
tests/integration/report-drill-down.test.ts(6 tests), each pinning the drill-down against the tab figure that opens it:Verification
Full suite: 1061 passed, 1 failed —
investment-queries.test.ts > returns dayChange from holdings_history, which is pre-existing hardcoded-date rot on cleanmain.Checked live against demo data: Groceries reads $1,891.26 / 24 transactions on both the row and the sheet; Cash Flow's Salary node reads $23,100.00 / 6 on both.
🤖 Generated with Claude Code