WiseAI Realtor — Command Center (Dashboard) Expected Output Spec
⛔ STATUS: DRAFT — NOT APPROVED. CLAUDE.md Rule #17 (HARD GATE) is NOT satisfied.
Do not start the customer-facing build of this screen until the founder approves this spec. It is Stage-1 agent research, pre-populated from the realtor design docs + the
dashboard.htmlmock; the founder confirms or changes each expected output in a Stage-2 interview.
Sourced from (read-only, 2026-06-29):
products/wiseai-realtor/design-direction.md(Workflow-Rail shell, bento command center, anti-patterns, components),information-architecture.md(group A "Daily Work"),data-model.md(there_*CRM spine),backend-completeness-audit.md(the systems-layer gaps + Canadian bar), and the visual prototyperealtor-mockups/dashboard.html. Format matched to the sibling specsacceptance/ai-front-desk.mdandacceptance/reviews-engine-mvp.md.
0. Scope — what this spec covers, and what it does NOT
This spec covers the Command Center / "Today" dashboard of the WiseAI Realtor
backend — the bento command center an agent or team owner lands on (the
/realtor/app shell on the wiseaiagency.com host; verify the exact mount
against the shipped app router per do-not-reinvent.md). It is one of four
sibling specs that together cover the Daily-Work core:
| Spec | Screen | Mock |
|---|---|---|
| this | Dashboard (bento command center) | realtor-mockups/dashboard.html |
realtor-leads-mvp.md | Leads (list + Kanban + drawer) | leads.html |
realtor-contact-record-mvp.md | Lead / contact record (two-tier) | record.html |
realtor-inbox-mvp.md | Conversation inbox (3-pane) | inbox.html |
This spec does NOT re-specify: the global Workflow-Rail shell chrome (left
nav rail, top bar, command palette, autopilot toggle widget) beyond how the
dashboard consumes it; the underlying voice/chatbot capture behaviour (that is
acceptance/ai-front-desk.md, the approved sibling for the same multi-tenant
agent); the Reviews/Automations engine (acceptance/reviews-engine-mvp.md); or
the public agent website (/s/[slug], already shipped). Those are assumed and
cross-referenced.
The RE Pro Website, listings, chat/voice, Listing Studio and Reviews Engine
already shipped to main (PR #1017 + follow-ons). This is a backend command
center on top of that — extend, do not rebuild (do-not-reinvent.md).
The re_* CRM spine is NEW and being built (WS1a). Today RE leads write to
the flat local_business_leads table via createLead(); the re_* tables are
proposed in data-model.md and nothing reads them yet. Every "reads X"
claim below is a target-state read of the CRM spine (Phase-A forward-fill:
re_contacts/re_interactions become the read model while local_business_leads
stays the capture buffer — data-model.md §2.2). Until the spine ships, tiles
fall back to local_business_leads aggregates or the honest empty state.
1. The mock screen this spec governs + what it reads
Governs: realtor-mockups/dashboard.html — a Workflow-Rail shell (navy left
rail + top bar) with a 12-column bento grid: a gold-accented hero tile (the
one daily number), a 4-tile KPI strip, a large "Aria — your AI front office"
activity proof tile, a "Call these 5 today" Follow-Up Coach tile, a "New
leads by source" ROI tile, a Pipeline snapshot, and a "Needs a human"
escalation tile. Top-bar AI: Autopilot toggle, Density (Comfortable/Compact)
control, "Today / Insights / Goals" tabs.
Reads (target-state CRM-spine APIs — WS1a; verify exact route names against the shipped lib, do not fabricate):
| Tile | Underlying read |
|---|---|
| Hero "New leads today" | re_contacts created today (or local_business_leads fallback), scoped by role |
| KPI: AI conversations / Calls answered / QR scans / Showings booked | re_interactions by interaction_type/channel for the period; calls from voice logs; QR from scan events |
| "Aria — your AI front office" activity feed | re_interactions WHERE actor='ai_agent' ORDER BY occurred_at DESC |
| "Call these 5 today" Follow-Up Coach | re_tasks (due/overdue) + re_contacts keep-in-touch triggers (birthday, home_anniversary, slipping) |
| "New leads by source" ROI | re_contacts.source_category rollup for the period |
| Pipeline snapshot | re_contacts.pipeline_status counts + re_deals weighted value |
| "Needs a human" | re_interactions/conversations flagged escalated, not yet taken over |
Demo tenant: Terry & Sheri Real Estate (local_businesses.id = 00000000-0000-4000-a000-000000000c01, slug terry-and-sheri, vertical = real_estate) — the canonical Beckett/Sutton/Woodstock demo. NEVER use a real
customer tenant as a write target (feedback_never_modify_customer_data). The
mock's "Team Beckett / Sheri Beckett / Sutton Group · Woodstock" identity maps
to this tenant.
2. The AI-Bridge / honesty anchor (applies to every tile)
Per knowledge/architecture/ai-bridge-principle.md:
- Aria is a bridge, named and transparent. The AI front office is "Aria"
(locked, per
project_wiseai_realtor_system). Every tile that shows AI work attributes it to Aria/AI honestly (the activity feed says "by Aria (AI voice)", not "by the office"). The "Needs a human" tile exists because the AI escalates rather than pretending — it never hides that a human is required. - Honest metrics only. Every number is a real count from the spine — no
"Delivered"/"Opened"/fabricated engagement theater (outbound process map).
A metric with no data source connected reads "Not measured — not yet
connected" (the
ai-front-desk.md§9 honesty rule), never a fake number. - No advice surfaced as fact. Dashboard copy never states home values, legal/mortgage/tax positions, or steering language; AI suggestions are framed as next actions for the human agent, not advice to a client.
3. Role-based visibility (capability-gated — rbac.ts)
WiseAI Realtor is multi-user. The implemented RBAC is rbac.ts (PR #1070,
live + tested — the source of truth), which gates by capability across the
roles agent | team_admin | transaction_coordinator | broker_admin | brokerage_owner | marketing_assistant | isa | external_partner at scopes
own | team | brokerage (data-model.md §1.7 carries the same enum). The
dashboard scopes by capability:
| Bucket | Roles (rbac.ts) | Dashboard scope |
|---|---|---|
| Brokerage management | brokerage_owner, broker_admin (brokerage scope; all pipelines) | Brokerage-wide: all agents' leads/pipeline/conversations; team-vs-member performance; pipeline value + commission/GCI — brokerage_owner edits (commissions:amount:edit), both read at brokerage scope |
| Team management | team_admin (team scope; all pipelines in team) | Team-wide: the team's leads/pipeline; team performance; pipeline value + commission/GCI read at team scope |
| Agent | agent (own scope) | Own book: own re_contacts/re_deals/tasks + pipeline value; own-deal commission/GCI visible (commissions:amount:read:own); no peer's pipeline or commission |
| ISA | isa (own/assigned scope) | Own/assigned leads + conversations; NO commission/GCI |
| Support / external | transaction_coordinator (deals + team-read), marketing_assistant (content/reviews), external_partner (read-only, bounded) | Feature-limited subset; never commission/GCI |
Team Beckett note: Terry and Sheri are both management roles — e.g.
brokerage_owner/team_admin(team = onelocal_businessestenant, two agents —data-model.md§1.7). The mock shows Sheri seeing team-wide activity, which is correct for her role. The agent-scope behaviour must be verified by adding a non-owner agent (e.g. a buyer ISA) to the demo tenant and confirming the narrowing.
State table:
| State | Condition | Dashboard behaviour |
|---|---|---|
| Provisioned, has data | tenant active, re_* populated | Full bento (§4) scoped by role |
| Provisioned, no data yet | tenant active, zero contacts/interactions | Demo-seeded empty state + onboarding checklist (§5), not blank tiles |
| Loading | data in flight | Skeletons per tile, not a full-page spinner (§5) |
| A metric source unconnected | e.g. voice line not provisioned | That tile reads "Not measured — not yet connected" (honesty rule) |
| Paused / cancelled tenant | local_businesses.status ∈ {paused, cancelled} | Read-only dashboard; capture is off (consistent with ai-front-desk.md §V9 / local-business-platform.md) |
4. Expected outputs — per bento region
4.1 — Hero tile (the ONE daily number)
Should see:
- A single 2×-scale tile, top-left, gold left-edge accent, with one hero metric and a 30–44px tabular numeral — default "New leads today" with a trend vs the prior 7 days and a sparkline (design-direction "KPI tile" + bento hero). It is the only number at this scale (anti-pattern #2 — size by importance; ≤12 tiles, curate to 5–9 prominent).
- An embedded urgent callout when applicable: e.g. "3 hot leads awaiting a callback · longest waiting 18 min · captured by AI voice overnight" with an Open → action. This is the speed-to-lead make-or-break surface (audit P1).
- The hero number is role-scoped: management sees team-wide new leads; an agent sees their own.
Should NOT see:
- A wall of equally-sized tiles with no hero (anti-pattern #2).
- A fabricated number when the tenant has no leads (→ empty state, §5).
- Commission/GCI as the hero for an agent-scope user.
Reads: re_contacts created in the period (role-scoped); urgent = unactioned
hot leads (lead_score high, no outbound re_interactions yet) with the longest
wait computed from MIN(inbound occurred_at).
Success: the agent sees, in one glance, the single number that should drive their morning, and a one-click path to the most time-sensitive work.
4.2 — KPI strip (the curated tile set)
Should see:
- A small set (mock shows 4) of secondary KPI tiles, each a label + big tabular number + a small honest trend: AI conversations, Calls answered by AI, QR sign scans, Showings booked. Deeper analytics live behind the Insights tab (not crammed onto Today).
- Each tile's number is real or "Not measured" — e.g. "Calls answered by AI"
reads "Not measured — not yet connected" until a
local_business_voice_linesrow is provisioned (mirrorsai-front-desk.md§9; do not show "100% pick-up" with zero calls).
Should NOT see:
- More than ~10 tiles total on Today (anti-pattern #2).
- "Showings booked" implying the AI booked an appointment when no scheduler
exists. ⚠️ OPEN — flag, do not silently resolve: the mock shows Aria
"booked 3 showings", but the approved
ai-front-desk.md(LOCKED decision 4) is capture + FAQ + escalate only — the AI does NOT book/schedule, and the audit lists a "Showing scheduler (Cal/Google)" as named, not designed. Until a scheduler ships and an appointments decision is made for the RE vertical, this tile must either be hidden or relabelled to a captured signal (e.g. "Showing requests captured"), and must not claim the AI confirmed a booking. Founder to resolve in the Stage-2 interview.
Reads: re_interactions grouped by interaction_type/channel; voice call
counts from the voice line; QR from scan events.
Success: four honest secondary numbers; no theater; nothing implying unbuilt capability.
4.3 — "Aria — your AI front office" activity proof tile
Should see:
- A large tile titled "Aria — your AI front office" with a Working · Autopilot status pill (reflecting the autopilot toggle, §4.7) and a one-line overnight recap, then a reverse-chronological feed of what Aria did: booked/requested-showing, answered+qualified a call, captured a lead from a QR scan, and "Needs you:" items Aria escalated (e.g. a commission question — "outside Aria's lane").
- Each row is honestly attributed to AI, carries a language chip where
relevant (e.g. ਪੰ Punjabi — auto-captured
re_contacts.preferred_language, never hardcoded), a relative timestamp, and links to the underlying contact/ conversation. A View log → opens the full activity.
Should NOT see:
- Activity attributed to "the office"/a human when Aria did it (AI-bridge transparency).
- An escalation row that disappears without a human path (it must route to the "Needs a human" tile / inbox).
Reads: re_interactions WHERE actor='ai_agent' (and escalation flags),
ORDER BY occurred_at DESC, role-scoped.
Success: the agent trusts the AI because they can see exactly what it did and where it correctly stopped and handed off.
4.4 — "Call these 5 today" — Follow-Up Coach
Should see:
- A tile titled "Call these 5 today" with a Follow-Up Coach label and a short, prioritized list mixing keep-in-touch triggers (birthday today, 1-yr move-in anniversary), at-risk leads (Slipping — viewed a listing 4× / no reply 2 days), and Hot AI-qualified leads — each with a one-tap Call action and the reason it surfaced.
- Language chips where the contact's
preferred_languageis non-English (drives "call in Punjabi/French" prep).
Should NOT see:
- A generic "you have N tasks" with no reasoning (the value is why these 5).
- Past clients with
do_not_contact = true(global suppression gates this list — §4.8 /data-model.md§1.1).
Reads: re_tasks (due/overdue, AI-coach-created) + re_contacts triggers
(birthday/home_anniversary/last_touch_at decay) — excluding suppressed contacts.
Success: the keep-in-touch engine (audit P1 "AI Follow-Up Coach") gives the agent a defensible call list with a reason for each.
4.5 — "New leads by source" — ROI tile
Should see:
- A ranked bar list of lead sources for the period with count and cost/lead: e.g. QR yard signs (14 · $0/lead), Website/AI chat (11 · $0/lead), REALTOR.ca (7 · $31/lead), Google Ads (5 · $44/lead), Referral (3 · $0/lead) — the "which X creates leads / at what cost" value narrative (audit — the five reports).
- AI-owned sources (QR, AI chat/voice, referral) honestly shown at $0/lead to make the wedge visible; paid portals carry their real cost/lead.
Should NOT see:
- Made-up cost figures where no ad spend is connected (show count only, cost "—", until spend is wired).
Reads: re_contacts.source_category rollup (taxonomy data-model.md §4).
Success: the agent can see at a glance which channels their money and the AI are actually producing leads.
4.6 — Pipeline snapshot + "Needs a human"
Pipeline snapshot — should see:
- A compact stage grid (New / Contacted / Nurturing / Appointment / Offer / Won MTD) with counts and colorblind-safe stage colors paired with labels (design-direction pipeline palette), plus a weighted value in pipeline total. Open board → deep-links to the Leads Pipeline view.
- Value is role-scoped: management sees team weighted value; agent sees own.
"Needs a human" — should see:
- A short list of conversations the AI escalated and a human has not taken
over yet — each with channel chip (Chat/Voice/SMS), language chip, timestamp,
a one-line reason ("What's your commission on a $700k sale?" — Aria escalated),
and a Take over → / Listen + call → action into the inbox
(
realtor-inbox-mvp.md).
Should NOT see:
- A pipeline value that leaks team/peer commission to an
agent(who may read only their OWN,commissions:amount:read:own). - An escalation that is older than its SLA without visual urgency.
Reads: re_contacts.pipeline_status + re_deals (value); escalated
re_interactions/conversations not yet taken_over.
Success: the owner sees the book's shape and the few conversations that need a person right now, with one click to act.
4.7 — Autopilot / co-pilot toggle (top bar, reflected on the dashboard)
Should see:
- A top-bar AI: Autopilot ⇄ Co-pilot switch that sets how autonomous the AI front office is (design-direction component; AI-bridge fit). When Autopilot, the Aria tile shows "Working · Autopilot"; when Co-pilot, the AI drafts but waits for the agent to send/act, and the dashboard reflects "drafts awaiting you".
- The setting is remembered and (for management) settable per the dashboard;
per-conversation override lives in the inbox (
realtor-inbox-mvp.md).
Should NOT see:
- A toggle that silently does nothing (it must change the AI behaviour the tiles
describe — verify behaviour at ≥2 timepoints, not DOM presence,
feedback_verify_behavior_not_dom_presence).
Success: the agent controls AI autonomy and the dashboard honestly reflects the chosen mode.
4.8 — Consent / suppression honesty (cross-cutting)
Should see / NOT see:
- No dashboard surface ever offers an action that would contact a
do_not_contact = truecontact or one whose channel consent iswithdrawn/ expired (re_contactsconsent rollup,data-model.md§1.1/§1.3). The Follow-Up Coach and any "send"/"call" affordance excludes suppressed contacts — the global suppression gates every outbound surface incl. the AI (audit P0 #3). No cron may auto-send (feedback_never_cron_send_campaigns).
5. Empty / loading / error states (anti-patterns #6 and #7)
A build that ships blank tiles or spinners is not done (design-direction anti-patterns #6 "blank empty states", #7 "spinners/pessimistic UI").
| State | Expected output |
|---|---|
| First-run / no data | Bento renders with a demo-seeded preview + a one-CTA onboarding checklist ("Connect your voice line", "Import past clients", "Add your first listing") — never empty tiles. The hero shows "0 new leads today — here's how to get your first" with the checklist, not a blank box. |
| Loading | Per-tile skeletons (shimmer placeholders), optimistic, <100ms transitions — not a full-page spinner. |
| A source unconnected | The affected tile reads "Not measured — not yet connected" with a connect link; other tiles still render. |
| Read error / API down | The affected tile shows a non-destructive inline error ("Couldn't load — retry") with a Retry; the rest of the dashboard stays usable. Never a white screen. |
| Paused / cancelled tenant | Read-only banner; capture-driven tiles show last-known with a "service paused" note; no actions that would send/capture. |
6. Accessibility (AODA → WCAG 2.1 AA)
Per the audit's compliance must-haves ("AODA → WCAG 2.1 AA on public sites AND admin"):
- Contrast: all text meets ≥4.5:1 (≥3:1 large). Teal fill carries white text at ~4.8:1; gold is highlight/accent only, never body text (design-direction color rule). Trend red/green and pipeline stage colors are paired with a label/icon (never color-only) — colorblind-safe.
- Keyboard: every tile action (Open, Take over, Call, toggle, tab switch, density switch) is reachable and operable by keyboard with a visible focus ring; the Cmd+K palette is keyboard-first.
- Semantics: KPI tiles expose label+value to assistive tech (not just a bare
numeral); the autopilot toggle is a labelled switch with state; the activity
feed is a list with meaningful row text; numerals use
tabular-nums. - Motion: sparkline/skeleton animation respects
prefers-reduced-motion. - Targets: comfortable density ~44px rows (default); compact toggle does not drop below AA target sizes for interactive controls.
7. Acceptance checklist (QA runs on the deployed URL)
Evidence is behavioural verification on the deployed wiseaiagency.com host
(real host — host-aliasing/middleware applies; preview hosts hide rewrites,
feedback_preview_host_hides_middleware_rewrites) against the demo tenant
(…0c01) — never "build passes". Sample state at ≥2 timepoints; assert change.
// Hero + KPIs
test.fixme('hero shows ONE daily number (new leads today) at 2x scale, top-left, with trend + sparkline', () => {});
test.fixme('urgent callout surfaces oldest unactioned hot lead with a working Open → into the lead', () => {});
test.fixme('a metric with no connected source reads "Not measured — not yet connected", never a fake number', () => {});
test.fixme('"Showings booked" does NOT claim the AI confirmed a booking (no scheduler) — hidden or relabelled', () => {});
// AI activity + coach + escalation
test.fixme('Aria activity feed attributes each row to AI honestly and links to the contact/conversation', () => {});
test.fixme('Follow-Up Coach lists prioritized contacts each WITH a reason; excludes do_not_contact contacts', () => {});
test.fixme('"Needs a human" lists escalated, not-yet-taken-over conversations with a working Take over →', () => {});
// Role scope (RBAC)
test.fixme('a brokerage_owner/broker_admin/team_admin sees team/brokerage pipeline value + commission/GCI at their scope', () => {});
test.fixme('an agent sees only their own book; OWN-deal commission/GCI visible (read:own) but never a peer/team commission; isa sees none', () => {});
// Autopilot
test.fixme('toggling Autopilot⇄Co-pilot changes AI behaviour the tiles describe (verify at 2 timepoints)', () => {});
// States + a11y
test.fixme('first-run tenant shows demo-seeded preview + onboarding checklist, never blank tiles', () => {});
test.fixme('loading shows per-tile skeletons, not a full-page spinner', () => {});
test.fixme('keyboard-only user can reach every tile action; focus rings visible; reduced-motion honored', () => {});
test.fixme('no color-only meaning — trend + pipeline stages carry a label/icon; gold is never body text', () => {});
8. Guardrails for agents building against this spec
- Rule #17 not satisfied — do not build until the founder approves.
- Extend, don't rebuild — reuse the shipped RE Pro Website / leads / AI /
Listing Studio / Reviews code (
do-not-reinvent.md); the dashboard is a new read surface over there_*spine, not a new product. - Migrate-before-use — the
re_*tables are PROPOSALS (data-model.md); none are applied by this spec. A column is not "used" until it exists; verify viainformation_schema.columns(Rule #18). DDL is founder-gated, additive,IF NOT EXISTS, service-role-grant-only. - RBAC is load-bearing (capability-gated,
rbac.ts) — commission/GCI is the highest-sensitivity data: onlybrokerage_owneredits;broker_admin/team_adminread at scope; anagentreads their OWN (commissions:amount:read:own);isa/support/external see none; a peer's commission must NEVER leak. Gate at the API + query layer; add a contract test liketier-config.contract.test.ts(data-model.md§5.2). - Honest metrics only — real counts or "Not measured"; no Delivered/Opened theater; no cron sends.
- Verify on the real host — wiseaiagency.com (host-aliasing); behaviour at ≥2 timepoints, not DOM presence. Evidence-or-nothing (Rule #20).
- If code diverges from this spec, the spec is updated first (founder approval), then the code.
End of spec. STATUS: DRAFT — NOT APPROVED. Open items for the Stage-2 founder
interview: (1) the "Showings booked" / AI-booking conflict vs ai-front-desk.md
decision 4; (2) the exact hero metric default; (3) which secondary KPIs make the
Today set; (4) the role→scope mapping for non-owner agents on the Beckett tenant.