Skip to content

feat(contacts): Contacts & Org Charts (CO) — offices, affiliations, suggestions inbox#19

Merged
itkujo merged 1 commit into
mainfrom
feat/contacts-org-charts
Jul 11, 2026
Merged

feat(contacts): Contacts & Org Charts (CO) — offices, affiliations, suggestions inbox#19
itkujo merged 1 commit into
mainfrom
feat/contacts-org-charts

Conversation

@itkujo

@itkujo itkujo commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Contacts & Org Charts (CO) — a standalone pre-launch phase

A consultancy needs to know who holds which office at every client and partner (VA, etc.) and how to reach them. This adds a top-level Contacts area + a per-org office hierarchy (the org chart), plus a source-agnostic suggestions inbox fed from meeting attendees.

"Org" = a clients row of any type (client / prospect / lead / partner / internal, from P4.1).

What's in it

  • Offices are first-class — per-org, with a reports-to parent (the org chart), and can be VACANT so "we're missing the VA CIO" is a real, flaggable state (is_key + amber key-vacancy highlight).
  • Contacts are people; they fill offices via affiliations that carry tenure history and allow multiple orgs (see someone move VA → a client over time; the prior tenure is preserved).
  • Full visual org chart — per org, a reports-to tree; each node shows the current holder or "Vacant · add contact".
  • Suggestions from meeting attendees — a nightly worker sweep scans meetings.external_attendees (P4.1) and upserts pending contact_suggestions, guessing the org by domain. Surfaced in the Contacts area (not the calendar). Built source-agnostic so a future n8n web-scan feeds the same inbox (source='n8n_web').
  • Export — per-contact CSV + vCard (mobile-friendly downloads) and an org-wide CSV.

Data model — migration 0008_contacts_org_charts.sql (additive + idempotent)

contacts, offices (reports-to parent, is_key, can be vacant), contact_affiliations (tenure + multi-org; a partial unique index enforces at most one current holder per office), contact_suggestions (source-agnostic queue with a pending-dedup index).

⚠️ Migration not yet applied. It hits the shared dev+prod Supabase — per the brief it must be applied only in coordination with the orchestrator. The DB types were hand-regenerated (no supabase CLI in this env) so the code green-gates without the tables existing yet. Apply 0008 before this merges/deploys.

Backend

  • apps/web/lib/data/contacts.ts — contacts / offices / affiliations / suggestions + CSV/vCard builders. setOfficeHolder encapsulates the end-prior-holder invariant (history preserved); buildOfficeTree builds the reports-to tree; office reparenting guards self-reference + cycles.
  • REST routes under /api/contacts/**, /api/clients/[id]/{offices,org-chart,contacts/export}, /api/contact-suggestions/**.
  • Worker: contact-suggestions.processor.ts (nightly sweep) + queue, wired into index.ts. Idempotent — skips existing contacts, pending/dismissed suggestions, and free-email domains.

Web UI — /contacts (three sections)

  1. All contacts — searchable/org-filterable list → profile drawer (details + inline edit, affiliation history grouped current/past, add/end affiliation, delete, CSV + vCard download).
  2. Org charts — org picker → visual reports-to tree with vacant nodes + key-vacancy flags; add/edit/delete office, fill/vacate holder; org-wide CSV export.
  3. Suggestions (editor-only) — pending inbox; Accept (→ contact + optional affiliation) / Dismiss (won't resurface).

Permissions

Adds contacts.view (all roles) + contacts.edit (editor tier) to PERMISSIONS + PERMISSION_MATRIX. Nav item + write routes gated accordingly. Viewer is strictly read-only. Bot kill-switch untouched (stays OFF).

Green gate

pnpm -w typecheck ✅ · pnpm -w lint ✅ · pnpm --filter web build ✅ (only the pre-existing unpdf import-trace warning).

Not in scope (per brief)

The n8n automation itself (only the queue hook), bulk CSV import, drag-and-drop org-chart reorg, migrating clients.primary_contact*. The per-org client-detail Contacts sub-tab is left as a cheap fast-follow (components already exist).

🤖 Generated with Claude Code

…ons, suggestions inbox)

A standalone pre-launch capability: a top-level Contacts area plus a per-org
office hierarchy (the org chart), fed by a source-agnostic suggestions inbox.

Data model (migration 0008, additive + idempotent — NOT yet applied to the
shared Supabase; pending orchestrator coordination):
  * contacts             — people (org-agnostic; linked via affiliations)
  * offices              — first-class org-chart nodes (reports-to parent; can be VACANT)
  * contact_affiliations — contact ↔ org (+ optional office) with tenure history + multi-org
  * contact_suggestions  — source-agnostic inbox (calendar attendees now, n8n web-scan later)
Hand-added the four tables to database.types.ts (no supabase CLI available) and
added domain types in @gracie/shared (types/contact.ts).

Backend:
  * apps/web/lib/data/contacts.ts — contacts/offices/affiliations/suggestions + CSV/vCard export;
    setOfficeHolder encapsulates the "end prior holder" invariant; buildOfficeTree.
  * REST routes under /api/contacts/**, /api/clients/[id]/{offices,org-chart,contacts/export},
    /api/contact-suggestions/** — read = contacts.view (all roles), writes = editor (contacts.edit).
  * Worker: nightly contact-suggestions sweep scans meetings.external_attendees (P4.1) and
    upserts pending suggestions, guessing the org by domain; skips existing contacts,
    pending/dismissed suggestions, and free-email. Idempotent.

Web UI (/contacts, three sections):
  * All contacts — searchable list + profile drawer (details, affiliation history, add/end
    affiliation, delete, CSV + vCard download).
  * Org charts — org picker → visual reports-to office tree with VACANT nodes and key-vacancy
    flags; add/edit/delete office, fill/vacate holder; org-wide CSV export.
  * Suggestions — pending inbox with Accept (→ contact + optional affiliation) / Dismiss.

Permissions: adds contacts.view (all) + contacts.edit (editor) to PERMISSIONS + PERMISSION_MATRIX.
Viewer is strictly read-only. Bot kill-switch untouched (stays OFF).

Green gate: pnpm -w typecheck && pnpm -w lint && pnpm --filter web build all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@itkujo itkujo merged commit df0e653 into main Jul 11, 2026
@itkujo itkujo deleted the feat/contacts-org-charts branch July 11, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant