PropFlow dashboard — scope & redesign plan

Where /dashboard stands today, and a concrete plan for the changes you called out — now reconciled with the 2026-08-19 standup.

Investigation across the live code on origin/main, cross-checked against the standup recap. Effort tags: small medium large

How the dashboard is built (so the plan makes sense)

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.

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.

Meeting context — 2026-08-19 standup (what the team expects)

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.

1. Go all the way back small — now with a real bug to chase

What's happening

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.

Honest flag — a conflict to verify before I promise "years." My code read said NOI rides uncapped monthly 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.
The nuance that matters
Plan
Touch: catalog/catalog.ts (NOI :172, occupancy :146, rents :160) · window tokens in catalog/default-window.ts · financial source dashboard/stats/financial-summary.ts

2. The bar feels like slop medium

Why it reads as slop (all real, all fixable)
Plan — restyle the one shared engine, lift every chart at once
Because it's one shared component, a single well-scoped PR upgrades the sparklines, the top-metric cards, and the expanded trend view together — but it needs a visual pass across all of them before merge.
Touch: ui/charts/ObservabilityChart.tsx · callers insight-views/TrendView.tsx, _shared/InlineMetricSparkline.tsx

3. "This week / Move-ins" → the occupancy-email feel smallmd

The two sides

The 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.

Good news: an in-app card — top-metric/OccupancyCard.tsxalready has that rich treatment and shares the exact same data (countMoveEvents, computeTrendingOccupancy) as the email. This is lift-the-pattern, not new data.
Move-ins this week0
Move-outs this week1
Leased — moving in1

↑ the pill treatment from the email, applied to the weekly tile

One design call for you

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.

Touch: leasing-renewals/WeeklySnapshotCard.tsx · pattern to lift: top-metric/OccupancyCard.tsx + reporting-digest/report-email.ts (metricsTable)

4. Remove "Portfolio Metrics" small — but coupled to #6

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.

Blocker: Portfolio Metrics is the only place the dashboard shows Total Past Due and Delinquency Rate. Delete it first and those figures vanish. So it must land with or after the new Collections section (#6), which absorbs them. Sequence matters.
Touch: 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)

5. Maintenance is very poor medium — biggest content lift

What's wrong
The data to fix all of this is already computed and thrown away: pendingApproval, spendByPriority, problemUnits, actionCenterData, and every WorkOrder field (priority incl. emergency, status, created/resolved, vendor, unit, displayId).
Plan — rebuild around the actionable backlog
Touch: maintenance/MaintenanceSection.tsx · source dashboard/stats/compute.ts (already emits the unused fields)
Meeting caveat — the real bottleneck is adoption, not layout. The team's own read: the on-site crew isn't closing out work orders in AppFolio/Clara (~1–2 WOs/month at Camellia, zero via Clara messages), so the completion data a rich maintenance card would show is largely absent. A better card is still worth building, but a redesign can't manufacture data that isn't being entered — set expectations that this card looks thin until adoption improves.

6. Collections needs a section smallmd (reuse)

Almost everything exists already — this is assembly, not construction.

Plan
Reuse: collections/_components/CollectionsAgingCards.tsx, BreakdownCard, the card-row-4up grid · data /api/collections + stats.totalPastDueBalance/delinquencyRate

Meeting 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.

7. Leasing funnel medium — from the meeting, not in your original six

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.

What the team asked for
Real Camellia numbers from the call: last 60 days = 52 inquiries → 23 tours → 10 applications (1 awaiting) → 9 signed. There's a genuine owner-facing insight in here the team wants surfaced: inquiry→tour is ~50% (strong), but tour→application is ~40% vs an industry ~50–60% — a weak spot worth showing.
Touch: the leasing widgets under 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.

Suggested sequence

  1. Verify the time-range data (#1) — resolve the "12M shows 7 months" bug before anything else; it decides scope for the chart work.
  2. Chart restyle + NOI month label (#2, #1) — lifts every card at once and makes the longer series look right.
  3. Collections section (#6) — reuse-heavy, and it's the prerequisite for the next step.
  4. Remove Portfolio Metrics (#4) — only after Collections absorbs Past Due + Delinquency.
  5. Weekly tile → email feel (#3) — self-contained, lift the existing pattern.
  6. Leasing funnel (#7) — needs its own scoping pass first; medium lift.
  7. Maintenance rebuild (#5) — biggest content lift; can run in parallel (but see the adoption caveat).

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.

PropFlow Docs