Files
finance-app/docs/ui-information-architecture-review.md
siddharthd a4ab543a6c
ci / lint-test (push) Failing after 44s
fix(analytics): make the displayed numbers mean what they say
Six metric-integrity defects from the UI/IA review, plus two found while
verifying the review's own claims against the code.

The reconciled-row exclusion existed only in queries.ts. Every analytics
route counted the superseded manual rows as spend — 48 rows, $4,474.79 of
double count, invisible precisely because the transaction list looked
right. It is now one fragment both sides import.

The spend-pace chart computed its own totals in the browser: gross
amounts, debits only, no personal share, no refunds, fees, interest or
itemised loan repayments. On live data it ended July at $4,747.31 under a
headline reading $3,597.10 — and its own baseline line was drawn from the
split-adjusted monthly totals, so the two series in one chart disagreed
with each other. Both now come from /api/analytics/daily, built from the
same fragments as the headline.

Fees aggregated every statement ever imported with no date filter, under
a heading with no period, so a lifetime figure read as a current one and
grew forever. Now bounded, labelled, and selectable.

Comparisons no longer measure a month in progress against complete ones:
the in-progress month is out of every baseline, and a selected current
month is compared through the same day.

Two the review did not catch:

- Every analytics window was a day early. toISOString() on a
  local-midnight Date converts backwards through UTC. Surfaced only once
  fees started reporting the range it had used.
- /monthly rounded per category, /daily per category-day, so the pace
  chart ended a few cents off the headline above it.

Shared currency needed amending rather than applying. Reading s.currency
would have labelled every order row AUD, since an order receipt has no
statement and carries its own currency — the opposite convention from a
foreign charge on an AUD statement, where amount IS AUD. NATIVE_CURRENCY's
COALESCE order keeps the two apart. Balances also now count rows whose AUD
value is genuinely unknown instead of netting a foreign figure against AUD
ones. Latent today: no foreign transaction is currently split.

Tag-filtered balance cards no longer claim "owes you". With a filter on,
payments are deliberately not subtracted, so the figure is a split total
and settling against it would record a payment for a debt that never was.

Split-coverage warnings deliberately omitted (user decision).
2026-07-27 17:10:27 +10:00

23 KiB
Raw Permalink Blame History

UI and information architecture review

Date: 2026-07-26
Status: Priority 0 implemented 2026-07-27 (see below). Priorities 14 remain proposals.

Implementation status — Priority 0 (2026-07-27)

All six Priority 0 items landed, with three amendments found while verifying the proposals against the code:

  1. Reconciled source rows — the exclusion was missing from all five analytics routes, not only /monthly. It is now one fragment (EXCLUDE_RECONCILED_SOURCE) that queries.ts also imports, so the two halves cannot drift apart again. Real effect: 48 rows, $4,474.79 of double-counted spend removed from every category total, mover, Pareto and merchant ranking.
  2. Spend pace — now served by /api/analytics/daily, built from the same fragments as the headline. Measured on live data, the old client-side series ended July at $4,747.31 against a headline of $3,597.10 — a 32% overstatement of the number directly above it.
  3. Fees and interest — bounded by an explicit period (default 12 months, months=0 for all time), with the range shown and selectable. The unbounded figure was overstating the last 12 months by roughly $2,700 of fees.
  4. Split-coverage warningdeliberately not implemented (user decision, 2026-07-27).
  5. Shared foreign currency — amended. The obvious fix, reading s.currency, would have mislabelled every order row as AUD, because an order receipt has no statement and carries its own currency. Sourcing is now NATIVE_CURRENCY = COALESCE(s.currency, t.foreign_currency_code, 'AUD'), whose COALESCE order keeps two opposite denomination conventions apart. Note this change is latent on today's data: no foreign transaction is currently split, so nothing on Shared looks different yet.
  6. Partial-month comparisons — the hero average, the top movers and the pace baseline now exclude the in-progress month, and compare through the same day of the month when the selected month is the current one.

Also fixed while in here, both found by checking rather than by proposal:

  • Every analytics window was a day early. toISOString() on a local-midnight Date converts backwards through UTC in any timezone east of Greenwich. Now toDateStr(). This was pre-existing in /monthly and /merchants.
  • Rounding grain. /monthly rounded per category and /daily per category-day, so the pace chart ended the month a few cents off its own headline. Both now carry 4dp and round once, at display.

Guarded by src/__tests__/integration/analytics-sql.test.ts.

The doc's characterisation of REGULAR_CATEGORIES (Insights section) is also slightly off: the set has 13 members including rent, utilities, insurance and subscriptions, not the 8 listed. The case for replacing it stands — a flat binary cannot express obligation — but that is the reason, not arbitrary membership. Note too that the proposed Fixed/Essential/Lifestyle model needs a commitment dimension that does not exist yet: fees cannot be split into avoidable versus known-annual, subscriptions cannot be split into contractual versus cancellable, and the contracted loan repayment is not in the spend stream at all (SPEND_BASE keeps only the interest portion). That is a data-model change, not an Insights rework.

Executive summary

The July 19 UI refresh gave the app a cohesive and distinctive visual identity. The ink-and-copper palette, typography, financial number treatment, month spine, and transaction drill-downs are all strong foundations.

The larger remaining issue is not appearance. It is information hierarchy. Analytics and Insights contain useful data, but they are reporting-heavy rather than decision-oriented. Shared communicates the immediate running balance, but the current settlement model prevents it from answering which expenses a payment settled, whether a trip is closed, or how the shared loan should be represented.

The product should make four questions easy to answer:

  1. Am I financially okay?
  2. What changed and why?
  3. What needs my attention?
  4. Who owes what, and for which expenses?

Today there is no single page that answers the first three. The app opens on Transactions and presents ten equally weighted navigation items.

The recommended direction is:

  • Add an Overview as the default landing page.
  • Keep Analytics focused on historical exploration: what happened?
  • Rebuild Insights around decisions and attention: what should I know or do?
  • Rebuild Shared around settlement contexts: who owes what, and why?
  • Keep the shared loan as a separate ledger from shared consumption expenses.
  • Fix calculation and coverage inconsistencies before adding more visualisations.

Context reviewed

This review covered:

  • The current Next.js pages and shared components.
  • Analytics SQL and API calculations.
  • Shared-expense balance and transaction queries.
  • CLAUDE.md.
  • docs/shared-expenses-design.md.
  • docs/expense-baseline.md.
  • Recent repository history.
  • Recent finance-app memories retrieved from OpenViking.

The OpenViking history confirmed:

  • The July 19 redesign intentionally introduced the ink-and-copper theme, Fraunces display type, month-spine navigation, top movers, category sparklines, and heat-tinted ledger tables.
  • The user prefers a modern, high-fidelity interface and actionable analytics.
  • Later July 2526 work changed the financial meaning under those screens: split-aware personal spend, AUD-aware settlement, refund netting, loan principal/interest separation, rule previews, and the proposed contextual settlement model.
  • The preferred settlement model links payments to real transactions, separates Household, Trip, and Historical contexts, and keeps the shared loan separate.

What already works

Visual system

  • The dark ink-and-copper theme is coherent and distinctive.
  • Serif headings and mono financial figures create useful hierarchy.
  • The copper accent is used consistently for selection and emphasis.
  • The design feels like one application rather than a collection of unrelated pages.

Analytics interactions

  • The month spine is an effective year-at-a-glance navigation control.
  • “What changed” is more useful than a generic category chart.
  • Category sparklines make direction visible without creating a large multi-series chart.
  • Category rows can be expanded into their transactions.
  • Inline recategorisation allows users to correct the data while investigating it.

Shared workflow

  • “Owes you,” “you owe,” and “all square” communicate the immediate relationship balance clearly.
  • Payment history is preserved rather than reducing settlement to a boolean.
  • Participant and tag filters support practical investigation.
  • Split transactions can be edited without returning to the main transaction page.

App-wide information architecture

Current problem

The app redirects / to /transactions. This makes the operational ledger the default product surface. Transactions are important, but they do not tell the user whether anything needs attention or what the current financial position means.

The sidebar also gives equal weight to:

  • operational screens such as Reconcile;
  • analytical screens such as Analytics;
  • configuration screens such as Rules;
  • organisational screens such as Tags.

This makes the product feel like a database administration interface even when the individual pages are well designed.

The /budget route is labelled Analytics in navigation. This is a leftover from an older product concept and should become /analytics.

Group navigation by intent:

Overview

  • Overview

Money

  • Transactions
  • Statements
  • Reconcile

Understand

  • Analytics
  • Insights
  • Merchants

Shared

  • Shared
  • Trips
  • Loan

Organise

  • Tags
  • Rules

Lower-frequency configuration items can be visually separated or collapsed.

The default landing page should be a concise status and attention surface, not another full analytics dashboard.

Suggested structure:

  1. This month

    • Personal spend to date
    • Expected baseline at this point in the month
    • Income
    • Net cash
  2. Financial resilience

    • Realistic monthly baseline
    • Cash coverage in months
    • Redraw shown separately from cash
  3. Needs attention

    • Uncategorised or other transactions
    • Unreconciled transactions
    • Statements failing balance assertions
    • New or unusual recurring charges
    • Shared expenses added since the last settlement
  4. Shared

    • Current balances by person and context
    • Loan contribution shortfall shown separately
  5. Recent change

    • The two or three categories that explain the largest movement

The Overview should link into Analytics, Insights, Shared, and Reconcile rather than reproduce their complete tables.

Analytics review

What the current page does

The current Analytics page includes:

  • selected-month spend hero;
  • twelve-month month spine;
  • income, expenses, invested, and net-cash strip;
  • top category movers;
  • eight category sparkline cards;
  • spend-concentration Pareto chart;
  • cumulative spend pace;
  • expandable category table;
  • six-month heat-tinted category ledger.

Each component is defensible in isolation. Together, they create too many competing summaries of the same category data.

What Analytics should answer

Analytics should answer:

What happened during this period, how does it compare, and what explains the difference?

Recommended primary structure:

  1. Period and comparison controls.
  2. Personal spend, income, invested, and net cash.
  3. Explanation of the change versus the selected comparison.
  4. One main category/trend visualisation.
  5. Category breakdown with transaction drill-down.
  6. An optional Explore section for detailed tables.
  • Keep either category sparklines or the six-month ledger as the primary category-trend representation, not both.
  • Move the Pareto chart behind an Explore section. It describes concentration but rarely produces an immediate decision.
  • Retain “What changed,” but make each item clickable and explain which transactions caused the movement.
  • Avoid comparing a partial current month with full prior months unless values are projected or compared through the same day.
  • Add gross-versus-personal-share switching only if it is clearly labelled. Personal share should remain the default.

Calculation and trust issues

Reconciled source rows can be double-counted

/api/analytics/monthly does not currently exclude manual source rows where reconciled_with_id IS NOT NULL. The baseline analysis identified 48 double-counted rows.

The analytics query should apply the same reconciled-row exclusion used by the main transaction queries.

Spend pace compares unlike numbers

The Analytics headline uses:

  • split-adjusted personal share;
  • fees and interest;
  • refund and credit netting;
  • loan interest rather than principal;
  • non-spend-category exclusions.

The cumulative spend-pace chart uses only transaction_type === "debit" and adds gross amount_aud ?? amount. It does not use personal share and does not apply the same refund, fee, interest, or loan semantics.

The chart can therefore disagree with the headline while both appear to represent “spend.” The cumulative series should be produced by the same server-side spend semantics as the monthly total.

Split coverage changes mid-series

Reliable in-app split data begins on 2026-01-09. A trailing twelve-month personal series currently combines older gross spending with newer split-adjusted spending.

Until historical splits are restored:

  • default personal trend analysis to FebruaryJune 2026;
  • visibly mark periods with incomplete split coverage; or
  • offer gross-only twelve-month comparison separately.

Do not present the mixed series as one comparable personal-spend trend.

Comparison baseline is too naive

The selected month is compared against the average of all other months with data. That average can include travel, annual fees, tax payments, incomplete current periods, and months with incompatible split coverage.

Better comparison choices:

  • previous month;
  • same month last year;
  • median of comparable complete months;
  • recurring baseline;
  • user-selected comparison.

Data trust indicator

Analytics should include a compact methodology and coverage indicator:

Personal share · refunds netted · investments excluded · split coverage reliable from Feb 2026 · 12 transactions need classification

This makes the meaning of the numbers inspectable without overwhelming the page.

Insights review

Current problem

The current Insights page contains:

  • Regular versus occasional spending;
  • another monthly category breakdown;
  • recurring charges;
  • fees and interest.

The monthly breakdown duplicates Analytics. The page does not yet surface the most decision-relevant findings already known from the data: sustainable monthly cost, liquidity, the loan-overpayment lever, data-quality weaknesses, or unusual changes requiring attention.

“Regular” is not the same as committed or essential

REGULAR_CATEGORIES includes:

  • groceries;
  • dining;
  • transport;
  • health;
  • personal care;
  • government;
  • charity;
  • pets.

These may recur, but they have very different flexibility and obligation. “Regular” describes transaction behaviour, not financial necessity.

The current chart therefore cannot answer:

  • What is the minimum monthly cost?
  • What can be cut?
  • What is contractually committed?
  • What is lifestyle spending?
  • What is a one-off?

Replace Regular versus Occasional with:

  1. Fixed commitments

    • Contracted loan repayment
    • Insurance
    • Rates and registration
    • Known annual fees
    • Contractual subscriptions
  2. Essential variable spending

    • Utilities
    • Groceries
    • Transport
    • Health
  3. Lifestyle and discretionary

    • Dining
    • Shopping
    • Entertainment
    • Personal care
  4. One-offs and travel

  5. Investments and transfers

    • Shown for cashflow context, excluded from spending

This should support scenario views rather than claiming there is one true baseline.

1. Financial baseline

Show the scenarios already established by the expense-baseline analysis:

  • Survival: contracted loan repayment and essentials only.
  • Realistic: contracted loan repayment plus ordinary dining and charity.
  • Status quo: current loan overpayment and normal life excluding travel.

For each scenario show:

  • monthly amount;
  • six-month reserve;
  • twelve-month reserve.

2. Liquidity and resilience

Show:

  • cash available;
  • redraw available separately;
  • months covered under each baseline;
  • a warning that redraw is lender-controlled and not equivalent to cash.

3. Biggest flexible levers

Examples:

  • voluntary loan overpayment;
  • dining;
  • shopping;
  • subscriptions;
  • travel.

The loan should always show both the contracted floor and actual repayment.

4. Attention and anomalies

Examples:

  • a new recurring charge;
  • a charge larger than its prior range;
  • a category materially above baseline;
  • a fee increase;
  • an unexpected incoming credit categorised as spend;
  • a merchant still classified as other;
  • an investment incorrectly counted as spending.

Each insight should link directly to the affected transactions.

5. Data-quality work queue

The baseline analysis found that data quality is currently a larger blocker than visualisation:

  • other remains a large unresolved category;
  • Raiz, Vanguard Super, and moomoo need investment classification;
  • incoming other credits can make spending negative;
  • government conflates tax with rates and registration;
  • annual fees distort short-window monthly averages.

Insights should make these visible as fixable tasks.

Recurring charges

The current detector identifies merchants with regular transaction intervals. That does not necessarily mean a subscription or commitment. Weekly grocery shopping can look recurring.

Recommended changes:

  • Rename the section Recurring patterns unless contractual charges can be distinguished.
  • Show confidence and the basis for classification.
  • Show the next expected charge date.
  • Separate likely subscriptions from recurring merchants.
  • Allow dismissing or confirming a detected pattern.
  • Highlight price changes.
  • Collapse inactive patterns by default.

The current eight-column table is also too wide for a primary page. Put secondary fields such as first seen, total paid, and count into an expandable detail row.

Fees and interest

The current fees query aggregates statement summary values across all available statements without a date filter. The UI does not label the period, so the total looks like a current-period figure even though it is effectively lifetime to date.

Recommended presentation:

  • Explicit date range.
  • Avoidable fees.
  • Known annual fees.
  • Credit-card interest.
  • Loan interest.
  • Change versus prior comparable period.
  • Drill-down transactions.

Loan interest should remain spending, but appear under fixed or non-discretionary costs rather than being hidden.

Shared review

What the current page answers well

The unfiltered balance cards correctly implement a running ledger:

splits minus payments

This is coherent and should not be changed to exclude splits marked settled. The transaction_splits.settled field is dead data and must not be used for new UI claims.

What the current model cannot answer

  • Which split expenses did a payment settle?
  • Is a particular trip settled?
  • Can a trip be closed without closing Household?
  • Is an imported offset-account credit already represented by a manual payment?
  • What remains open inside one settlement context?
  • How should the shared-loan contribution shortfall be shown?

The page should not imply answers that the data model cannot support.

Tag-filtered balance cards are semantically misleading

When a tag filter is active, participant balance queries intentionally stop subtracting payments because payments are not attributable to a tag. The cards then show raw split totals for the tag.

This behavior is explained in small text, but the card still says “owes you” or “you owe.” That looks like a real payable balance when it is not.

When filtered, relabel the cards:

Split total in Europe 2026

Do not show payment or settlement actions from that state.

Use explicit settlement contexts:

  • Household
  • Individual trips
  • Historical / Pre-2026
  • Closed contexts

Recommended Shared navigation:

  • All
  • Household
  • Trips
  • Closed

Within a context show:

  1. Net balance and direction.
  2. Expenses added since the last settlement.
  3. Payments attributed to that context.
  4. A chronological activity ledger combining expenses and payments.
  5. Context status: running, ready to settle, or closed.
  6. Settlement action.

An offset-account credit and a manual split_payments row can represent the same money. The page should:

  • propose matching an imported credit to a settlement;
  • display the linked transaction;
  • prevent silent duplication;
  • allow a manual payment only when no matching transaction exists.

“Record Payment” should become a context-aware settlement flow:

  1. Choose what is being settled.
  2. Match an existing incoming transaction where possible.
  3. Confirm amount and residual balance.
  4. Preserve an auditable history.

Shared transaction table

The table currently shows raw tx.amount with a dollar sign and no currency indicator. Participant balances correctly convert to AUD.

For foreign transactions, show:

  • the native amount and currency;
  • the AUD equivalent;
  • splits based on the AUD settlement amount.

This prevents a visible mismatch between transaction rows and participant balances.

Shared loan

The loan is not a shared-expense settlement context. It funds an asset rather than consumption, and a loan contribution must never settle a dinner or utility bill.

Give it a separate page or clearly separated ledger showing:

  • expected contribution by period;
  • actual contribution;
  • running shortfall or receivable;
  • principal reduction;
  • interest expense;
  • contracted repayment;
  • actual repayment;
  • voluntary overpayment;
  • redraw movement.

The partner obligation is a fixed 50% of the repayment schedule, not a percentage inferred from actual contributions.

Responsive and interaction improvements

  • Replace wide eight-column primary tables with compact rows and expandable details.
  • Keep financial summaries readable at mobile widths without horizontal scrolling.
  • Add explicit loading skeletons rather than only text.
  • Add error states for failed analytics requests.
  • Ensure chart meaning is not conveyed by colour alone.
  • Give interactive chart regions keyboard-accessible equivalents.
  • Confirm material deletions, including payment-history deletion.
  • Make expandable table rows use buttons with appropriate accessibility state.
  • Use consistent labels for personal share, gross amount, native currency, and AUD equivalent.

Priority 0 — metric integrity

  1. Exclude reconciled source rows from monthly analytics.
  2. Make spend pace use the same spend semantics as the headline.
  3. Add date ranges to fees and interest.
  4. Add split-coverage warnings to historical personal-share analysis.
  5. Fix Shared foreign-currency presentation.
  6. Avoid partial-month versus full-month comparisons.

Priority 1 — product hierarchy

  1. Add Overview and make it the default route.
  2. Group sidebar navigation by user intent.
  3. Rename /budget to /analytics.
  4. Add consistent methodology and coverage indicators.

Priority 2 — Analytics and Insights

  1. Simplify Analytics around period, comparison, change explanation, trend, and drill-down.
  2. Remove the duplicate monthly breakdown from Insights.
  3. Add baseline scenarios and liquidity coverage.
  4. Add flexible-spending levers, anomalies, and a data-quality work queue.
  5. Rework recurring patterns and fees into decision-oriented summaries.

Priority 3 — Shared

  1. Add settlement contexts.
  2. Link payments to real transactions.
  3. Add context activity ledgers and closeable trip contexts.
  4. Introduce the separate loan contribution ledger.
  5. Backfill historical closed-context splits so long-range personal analytics become comparable.

Priority 4 — polish

  1. Improve mobile layouts.
  2. Add accessibility semantics.
  3. Add richer loading, error, and empty states.
  4. Consolidate repeated card, table, filter, and page-header patterns into shared components.

Proposed success criteria

The redesign is successful when:

  • The first page explains current status and outstanding actions without opening multiple screens.
  • Analytics can explain why one comparable period differs from another.
  • Insights identifies baseline cost, financial resilience, flexible levers, and data-quality problems.
  • Every displayed total states or clearly implies its period and whether it is gross or personal share.
  • Historical charts do not silently combine incompatible split coverage.
  • Shared can distinguish Household, Trip, and Historical balances.
  • A settlement can be traced to both the obligation it reduces and the real transaction representing the payment.
  • Loan contributions cannot affect ordinary shared-expense balances.