Skip to content

Contact + AI Facts profile page (redesign Phase 1, SPEC-0017)#215

Open
joestump wants to merge 1 commit into
mainfrom
feature/contacts-ai-facts
Open

Contact + AI Facts profile page (redesign Phase 1, SPEC-0017)#215
joestump wants to merge 1 commit into
mainfrom
feature/contacts-ai-facts

Conversation

@joestump

Copy link
Copy Markdown
Owner

Summary

Phase 1 of the redesign (design brief-2): a per-person Contact + AI Facts profile at GET /contact/{id}, reached from the transcript header (the name links to the person's profile). Implements SPEC-0017 (docs/openspec/specs/contact-profile/).

Reassuringly, the data already existed — this is largely a new page over the current schema, no new tables.

What it shows

  • Merged identity — Signal + iMessage handles in one view, "Same person across N sources · merged" (degrades to a single-source label when unmerged), full identifier-chip set, source presence dot.
  • Message-volume sparkline — per-month store data rolled up to years, gap-filled across the full span (a silent year is an empty labelled slot, not a collapsed axis); CSP-safe inline SVG (presentational attributes only).
  • AI-gathered facts — grouped by category in the declared facts.Categories order, each cited fact deep-linking to its own source conversation (/c/{convID}/at/{msgID}); a gone message renders undated/link-less. Fact text is html/template-escaped.
  • Derivable Profile stat tiles — total · you sent · received · photos · messages/day · most-active hour (UTC) · first message. Plus top reactions.

Implementation notes

  • Keyed by contact_id (the merged-person grain). Load-bearing predicate: conversation_id IN (SELECT id FROM conversations WHERE contact_id = ?).
  • GetContactByID is identity-only; ContactStats owns the counts, so a page render does a single message scan (not two).
  • New: internal/store/contacts.go, internal/web/contact.go + contact.html. ContactFact gained SourceConversationID; ConversationSummary gained ContactID (drives the header link).
  • Deferred non-goals (spec'd, with the future message_sentiment-style NLP-table plug-in point): sentiment-over-time, shared vocabulary, activity-rhythm heatmap, median reply time, per-day mood.

Review + verification

  • Went through a high-effort multi-agent /code-review; it caught (and this PR fixes) a zero-conversation → HTTP 500 template crash, a sparkline gap-fill bug, a label-overlap bug, and a redundant double message scan — each now fixed with a regression test.
  • Store + web suites, go vet, gofmt, and the CSP no-inline-style guard are green. Verified live against the fixture archive.

Note

The mockup's fact categories (Family/Location/Channels/…) differ from the existing facts taxonomy (personal/work/relationships/…); this renders the real stored categories — changing the extraction taxonomy is a separate decision.

Part of SPEC-0017

🤖 Posted on behalf of @joestump by Claude.

New GET /contact/{id} per-person profile (SPEC-0017), reached from the transcript
header: merged Signal+iMessage identity + identifier chips, a gap-filled
message-volume sparkline, derivable stat tiles (total / sent / received / photos
/ pace / most-active hour / first message), top reactions, and AI-gathered facts
grouped by category — each cited fact deep-linking to its OWN source conversation.

All aggregates are keyed by contact id (the merged-person grain) over the
existing schema (contacts / contact_identifiers / contact_facts / messages /
reactions / attachments) — no new tables. GetContactByID is identity-only;
ContactStats owns the counts (single scan). The sparkline is CSP-safe inline SVG
(presentational attributes only) and gap-fills silent years. Fact text is
html/template-escaped. UTC bucketing; ts ordering never uses MIN/MAX(ts) string
aggregation. A contact with zero conversations still renders (no template crash).

- store: internal/store/contacts.go + ContactFact.SourceConversationID +
  ConversationSummary.ContactID (drives the header link)
- web: /contact/{id} handler + contact.html; the transcript header name links to
  the profile when the conversation is linked to a contact
- deferred non-goals (spec'd): sentiment-over-time, shared vocabulary,
  activity-rhythm heatmap, median reply time, per-day mood
- docs: SPEC-0017 contact-profile spec pair

Tests cover merged aggregates, sent/received split, fact source resolution
(incl. gone-message id 0), full/partial render, 404, fact escaping, and the
zero-conversation contact regression.

Part of SPEC-0017

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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