v2: accounts list + detail pages with inline account linking#1104
Merged
Conversation
Adds the Accounts surface to the v2 SPA. The list page groups every
account by institution or type, surfaces a net-worth / assets /
liabilities strip, and exposes a per-card link to the new detail page.
The detail page covers balance hero (sign-aware for liabilities,
utilization bar for credit), inline rename via display_name, exclude
toggle, recent transactions, and parent-connection link.
Account linking moves here from the removed top-level Account links
page: the detail page hosts a section listing every link the account
participates in (primary or dependent), with reconcile + unlink
actions, and a Sheet to create a new link from this account to an
eligible dependent. Dependents themselves hide the linking UI to
prevent A→B→C chains the backend would reject anyway.
Backend untouched — uses /api/v1/accounts, /api/v1/accounts/{id}/detail,
/api/v1/account-links. The Account interface is widened to mirror the
full AccountResponse (previously a subset). Existing connections page
+ connection detail page link through to the new account detail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Card primitive defaults to py-6 and I added py-4 on CardContent — the two stacked into ~40px of vertical padding inside what's meant to be a one-liner stat card. Override the Card wrapper to py-4 and drop the redundant CardContent padding so the strip reads as a dense header instead of a content panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The detail page was fighting the Card primitive's defaults. Two
problems:
1. Stat cards (Balance, Details) split the label and value across
CardHeader and CardContent. The Card's flex `gap-6` between
children pushed the 24px of air between the label and the number
that read as broken layout. The shadcn dashboard pattern puts
CardDescription + CardTitle inside one CardHeader (gap-2) instead.
Rewrote BalanceCard to do this — utilization bar / currency note
move to CardFooter. Dropped the redundant "Details" header from
SecondaryCard; the rows are self-labelling.
2. Section cards (Settings, Account links, Recent transactions)
used `pb-3` / `pb-2` overrides on CardHeader to tighten the gap.
That class is a no-op without a border-b — only `[.border-b]:pb-6`
responds to it — so the gap-6 was applying anyway. Removed the
overrides and moved right-aligned actions (Link an account, Last
N) into CardAction, which is the slot the Card grid is built to
place them in (no flex justify-between hacks).
Result: stat cards now read as a polished label/value pair, sections
breathe with consistent vertical rhythm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups based on review: - Row padding was py-3 (12px) while the Transactions page's TableCell is p-2 (8px), so rows on the account detail read as ~50% taller than the same data on the Tx page. Dropped to py-2 to match. - Cap the recent-transactions list at 15 client-side. The detail endpoint returns up to 25; 25 is too many for a "preview" — the full list is one click away via the footer link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
984a1aa to
380eaf3
Compare
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.
Summary
Adds the Accounts surface to the v2 SPA: a list page grouped by institution or type, and a per-account detail page that hosts inline rename, exclude, and account linking (which is now scoped to the account it's set up on, since the top-level Account links page was removed in #1098).
What's new
/v2/accounts— list grouped by institution (default) or type, with a net-worth/assets/liabilities strip, family-member tab filter, dependent badges, credit utilization bars, and inline connection-status pills for accounts on a degraded connection. Pulls from/api/v1/accounts./v2/accounts/$id— balance hero (sign-aware: "Balance owed" for credit/loan, "Current balance" otherwise), inline display-name editor, exclude-from-sync toggle, parent-connection link, the most recent 25 transactions, and an Account links card.LinkAccountSheetcreates a new primary→dependent link, filtering out ineligible accounts (already-dependent, reverse-link conflicts) the way the backend would. Dependents themselves hide the linking UI to avoid offering an A→B→C chain the backend would reject.Account linksnav entry removed from the sidebar (it pointed at a page that no longer exists post-v2: simplify-pass over connections stack (stack 7/7) #1098). Setup now reads as Accounts / Connections.Backend
Untouched — the page uses the existing
/api/v1/accounts,/api/v1/accounts/{id}/detail,/api/v1/account-links(list / create / update / delete / reconcile) endpoints via new TanStack Query hooks inweb/src/api/queries/{accounts,account-links}.ts. TheAccountTS type was widened to mirror the fullAccountResponse(was a subset).Conventions
PAGE_OVERRIDES+ an explicit detail route inweb/src/main.tsx.AccountCard,AccountsSummary,account-utils) live underweb/src/features/accounts/— used only by these two routes for now, so feature-scoped per the v2 rules.toggle-groupprimitive installed for the institution/type grouping switch.*_account_idas short_ids (cf. the project's compact-ID convention); the singular endpoints return UUIDs. The links section and link-sheet filter onshort_idso the current shape works — noted inline.Evidence
Validated in Chrome DevTools MCP against
make devon port 8082 (Plaid sandbox data).bun run lintandbun run buildboth pass.Accounts list (desktop) — net-worth / assets / liabilities strip, institution grouping, credit-card sign flip on the Platinum row:

Accounts list (mobile, 420px) — same content, single column:

Account detail — credit card — "Balance owed $2,571.04" hero, settings card, empty account-links section, recent transactions:

Account detail — depository with active link — "Current balance $55,807.95", with an active "Primary · Covers → Essential Savings" link in the Account links section:

Link account sheet — primary fixed to the current account, dependent picker filters out ineligible accounts, tolerance defaults to 3 days:

Account detail — dependent state — the "Linked dependent" badge appears, the Link CTA is hidden, and the AccountLinksSection is suppressed:

I exercised the create + delete link flow end-to-end during validation (verified the dependent badge propagates and the link sheet's eligible-dependent filter updates after creation) and cleaned up the test link after capturing evidence.
Test plan
/v2/accountsand confirm the detail page loads with correct balance / utilization / recent transactions?group=updates🤖 Generated with Claude Code