The first CRM build was torn out — every module worked in isolation but nothing connected: a pipeline card that didn't reflect the contact page, a task list with no relationship to a client, an activity log that couldn't move a deal forward. A reference spec (a third-party "Realtor CRM" PRD) was supplied as a guide for the rebuild. This is that spec reconciled against what KeyRingOS actually is, what's already been decided this project, and what's real vs. invented — for review before anything gets wireframed or coded.
closingboss.io/docs/crm-specWorth naming plainly: nothing here is a concession to the reference spec for its own sake. Three of its structural ideas match conclusions this project already reached the hard way, through direct feedback on the first build — reassuring convergence, not new direction.
tasks table spans a task_type from MANUAL to CALL to ESIGN to CONTINGENCY, tied to both a contact and a deal — structurally the same call this project already made independently: a scheduled call and a completed call are the same kind of row at two points in its life, not two different entities (matches how Pipedrive itself behaves). Kept.esign / storage / physical across 27 document types in five transaction-chronology categories. Already right — nothing to change.Several of its modules solve problems KeyRingOS doesn't have, or were already explicitly ruled out this project. Cutting them isn't a scope reduction so much as not building for a customer (a brokerage with a routing pool, a marketing team running SMS blasts) that isn't the one being served.
The reference's "Unified Inbox" bundles three different levels of integration into one module, which is what made it look like scope creep. Split apart, each piece has a different real cost:
tel: deep link. Opens the phone's own dialer. Free, no vendor, no number to provision. Logged as an Activity the moment it's initiated.sms: deep link, no Twilio. Opens the phone's own Messages app pre-filled, same as Call. Free, no per-message cost, no 2-way thread inside KeyRingOS — the conversation happens on the phone, KeyRingOS just logs that it happened. This is the one item revised mid-respec: dropped entirely at first pass, restored once the distinction was clear — the deep link stays, only the Twilio-backed 2-way inbox is what's cut.kind: "email" Activity with the real subject/body, not a mailto: stub.
Already decided this project and unaffected by the respec: Calendar and Documents
moved out from under CRM to sit beside it, because a task or a signature request
isn't only ever CRM work. The reference's flat top-level routing
(/inbox, /deals, /tasks as siblings of
everything else) is closer to that shape than to the original nested-under-CRM
version — but "Inbox" isn't rebuilt (§03) and "Smart Lists"/"Reports" aren't
rebuilt (§02), so the flattening mostly doesn't apply here.
| Rail group | Item | Route |
|---|---|---|
| Manage | CRM → Pipeline | /crm/pipeline |
| CRM → Contacts | /crm/contacts, /crm/contacts/[id] | |
| CRM → Tasks | /crm/tasks | |
| Calendar | /calendar | |
| Docs & e-sign | /documents, /documents/[id], /documents/upload |
The reference's Contact Detail splits into five horizontal tabs (Profile & Identity, Communication, Live MLS Activity, Documents & E-Sign, Tasks & Milestones) plus a separate 3-column panel. Already tried in spirit and walked back once: a call, a text, a stage change, and a favorited listing are all just entries in one Activity timeline with a provenance tag (Agent / Site / System) on each row — that's what replaces four of those five tabs. Kept from the reference: the idea that a Contact page should feel like a real command center, not a form — just built as one unified surface instead of five.
listingId
once they write an offer — worth setting one at "Offer Written," sourced from
whichever listing they were actually shown or favorited, not a new invented fact.
Flagging both for a decision, not assuming yes.
The reference's board adds a metric strip (Total Pipeline $, Est. GCI, Closing This Month, Stale Deals) and per-card badges (lead source, lead temperature, pre-approval status) on top of the stage columns already built. Sorted by what's actually derivable versus what would have to be invented:
lastActivityDaysAgo() already exists as a real derived value; a card with no activity in >10 days gets the badge, no new data needed.source (Home valuation, Digital card, Site contact form, etc.) — just surface it on the card.The one clear upgrade from the reference worth adopting outright (§01). The first Tasks build had a single "mark complete" checkbox for every row regardless of kind; the reference's per-row contextual action is a real improvement and everything it needs already exists on the Activity type:
| Activity kind | Primary action | Always available |
|---|---|---|
| call | Call (tel:) | Mark done · Snooze (+1 day) |
| text | Text (sms:) | |
| Send email | ||
| deadline / note | View contact | |
| meeting / showing / consultation | View contact | |
| stage / favorite | — (system rows are always done: true, never queued) |
dueInDays by one, same field that already drives sort order and
overdue/today/upcoming grouping, nothing new to keep in sync.
The reference's own three-way ESIGNABLE / STORE_REVIEW / WET_INK split (§01)
confirms the existing 27-type, five-category taxonomy rather than asking for a
different one — this module's rebuild is closer to a restore than a redesign.
One real, separate note on the reference's Prisma schema: it models
deals as their own table, distinct from contacts, so one
contact can carry multiple deals over their lifetime (a past client becoming a new
lead years later). KeyRingOS's fixtures still treat a contact as one deal — correct
for now, since there's no schema or auth yet to migrate, but worth flagging as a
real future question rather than a decision made by omission.
Nothing below is live — the CRM was fully torn out pending this document.
Two decisions in §05 and §06 are flagged rather than assumed (pre-approval +
lender field, buyer offer listingId, and the Total Pipeline $ /
Est. GCI skip) because they're genuine product calls, not implementation details.
Once this is confirmed, the rebuild starts fresh against this spec — not evolved
from the deleted code.