Skip to content

fix(reports): stop the Spending tab crowning its own data-quality gaps - #154

Merged
KenTaniguchi-R merged 1 commit into
mainfrom
fix/147-information-design
Sep 2, 2026
Merged

fix(reports): stop the Spending tab crowning its own data-quality gaps#154
KenTaniguchi-R merged 1 commit into
mainfrom
fix/147-information-design

Conversation

@KenTaniguchi-R

Copy link
Copy Markdown
Owner

The information design third of #147, built to your Spending tab mock. Completes the issue.

Stacked on #153#152#151#150.

The summary bar celebrated the wrong things

Total Spent · Categories · Top: X. "Categories: 18" is a number no decision turns on, and topCategory = data[0] took the largest row blindly — so whenever Uncategorized was largest, which is the usual case, the page put a crown on the spending it had failed to identify.

Three tiles replace it, per the mock:

Total spent · Jun 2 – Sep 2 the figure, split into ● Categorized / ● Uncategorized
Compared with Mar 2 – Jun 2 · the preceding period, same length
Share of income 73.8% of $23,100.00 received

The split states the unaccounted-for share instead of decorating it.

Uncategorized was the loudest colour on the page

It drew CHART_COLORS[0], the most saturated blue. It takes --chart-neutral now — the treatment the synthetic "Other" slice already had — in the chart and in the table's category tile. Uncategorized is not a category, it's the absence of one, and shouldn't look like the hero.

Bar is the default view. Nine categories spanning three orders of magnitude is a size comparison; bars read it directly.

A blank Change cell meant two different things

prevTotal defaulted to 0 for a category with no baseline row, and ComparisonBadge rendered nothing for zero — so "this category is new" and "this category didn't move" were the same empty cell.

prevTotal is number | null now (null when there's no baseline row, and null throughout when no comparison period was requested), and the badge renders New. The MCP tool reports null rather than 0 for the same reason: a consumer shouldn't be able to compute a change against a period the category wasn't in.

The resolved range appeared nowhere

The chip said "Last 3 months" and the actual dates were on no part of the page. It prints them: Date: Last 3 months Jun 2 – Sep 2.

Also added a % of total column, so a row's share is readable without dividing by the headline.

Tests

  • src/lib/comparison-state.test.ts — 7 tests: a null baseline is new, a zero baseline is new, up / down / flat, the half-percent boundary, and a category that spent nothing against a real baseline (−100%, not "new").
  • tests/integration/report-drill-down.test.ts — 2 added: a category absent from the baseline reports null while one present reports its figure; with no comparison period nothing claims a previous figure.

Full suite: 1095 passed, 1 failed — the pre-existing investment-queries date rot.

Verification

Inserted a temporary uncategorized charge and a charge in a category with no baseline, then read the live page:

  • Uncategorized — $3,200.00 · 18.8% · New, bar fill var(--chart-neutral)
  • Property Tax — $450.00 · 2.6% · New
  • Headline — $17,055.33, split $13,855.33 categorized / $3,200.00 uncategorized, 73.8% of $23,100.00

Rows removed afterwards.

Not built

The mock also proposes a "Data issues in this range" panel (uncategorized count with a link to categorize, categories that net positive and so vanish from spending, transfers miscategorized as spending). That's a new feature rather than information design and sits outside what #147 lists — worth its own issue if you want it.

🤖 Generated with Claude Code

@KenTaniguchi-R
KenTaniguchi-R changed the base branch from fix/147-charts to main September 2, 2026 13:06
The information-design third of #147, built to the Spending tab mock.

The summary bar read Total Spent · Categories · Top: X. "Categories: 18" is
a number no decision turns on, and the crown went to `data[0]` blindly — so
whenever Uncategorized was the largest line, which is the usual case, the
page put a trophy on the spending it had failed to identify.

Three tiles replace it. The headline names the range it measured ("Total
spent · Jun 2 – Sep 2") and splits the figure into categorized and
uncategorized, so the share that is unaccounted for is stated rather than
decorated. The second says what the comparison is against and that it is the
same length. The third gives spending as a share of income received, which
is a number a decision does turn on.

Uncategorized also drew `CHART_COLORS[0]`, the most saturated blue, making
"we do not know" the visual hero of the chart. It takes `--chart-neutral`
now, the treatment the synthetic "Other" slice already had, in both the
chart and the table's category tile. Uncategorized is not a category — it is
the absence of one — and should not look like the loudest.

Bar becomes the default view. Nine categories spanning three orders of
magnitude is a size comparison, which bars read directly.

A blank cell in the Change column meant two different things. `prevTotal`
defaulted to `0` for a category with no baseline row, and `ComparisonBadge`
rendered nothing for zero — so "this category is new" and "this category did
not move" were the same empty cell. `prevTotal` is `number | null` now, null
meaning no baseline row (and null throughout when no comparison period was
requested at all), and the badge renders "New". The MCP tool reports null
rather than 0 for the same reason: a consumer should not be able to compute
a change against a period the category was not in.

The date chip said "Last 3 months" and the resolved dates appeared nowhere
on the page. It prints them.

Also added: a % of total column, so a row's share is readable without
dividing by the headline.

Verified against demo data with a temporary uncategorized charge and a
category with no baseline: Uncategorized reads $3,200.00 · 18.8% · New with
a neutral bar, Property Tax reads $450.00 · 2.6% · New, and the headline
splits $17,055.33 into $13,855.33 categorized and $3,200.00 uncategorized at
73.8% of $23,100.00 received. Rows removed afterwards.

Refs #147
@KenTaniguchi-R
KenTaniguchi-R force-pushed the fix/147-information-design branch from 09a4c25 to 61c7fa0 Compare September 2, 2026 13:06
@KenTaniguchi-R
KenTaniguchi-R merged commit f6df8bf into main Sep 2, 2026
4 of 5 checks passed
@KenTaniguchi-R
KenTaniguchi-R deleted the fix/147-information-design branch September 2, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant