Skip to content

Contacts CRM + tapback/empty-stub reliability fixes#53

Open
clickclacknvrwhack wants to merge 3 commits into
MaxGhenis:mainfrom
clickclacknvrwhack:feat/contacts-crm-reliability
Open

Contacts CRM + tapback/empty-stub reliability fixes#53
clickclacknvrwhack wants to merge 3 commits into
MaxGhenis:mainfrom
clickclacknvrwhack:feat/contacts-crm-reliability

Conversation

@clickclacknvrwhack

Copy link
Copy Markdown
Contributor

The second PR from the #40 split, per review. Rebased on current main. Companion to #52 (schedule-send).

Scoped deliberately to the cleanly-novel pieces; see "Deferred" below for what was intentionally left out.

Features

Contacts CRM — a person icon in the header opens a full-screen Contacts overlay:

  • Left: searchable people list, sortable by Recent / A–Z / Most messages, aggregating 1:1 messages per person across platforms with real message counts.
  • Right: detail pane with avatar, contact info across platforms, last-contacted, editable tags, a reach-out reminder cadence, a generated relationship summary, and "Open conversations" to jump to the thread.
  • Backend: people.go (aggregation), contact_meta.go (tags/cadence/cached summary), story/summary.go (local summary + system-message filtering), and GET /api/people / GET /api/people/{key} / POST /api/people/{key}/{tags,reach-out,summary}.

Reliability fixes

  • iMessage tapbacks → reactions: Loved "see you then" texts from iPhones become an emoji reaction on the referenced message instead of a standalone row. Guarded against false positives: a non-truncated quote must match a message in full (only iMessage's ellipsis-truncated quotes match by prefix), so a user who literally types Loved "hi" doesn't silently lose the message.
  • Empty stub messages: contentless placeholder rows (no body/media/reactions) are detected and dropped instead of rendering as "Empty message" and wrongly surfacing a thread. Live-path gate + startup repair.

Review items addressed

  • Method-gate fix: /api/people/{key}/{tags,reach-out,summary} now require POST (405 otherwise), matching the sibling routes — previously they mutated on any verb including GET.
  • Tapback false-positive guard: the exact-vs-truncated matching above (with a test).
  • Title scope: "inbox tabs" dropped (already in main via Inbox tabs, reaction reactor names, and recents-ordering fix #24).

Deferred (intentionally, to keep this focused)

  • Unread fix: main already fixes stale-unread-resurrection via Harden HTTP API; fix stale-snapshot unread clobber; contacts query pushdown #31 (ApplyConversationSnapshot). To honor the "don't ship two mechanisms" requirement, this PR does not include our last_read_ts watermark. Happy to send it as a separate focused PR if you'd prefer the watermark approach (which guards all callers, not just the two snapshot sites).
  • Group-create (two-step CREATE_RCS) and the keyed-reconcile render fix: both entangle with main's diverged composer/render; better as their own PRs.

Verification

  • Full go test ./internal/... ./cmd/ passes; staticcheck clean on changed packages (no dead code).
  • CRM overlay verified end-to-end on the demo: open → list/sort → select person → detail (contact info, tags, reach-out, generated summary, open-conversation).

🤖 Generated with Claude Code

clickclacknvrwhack and others added 3 commits June 19, 2026 11:42
A relationship view over everyone you've messaged.

- Store: people.go aggregates 1:1 messages by normalized person (across
  platforms) with real message counts; contact_meta.go holds per-person
  tags, reach-out cadence, and a cached relationship summary (contact_meta
  table). story/summary.go generates a local relationship summary and
  filters system/RCS-banner messages ("No communication" when empty).
- API: GET /api/people (list) and /api/people/{key} (detail), plus
  POST sub-actions /tags, /reach-out, /summary. The mutating sub-actions
  now require POST (405 otherwise), matching the sibling routes — previously
  they mutated on any verb, including GET.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two live-path fixes plus startup repairs:

- tapback.go: iMessage tapbacks (`Loved "see you then"`) arrive from
  iPhones as plain SMS/RCS text. Convert them into an emoji reaction on the
  message they refer to instead of storing a standalone row. Guarded against
  false positives: a non-truncated quote must match a message in full (only
  iMessage's ellipsis-truncated quotes match by prefix), so a user who types
  `Loved "hi"` doesn't silently lose the message to a prefix match.
- stub.go: detect and drop empty placeholder rows (no body/media/reactions
  + terminal status) that would otherwise render as "Empty message" and
  wrongly surface a conversation in recents.
- events.go / backfill.go gate incoming messages through both; app.go runs
  RepairTapbacks + RepairEmptyStubMessages on startup for existing data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A person icon in the header opens a full-screen Contacts overlay: a
searchable, sortable people list (Recent / A–Z / Most messages) on the
left, and a detail pane on the right showing the avatar, contact info
across platforms, last-contacted, editable tags, a reach-out reminder
cadence, and a generated relationship summary. "Open conversations"
jumps to that person's thread. Consumes the /api/people endpoints.

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