# Design sweep — Contacts group

Files: `routes/Contacts.tsx` (692 lines), `routes/ContactDetail.tsx` (378 lines).
Reached via `App.tsx`: `/contacts` → `Contacts`, `/contacts/:id` → `ContactDetail`,
and `/segments` redirects into `/contacts?tab=segments` (Segments folded in as a
tab, channels-scope-3 ruling — role-gated to marketing+).

LEGACY_BASELINE markers: `routes/Contacts.tsx` = **30**, `routes/ContactDetail.tsx`
= **23**. Both entirely `--cm-*` tokens for color (no raw hex/legacy-var hits) —
the debt is `legacy-class` (`so-btn*` — 8 uses in Contacts, 6 in ContactDetail)
and `off-scale-type` (rem-based `fontSize` in inline style objects — Contacts has
~20 const style objects, ContactDetail ~13). Raw form-control count: Contacts has
4 `<input>` (search + 3 checkboxes incl. per-row) and 5 `<select>`; ContactDetail
has 3 `<input>` (edit form only, no `<select>`).

---

## Contacts.tsx (list)

- **IA/concept count** — high for one screen: 2 tabs (Contacts/Segments), 3
  header actions (Export/Import/New), 7 independent filter controls (search,
  channel, tag, consent, activity, block state, include-outbound), a 7-column
  table with row+select-all checkboxes, and a floating bulk-action bar that
  appears on selection. A first-time user holds 4+ simultaneous concepts before
  reading a single row.
- **Interaction** — no dead ends; checkboxes and tab strip carry proper
  `aria-label`/`role`. Select-all is page-scoped only with no indicator that
  other pages aren't selected (silent partial-selection trap on multi-page
  bulk sends). Bulk bar is `position: fixed` bottom-center, `minWidth: 360` —
  untested against 320px viewports, risks clipping/overlap with content below.
- **Content/copy** — clean, plain English throughout ("No contacts match those
  filters", "Include outbound"); no engineering vocabulary leaks to the UI.
- **Visual/tokens** — 30 markers, all `so-btn*` classes + rem `fontSize`
  literals in ~20 inline style consts; colors already route through `--cm-*`.
  Structurally an inline-style architecture, not CSS classes — heavier to
  re-theme than a swept screen even though the tokens underneath are correct.
- **A11y+responsive** — table wrapped in `cm-table-scroll` (good, matches
  house convention); filter bar wraps via flexbox. The fixed-position
  selection bar is the one concrete responsive risk on this screen.

**VERDICT: redesign.** The screen works and is already token-clean, but 7
flat filter controls + 2 tabs + a floating bulk bar is more surface than a
daily-use directory needs to expose at once — this is IA overload, not a
token-paint job, and it's the highest-traffic screen in the group.

**FORKS**
1. The 7 filter controls sit flat in one bar today — collapse most into a
   single "Filters" popover/drawer (keep search + channel inline), or keep
   all visible for one-click access? *Recommend: collapse to a popover;
   search+channel stay inline.*
2. The bulk-action bar floats fixed at the viewport bottom — move it to an
   inline toolbar that appears above the table instead (safer at narrow
   widths, no z-index/overlap risk)? *Recommend: inline toolbar.*
3. Segments is folded into Contacts as a tab (channels-scope-3 ruling) — is
   that boundary open for revisit in this sweep, or held as settled?
   *Recommend: held settled — it's a ledgered decision, not a fresh call.*

---

## ContactDetail.tsx (profile)

- **IA/concept count** — modest and well-scoped: header (name, badges,
  3 actions), an inline edit form, a label/value profile card, and a
  separate `ConsentPanel`. No tab/mode sprawl.
- **Interaction** — Edit toggles an inline form; Block routes through
  `ConfirmDialog` with contact-specific copy — good pattern, no dead ends.
  Tag editing is a raw comma-separated `<input>` ("vip, dubai") that the
  operator must hand-punctuate, inconsistent with how tags render everywhere
  else (`TagChips` pills) and on the Contacts list itself.
- **Content/copy** — mostly plain ("Unblock this contact?", "Never" for empty
  activity dates). One real leak: the Attributes block renders raw
  `key: value` pairs straight from the data (`{k} {String(v)}`) with no
  humanization — whatever key an integration/import wrote is what the
  operator sees verbatim.
- **Visual/tokens** — 23 markers, same shape as Contacts: `so-btn*` classes
  (Edit/Block/Open conversation/Save/Cancel) + rem `fontSize` in ~13 inline
  style consts. No raw hex/legacy-var debt.
- **A11y+responsive** — edit-form fields flex-wrap and the profile grid uses
  `minmax(120px, max-content) 1fr` with `wordBreak: break-word` — survives
  narrow widths reasonably. No focus-visible issues spotted in this file
  (relies on the shared `so-btn`/input default focus ring).

**VERDICT: polish.** Structure is sound and already reads as one surface
(header → edit → profile card → consent); the fixes are surface-level —
tag input, attribute display, and the same token/class cleanup as Contacts —
not a rebuild of the screen's shape.

**FORKS**
1. Tag editing is a raw comma-separated text field — replace with a proper
   chip/token input (matching `TagChips` display), or leave as a plain text
   field since edits are rare? *Recommend: chip input, small lift, removes
   the one raw-punctuation UX in the group.*
2. The Attributes block shows raw, unhumanized keys — is that intentional
   passthrough (operator-defined custom fields, don't guess a label), or
   should keys get at least underscore→Title Case formatting?
   *Recommend: format the key, leave the value raw.*
