Where /dashboard stands today, and a concrete plan for the changes you called out — now reconciled with the 2026-08-19 standup.
The dashboard isn't a static column — it's a 12-column drag/resize canvas (ADR-0097/0103). Every card is a "widget" defined in a catalog, ordered by a layout manifest, and composed in one big file. Ten widgets ship by default; each user's layout persists.
DashboardHome.tsx (lines 1085–1295) — the widget map.catalog/surfaces.ts.DashboardDataProvider polls /api/dashboard/stats + /api/leasing/stats every 30s. Numbers come from computeDashboardStats and friends.ObservabilityChart.tsx) — Recharts is banned by an in-code comment. Collapsed cards get a sparkline; expanding a card opens the big trend chart.The upshot: most of what you asked for is a component swap or a default change, not new data plumbing — the data is almost always already computed.
Cross-checked the plan against the standup recap. It confirms the direction — dashboard rebuilt as a quick summary of each section, each clickable into its detail (leasing snapshot, maintenance, collections summary → 30/60/90) — and adds three things my first pass missed:
Portfolio Metrics removal was decided live ("looks like the old March UI… removing that"). Collections-as-a-summary-KPI and the weekly "reports" email as the tile reference were both named explicitly.
Two things stacked on top of each other. First, a default-window choice — expanded charts open short (NOI 6 months, Occupancy/Rents 30 days). But the meeting surfaced a second, deeper problem: even on a 12-month view, only ~7 months actually rendered, and "all time" wouldn't pull the full history. Fede noted AppFolio holds ~8 years. So a default bump alone won't fix it.
FinancialPeriod rows with ~98 stored months for Camellia. The team's live observation was ~7 months on a 12M view. Both can be true if the data is stored but the chart's query/render is the bottleneck — or the rows aren't actually backfilled as far as the code path allows. I need to check what the chart actually returns for Camellia against what's in the table before treating "all the way back" as a one-line default change. That verification is step one of this item.FinancialPeriod rows built from AppFolio's twelve_month_income_statement (true per-month columns) — uncapped, ~98 stored months for Camellia. The known YTD bug on the other endpoint (income_statement) does not touch this path.FinancialPeriod months and run the chart's own query at all — settle whether the ~7-month ceiling is storage, query, or render. This decides whether the rest is a one-liner or a backfill.6M → 12M (or all), Occupancy/Rents 1M → 12M. The window vocabulary already has 12M and all tokens, with persistence.catalog/catalog.ts (NOI :172, occupancy :146, rents :160) · window tokens in catalog/default-window.ts · financial source dashboard/stats/financial-summary.ts#ebebeb gridlines, 10px muted-gray labels. Faint and unfinished on a marquee surface.preserveAspectRatio="none" and stretches a fixed 900-wide viewBox to fill the container — a code comment even admits it looked "pixelated/uneven." Classic hand-rolled-chart tell.@container hacks to dodge collisions.--v-* "Vercel Observability" family for the plot, brand --color-*/purple for the brush. It doesn't read as one system.--v-* family.ui/charts/ObservabilityChart.tsx · callers insight-views/TrendView.tsx, _shared/InlineMetricSparkline.tsxThe bare tile is WeeklySnapshotCard.tsx — five flat counters (Move-ins, Move-outs, Signed, Applications, Tours), a plain number over an uppercase label, showing — when empty, so a quiet week reads as a dead tile. The email you love (owner-report-email.ts) uses a rounded pill per row: navy label, bold indigo value, a "no change vs last week" delta line, and an 11-row occupancy box score under an "Occupancy · as of Jul 20" header.
top-metric/OccupancyCard.tsx — already has that rich treatment and shares the exact same data (countMoveEvents, computeTrendingOccupancy) as the email. This is lift-the-pattern, not new data.↑ the pill treatment from the email, applied to the weekly tile
OccupancyCard already owns "where we stand now." So I'd make the weekly tile the complementary "what changed this week" panel — the flow deltas (move-ins/outs/transfers/signed/apps/tours) in the email's pill style — rather than duplicating the full box score. Alternative: fold weekly counters into OccupancyCard's expanded view and retire the strip. My rec is the former.
leasing-renewals/WeeklySnapshotCard.tsx · pattern to lift: top-metric/OccupancyCard.tsx + reporting-digest/report-email.ts (metricsTable)That's the big table at the bottom (MetricSummaryTable, widget id metricTable). Removal is mechanical but touches several registries, and a catalog-drift test pins the wiring — so it's all-or-nothing or CI fails.
DashboardHome.tsx (nodes :1268, multiExpandable :770, drillableMetricRows :739, in-file component) · catalog/catalog.ts (:36, :250) · catalog/surfaces.ts (:55/:67) · inspector/expand-wiring.ts (:44/:94) · canvas/card-corner-tag.ts (:59) · catalog/card-windows.ts (:398)pendingApproval is already computed and never shown.pendingApproval, spendByPriority, problemUnits, actionCenterData, and every WorkOrder field (priority incl. emergency, status, created/resolved, vendor, unit, displayId).maintenance/MaintenanceSection.tsx · source dashboard/stats/compute.ts (already emits the unused fields)Almost everything exists already — this is assembly, not construction.
collections/_components/CollectionsAgingCards.tsx — a "Total past due" hero + a 4-up aging row (1–30 / 31–60 / 61–90 / 91+) with dollars, share-of-total bars, and status composition. Exactly a dashboard-lane shape.GET /api/collections returns the summary + per-band aging; and stats.totalPastDueBalance + stats.delinquencyRate are already on the dashboard payload (same rollupAging the /collections page uses, so numbers are guaranteed to match).CollectionsAgingCards (or a compact variant), driven by the collections summary. It absorbs the Total Past Due + Delinquency orphaned by #4 and matches the /collections page styling.collections/_components/CollectionsAgingCards.tsx, BreakdownCard, the card-row-4up grid · data /api/collections + stats.totalPastDueBalance/delinquencyRateMeeting shape: delinquency rate + total outstanding as the summary KPIs, clickable into a 30/60/90 breakdown — exactly what the aging component already renders. The /collections page itself has separate bugs the team flagged ("ended" date, "paid"→"last paid", "certified funds only" as a status) — noted, but those are page-level, not this dashboard widget.
The team wants the leasing section turned into a true funnel, not a flat KPI row. This is the biggest thing my first pass missed.
leasing-renewals/ (the funnel + KPI dots) · data already in leasingStats (inquiries/tours/applications/signed counts + awaiting-review). Needs its own scoping pass — I only have the meeting's intent, not a component-level map yet.Almost everything is a component swap, a default change, or a reuse of existing code — the numbers are already computed. The two items that need a scoping pass before they're one-PR-ready are the time-range verification (#1) and the leasing funnel (#7). Waiting on your extra collections context before locking #6's shape.