Facility Pipeline — UX spec
Page structure, component reuse, the complete modal inventory, and the per-page state checklist.
Grounds the annotated mockups in mockups/facility-pipeline-mockups.html. Follows
docs/ux-ui-guidelines.md.
1. Surface & navigation
Extend /admin/crm (the existing CRM Kanban from ADR-0033) — do not add a competing
"Facility Pipeline" nav item. Rename the existing CRM nav entry to "Pipeline" and host the
experience as tabs:
- Today's Follow-ups — default landing when tasks are due (the rep's worklist). Smart default:
open here if
count(due tasks for me) > 0, else Signal Inbox. - Signal Inbox — new + unmatched
job_signals. - Pipeline Board — the existing
crm_leadsKanban (drag-and-drop already built). - Prospect Discovery — high-score facilities with no open posting (CMS-only signals).
- Sequences — cadence templates + enrollments.
Facility 360 is the existing facility detail route (/admin/facilities/[id]), linked from cards —
not a tab. Wrap everything in AdminShell (white sidebar — the standalone mockups' dark shell is not
production). Tab labels use SVG icons from nav-icons, not emoji.
2. Component reuse (all verified in src/components/ui/)
| Need | Use | Not |
|---|---|---|
| Signal Inbox / Prospect lists | Table (mobile card-stack via data-label) | raw <table> |
| Role / urgency / tier / status chips | Badge (tones: danger/warning/neutral/success/info, dot variant) | bespoke .pill |
| Feedback | notify() + Toaster (aria-live) | a custom toast div |
| Destructive confirm (Dismiss, Mark Lost, deactivate sequence) | confirmDanger() + DangerConfirmHost | a bare toast |
| Empty states | EmptyState (icon + title + description + action) | ghost text |
| Modals | Dialog | window.prompt |
| Phone fields | PhoneInput | a text input |
| Tabs | Tabs | custom tab CSS |
| Loading | Spinner + route loading.tsx skeletons | nothing |
Color/semantics fix: Tier-1 = brand indigo (high opportunity is positive), Tier-2 = amber, Tier-3 = neutral. Urgency chips follow the same discipline: urgency 3–4 = brand/info, urgency 1–2 = amber/warning, urgency 0 = neutral — never red (red is danger only, same as Tier-1); always keep the ⚡N text so it's never color-only. Never convey status by color alone — always pair with text/dot. Overdue tasks use a red left-border accent, not a full red row.
3. Trust signals / provenance (required by the guidelines)
- Score chip shows "as of {date}" (
score_updated_at); score recomputes on ingest + quarterly. - CMS-derived rows in the "why this score" panel show "CMS data as of {quarter}".
- F-tag row tooltip: "Source: CMS CASPER survey results" (so it's not mistaken for a posting).
- Signal cards show posting age + last-seen; the unmatched badge tooltip explains the failure
reason (
below_threshold/ambiguous) before the match modal opens. - All four KPI cards are deep-links to the filtered view they describe.
4. Complete modal inventory
| # | Modal | Purpose | Key fields | Primary / secondary | Notes |
|---|---|---|---|---|---|
| M-01 | Raise Lead | signal → crm_lead | facility (read-only), owner, stage, est. value, enroll-in-sequence (progressive) | Raise lead / Cancel | disable in-flight; active-client → upsell variant |
| M-02 | Match Signal to Facility | resolve an unmatched signal | signal summary (read-only), facility typeahead, top-3 auto-suggestions w/ confidence, "add as alias" | Confirm match / Skip | shows provenance + why match failed |
| M-03 | Log Activity / Call | record a crm_activity + advance step | type (prefilled from task), contact, datetime, notes, outcome, advance-step | Save / Cancel | stamps last_contacted_date + next_follow_up_date |
| M-04 | Enroll in Sequence | enroll facility/lead | sequence, start date, owner | Enroll / Cancel | shows step count + est. completion; blocks if contact-enrichment gate unmet; warns if already enrolled |
| M-05 | Review & Send Email Draft | approve AI draft | to (read-only), subject (editable), body (editable), provenance line | Send (confirm consequence) / Save draft / Cancel | never auto-send; "Drafted by AI from {template} · {time}" |
| M-06 | Add / Edit Contact | facility_contact | name*, title, role* (widened vocab), email, PhoneInput, LinkedIn, is-primary | Save / Cancel | required: name + role; inline email/URL validation |
| M-07 | Mark Won | status=won + convert | confirmed est. value, conversion date, link facility, intake required | Mark won / Cancel | consequence text; Undo (5s); reuse src/lib/crm/pipeline.ts |
| M-08 | Mark Lost | status=lost + reason | reason (select), notes (required if "other"), reheat date | Mark lost / Cancel | confirmDanger consequence; reversible |
| M-09 | Edit Sequence Step | edit a follow_up_step | day offset, channel, persona, template key, instructions | Save / Cancel | warns "N enrollments affected"; future-only vs all |
| M-10 | Dismiss Signal | status=dismissed | signal summary, reason (incl. "already a client"), notes | Dismiss / Keep | confirmDanger consequence; recoverable via filter |
| M-11 | Snooze Task | status=snoozed + snoozed_until | snooze-until (quick chips +1d/+3d/+1w/+2w), reason | Snooze / Cancel | shows re-appear date; default +3d |
| M-12 | Create / Edit Sequence | CRUD follow_up_sequence | name, description, enrollment_trigger, active, step list | Save / Cancel | progressive step editor; warn before deactivating w/ active enrollments |
| M-13 | Add Lead (manual) | quick crm_lead w/o signal | facility typeahead (real), stage (prefilled), owner, est. value | Add / Cancel | replaces the mockup's window.prompt |
5. Per-page state checklist
Every interactive component must implement: default · hover · focus · active · disabled · loading ·
error · empty (the bolded three are what the draft missed everywhere). prefers-reduced-motion
must be honored on the Kanban drag and tab fade.
- Signal Inbox: loading skeleton (on load + after "Run ingestion"); error (ingestion failure);
empty ("No new signals — last ran {time}. Run ingestion." with CTA); filtered-empty; row focus;
action buttons disabled in-flight (prevent duplicate raise/dismiss); unmatched provenance tooltip;
mobile card-stack via
Tabledata-label. - Pipeline Board: loading skeleton cards; error (CRM query); empty board + empty column via
EmptyStatew/ CTA; card move optimistic with rollback + toast on failure; drag move announced to screen readers (aria-live); the ⋯ menu needsrole=menu/menuitem+ Esc; mobile horizontal scroll (one column visible) rather than a flat vertical dump; WIP soft-warning past a column cap. - Today's Follow-ups: loading; error; empty (positive: "Nothing due — check back tomorrow"); task-row hover; "Mark done" spinner (not just opacity); Snooze opens M-11 (not instant); facility link on each row.
- Facility 360: skeletons for score bars / timeline / contacts; error (not found / score not computed); score "as of" + CMS vintage; empty timeline / contacts / signals; "no active sequence" → Enroll CTA; paused-sequence indicator.
- Prospect Discovery: loading; empty ("No Tier-1 prospects without a lead — nice."); each row → Raise Lead / Enroll.
- Sequences: loading; error; empty ("Create your first sequence" CTA); empty step list;
per-enrollment count; deactivate confirm (
confirmDanger); paused/inactive indicator.
6. Daily-flow notes
- Land on Today's Follow-ups when tasks are due. On an empty pipeline, show a one-step setup card: "Run signal ingestion to find facilities hiring for MDS roles → raise leads from the Signal Inbox," with "Run ingestion" as the prominent primary CTA.
- KPI cards (new signals, unmatched, Tier-1, due) are clickable into their filtered views.