Contact Detail Page
Every contact has its own page at /dashboard/all-contacts/{contactId} — reached by clicking a contact's name in the All Contacts grid, in a list's own contact grid, from the View full contact link inside a Pipeline record's detail modal, or from an agency owner's cross-workspace contact search. If the contact doesn't exist, has been soft-deleted, or you're not a member of its workspace, you're redirected straight back to All Contacts rather than shown an error page.
On this page
Layout
A fixed tab bar sits above the content — Details, Pipeline, and Activity — with exactly one tab's content showing at a time below it. The tab bar itself never scrolls; each tab owns its own bounded region that scrolls internally on its own when it needs to, so a long list in one tab never grows the page itself.
- Details combines Contact details, Company details, and Custom fields into one continuous multi-column grid — one column on a narrow screen, two from a small breakpoint up, three on a large one — separated by plain section-label dividers rather than separate cards.
- Pipeline shows this contact's Pipeline records as a single internally-scrolling list.
- Activity splits into two independent columns side by side — Activity timeline on the left, Campaign history on the right — each scrolling within its own bounded area.
Contact and company details
The Details tab lays out Contact details and Company details as two groups inside one continuous grid — not two separate cards. A plain section-label divider (an icon, a label, a thin rule underneath) separates the groups; there's nothing to click on it, since a tab already replaced the old expand/collapse interaction.
First name
Dana
Last name
Okafor
dana@nimbusrobotics.co
Verification status
Valid
Title
VP Operations
Location
Austin, TX
Company
Nimbus Robotics
Company Headcount
51-200
Founded Year
2018
Company Domain
nimbusrobotics.co
Company Industry
Logistics
HQ City
Austin
Contact details — First name, Last name, Email, Verification status (Unverified / Valid / Risky / Invalid / Verified / High probability / Catch-all / Email not found), Title, Seniority, Job Function, LinkedIn URL, Location. Every field is click-to-edit; clicking Email shows the raw address, saving lower-cases and trims it, and rejects a duplicate already in this workspace or an attempt to clear it to empty. Location is one field on screen but writes to two underlying columns behind the scenes — typing "Austin, TX" splits on the comma into city and region; typing a single value with no comma saves it as one broad location string instead.
Company details — Company, Company Headcount, Company Specialties, Founded Year, and Company Description are all click-to-edit. Company Headcount accepts either a plain whole number or a range string like "51-200" — whichever you type is what's saved; the other representation is cleared. Company Domain, Company Industry, Company Type, HQ City, HQ Region, and HQ Country are read-only — there's no edit control for any of these six anywhere in the app today, on this page or the All Contacts grid; they're populated only by enrichment or import.
Other custom fields
A third section-label group, Custom fields, sits at the bottom of the same Details-tab grid, in its own full-width row below Contact details and Company details — not a separate card, and nothing to expand or collapse.
Anything not covered above (JSON)
{"referralSource":"partner-webinar"}
Every key in the contact's custom-fields data that isn't one of the ~18 known fields shown above (typically an unexpected column from a CSV import) shows up here as a single-line, minified JSON blob — one editable field, not a formatted key/value editor. Editing merges by key: a key you leave out of what you type is left untouched; setting a key to null clears it.
Activity timeline
The Activity tab — one of three tabs at the top of the page, alongside Details and Pipeline — shows a merged, chronological feed of everything real that's happened with this contact: email opens, clicks, and bounces; Pipeline stage changes; and reply messages, both received and sent. It sits side by side with Campaign history as two independent columns within that same tab.
Timeline
Replied: "Re: Quick question about your Q3 hiring plans"
Clicked a link in an email
Moved from Engaged to Interested
Opened an email
Each entry gets a small colored icon badge by source — a Mail icon in #00C896 for email opens, clicks, and bounces; a TrendingUp icon in #F59E0B for Pipeline stage changes; and a MessageSquare icon in #60A5FA for reply messages — plus a matching colored label (Email / Pipeline / Reply) and a timestamp, newest entry first.
A contact with no recorded activity yet shows a plain centered message, "No activity recorded for this contact yet." — not an error state, not a stuck loading spinner.
When this contact's own data shows direct evidence that a reply lost its link to a campaign lead after being received, a plain line appears above the list: "Reply history may be incomplete for this contact." This is a real, computed, per-contact signal, not a blanket disclaimer shown to everyone — it only appears when that specific evidence exists for this particular contact.
Pipeline records
Reached via the Pipeline tab — the second of the page's three tabs. Its content is a single, internally-scrolling list, not a standalone card on the page — a long list scrolls within the tab's own bounded area, never the page itself.
Q3 warehouse automation outreach
Spring re-engagement
Every Pipeline record this contact holds — one per campaign they're enrolled in — with the campaign name, a stage badge, the estimated value if set, and the health score. Clicking a row opens the same record detail modal used everywhere else in Pipeline, where you can change stage, set an estimated value, and review or undo history. A contact enrolled in no campaign yet shows "Not in any campaign's pipeline yet." instead of an empty list.
Campaign history
Reached via the Activity tab, where it sits side by side with Activity timeline as two independent columns — not a standalone card, and not collapsed by default in the old accordion sense, since there's nothing to expand: both columns are visible together as soon as you open the tab, each scrolling within its own bounded area.
Timeline
Replied: "Re: Quick question…"
Clicked a link in an email
Campaign history
Q3 warehouse automation outreach
ACTIVESpring re-engagement
PAUSEDEvery campaign this contact is enrolled in, with the campaign name and its raw status badge (ACTIVE, PAUSED, DRAFT, or ARCHIVED) — a read-only list, with no click-through from a row itself.
Editing and the manual-edit rule
Every edit here goes through the same click-to-edit control the All Contacts grid uses — click, edit, save on blur or Enter, revert on Escape or on a failed save. Saving marks each field you touched as manually edited, timestamped — the same rule the All Contacts grid already follows: a human's own edit always wins. A later automated write to that same field, from a fresh CSV import or a new enrichment run, won't silently overwrite what you typed here. Deliberately clearing a field to blank bypasses that protection, since a human clearing something on purpose isn't the scenario the rule exists to guard against.
Where tagging, deleting, and list actions live
Tagging, deleting, and adding to a list or campaign all live on the All Contacts grid's own bulk-selection bar instead of here — none of the three has its own control on this single-contact page yet.