Home dashboard implementation plan (#157)¶
For agentic workers: implement this plan task-by-task against the spec. Do not merge
oh-yea. Port backend pieces fromorigin/feat/157-home-tabonto the #161 shell.
Goal: Home tab becomes a scrollable dashboard (net worth, viewer positions, 1H P&L chart, ranged leaderboard, missed votes) with new home dashboard HTTP routes.
Architecture: Keep GET /v1/home for Cabals-tab discovery. Add GET /v1/home/dashboard, GET /v1/home/pnl-series, GET /v1/home/missed-proposals. Replay nav_snapshots plus a live “now” point for the chart. Mobile reads dashboard from AppSessionStore on the Orbix canvas.
Tech stack: Go API, Postgres nav_snapshots / proposals / proposal_votes, SwiftUI + Swift Charts, packages/mobile-core DTOs.
Spec: docs/superpowers/specs/2026-09-19-home-dashboard-design.md
Global Constraints¶
- User-facing copy: “cabal”, never club/group. API JSON stays
groups/groupId. - No NAV, wallet, mint, or xStock in UI strings.
- Mobile never calls Jupiter, Pyth, xStocks, or Solana RPC.
- Personal Apple team only:
MONACO_IOS_DEVELOPMENT_TEAM=8VKB4AC8F9. - Never commit a UDID.
just test mobile= hostswift testinpackages/mobile-core. - Work on a git worktree branch
feat/157-home-dashboardcut fromfeat/161-five-tab-shell(PR #197), not from stalefeat/157-home-tab. - Do not dirty the primary checkout if it is
main. - Toasts for vote confirmations; no main-UI banners.
- Net worth is cabal equity only. Account balance stays
GET /v1/me/balance.
Task 1: Dashboard DTOs and decode tests (mobile-core)¶
Files:
- Create: packages/mobile-core/Sources/MonacoCore/HomeDashboardDTO.swift
- Create: packages/mobile-core/Tests/MonacoCoreTests/Fixtures/home_dashboard.json
- Create: packages/mobile-core/Tests/MonacoCoreTests/HomeDashboardDTOTests.swift
- Modify: packages/mobile-core/Sources/MonacoCore/MonacoAPIClient.swift
- Modify: packages/mobile-core/Sources/MonacoCore/MainFlowCopyAudit.swift
- Modify: packages/mobile-core/Tests/MonacoCoreTests/MainFlowCopyAuditTests.swift
Produces: HomeDashboardDTO, HomeMyGroupDTO, HomePnLSeriesPointDTO, HomeMissedProposalDTO, HomeLeaderboardRange, getHomeDashboard(leaderboardRange:), getHomePnLSeries(range:).
- [ ] Add fixture JSON matching the spec keys (
netWorthUsd,netWorthDollarPnl,netWorthPercentReturn,myGroups,pnlSeries1H,leaderboard,missedProposals). - [ ] Decode test: 1 my-group row, 2 series points, 1 person, 1 missed proposal;
groupIdmaps to SwiftgroupID. - [ ] Client paths:
/v1/home/dashboard,/v1/home/pnl-series,/v1/home/missed-proposals. QueryleaderboardRange/range. - [ ] Copy audit adds:
Your cabals,Join a cabal to see your positions here.,P&L history shows up after you fund a cabal.,You're caught up.,Needs your vote. - [ ] Run:
just test mobile
Task 2: Snapshot queries + missed-proposal SQL¶
Files:
- Modify: apps/backend/internal/postgres/nav_snapshots.go
- Modify: apps/backend/internal/postgres/proposals.go
- Test: apps/backend/internal/postgres/nav_snapshots_test.go (or new home_dashboard_store_test.go)
- Test: apps/backend/internal/postgres/proposals_test.go
Produces: ListNavSnapshotsForGroupsSince(ctx, groupIDs, since), GetNavSnapshotAtOrBefore(ctx, groupID, at), ListMissedOpenProposalsForUser(ctx, userID, groupIDs, limit).
Port from origin/feat/157-home-tab. Missed query: proposals.status = 'open' AND expires_at > now() AND group_id = ANY($groups) AND no proposal_votes row for userID, ORDER BY created_at DESC LIMIT 20, join groups.name.
- [ ] Tests: snapshot at-or-before returns latest
created_at <= t; missed list excludes voted and expired. - [ ] Run:
just test backend(or the package tests if the full suite is too long — still include these files).
Task 3: HomeService dashboard + HTTP¶
Files:
- Create: apps/backend/internal/app/home_dashboard.go
- Create: apps/backend/internal/app/home_dashboard_test.go
- Modify: apps/backend/internal/httpapi/home.go
- Create: apps/backend/internal/httpapi/home_dashboard_test.go
- Modify: apps/backend/cmd/api/main.go (apiRoutes + HandleFunc)
- Modify: apps/backend/cmd/api/boot_log.go only if route list is generated elsewhere — keep apiRoutes in sync.
Produces: GetHomeDashboard, GetHomePnLSeries, GetHomeMissedProposals, ParseHomeLeaderboardRange.
- [ ] Port math from
origin/feat/157-home-tab(viewerGroupPositions,buildViewerPnLSerieswith live now-point,buildRangedLeaderboard,listMissedProposals). - [ ]
GET /v1/mestays exact. Do not register a conflicting/v1/homeprefix that 404s dashboard (useGET /v1/home/dashboardetc. on Go 1.22 mux). - [ ] Invalid range → 400
{ "error": "invalid leaderboardRange" }. Unauth → 401. - [ ] Handler tests: 401 without bearer; 200 body includes
myGroupsfor a joined funded user; missed proposal appears until a vote row exists. - [ ] Comment on ranged leaderboard: current share units × historical snapshot NAV; not a daily rollup.
- [ ] Run:
just test backend
Task 4: Home UI on the Orbix shell¶
Files:
- Modify: apps/mobile/Monaco/Features/Home/HomeView.swift
- Create: apps/mobile/Monaco/Features/Home/HomeNetWorthSection.swift
- Create: apps/mobile/Monaco/Features/Home/HomePositionsSection.swift
- Create: apps/mobile/Monaco/Features/Home/HomePnLChartSection.swift
- Create: apps/mobile/Monaco/Features/Home/HomeLeaderboardSection.swift
- Create: apps/mobile/Monaco/Features/Home/HomeMissedVotesSection.swift
- Modify: apps/mobile/Monaco/Features/Shell/AppSessionStore.swift
- Modify: apps/mobile/Monaco/API/MonacoAPIClient.swift (app target client, match mobile-core)
- Create: apps/mobile/Monaco/API/DTOs/HomeDashboardDTO.swift if the app target does not import the mobile-core type (match existing HomeViewDTO split).
Layout (top → bottom): hero net worth → compact account + Deposit → positions → 1H chart → leaderboard chips → missed votes.
- [ ] Delete Home
Picker(Cabals / People). Cabals tab still usesGET /v1/homegroups. - [ ]
MonacoHeroHeaderfor the figure (no nested card around the hero). P&L tint:MonacoTheme.success/.destructive. - [ ] Account row stays
PlatformBalanceCard(or a compact sibling) + Deposit link. Do not add it into net worth. - [ ] Chart: Swift Charts
AreaMark+LineMark, kiln stroke, height 160, empty state under 2 points. - [ ] Leaderboard:
MonacoChip1H/1D/1W/1M/All. Chip change refetches dashboard with that range only. - [ ] Positions →
GroupDetailView. People →UserProfileGroupsView. Missed →ProposalDetailView(auth:proposalId:). - [ ] Ignore
isRequestCancellationon dashboard loads. - [ ] A11y:
home-net-worth,home-my-group-{id},home-pnl-chart,home-leaderboard-row-{id},home-missed-{proposalId}. - [ ] Run:
just test mobileandjust build mobile
Task 5: Gold-sim QA¶
Do not skip. Gold sim UDID from scripts/gold-sim-udid.sh. Backend must be the current just run backend (dashboard routes registered).
Manual:
- Session restore lands on Home tab (not Cabals/People picker).
- Hero net worth equals sum of Your positions equities.
- Account balance is a separate number (can be $0.00).
- Funded cabal with a deposit: chart has a line (2+ points).
- Leaderboard All → 1W updates ranks (or stays one row if only one person).
- Open unvoted proposal appears under Needs your vote; after Vote yes, row gone on refresh.
- Cabals tab still lists all cabals including unjoined.
Done when¶
- Dashboard routes live on the running API; Home matches the spec order; Cabals tab still discovery.
just test backend,just test mobile,just build mobilegreen.- Gold-sim steps above pass.
- Spec file is not edited during implementation unless the user asks.