ci / lint-test (push) Failing after 45s
Backfill dry-run over 130 real messages surfaced one 422: 'payments sum to 1.17
but receipt states 16.50'. The receipt is a mixed Uber payment —
Uber Cash $1.17 + Westpac ••••8032 $15.33 — and the card regex only matched
Visa|MasterCard|American Express|Amex, so an issuer-named leg was dropped
entirely. validateOrderTotals correctly refused it rather than recording $1.17
as the cost of a $16.50 order.
Anchors on the ••••NNNN masking instead, which also covers the form already
seen in the corpus ('Mastercard ••••3893 (CBA Ultimate) CHF 51.23'). Fixture
and regression test added.
Also repoints .env.test at the current postgres-personal container IP and
documents why: the container publishes no host port, so the address changes on
every recreate and the whole integration suite fails with connection errors
until it is refreshed.
662 lines
20 KiB
Markdown
662 lines
20 KiB
Markdown
# UI and information architecture review
|
||
|
||
**Date:** 2026-07-26
|
||
**Status:** Review and redesign proposal — nothing implemented
|
||
|
||
## 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 25–26 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`.
|
||
|
||
### Recommended navigation
|
||
|
||
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.
|
||
|
||
### Recommended Overview
|
||
|
||
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.
|
||
|
||
### Recommended removals and consolidation
|
||
|
||
- 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 February–June 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?
|
||
|
||
### Recommended model
|
||
|
||
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.
|
||
|
||
### Recommended Insights structure
|
||
|
||
#### 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.
|
||
|
||
### Recommended settlement-context design
|
||
|
||
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.
|
||
|
||
### Payments should link to transactions
|
||
|
||
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.
|
||
|
||
## Recommended implementation order
|
||
|
||
### 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.
|
||
|