Skip to content
This repository was archived by the owner on Jul 15, 2026. It is now read-only.
This repository was archived by the owner on Jul 15, 2026. It is now read-only.

Epic: Cross-provider contact merging & de-duplication (with settings) #8

Description

@joestump-agent

Context / Problem

msgbrowse ingests contacts from multiple providers (Signal, iMessage, WhatsApp, and later Telegram). Today contacts and their identifiers live in the contacts + contact_identifiers tables (internal/store/schema.go; get-or-create linking in internal/store/store.go ~lines 180–194; per-source delete/cleanup in internal/store/sources.go). Linking across sources is implicit and there is no user-facing control over how the same real person's identities from different providers are merged or kept separate.

This epic introduces user-controlled cross-provider contact merging and de-duplication, backed by a pluggable address-book / contact-resolver abstraction so platform-specific integrations (macOS Contacts) can be swapped for a no-op on Linux.

Goals

  • Settings that control how contacts are merged across all providers (identifier matching rules, manual merge/split, overrides).
  • On iOS/macOS, reference the native Contacts app to help map identifiers to real people and de-duplicate. This is platform-specific and must not be a hard dependency elsewhere.
  • Because the Contacts integration is Mac-only, a pluggable/adaptable interface (ContactResolver / address-book provider) so Linux (and other platforms) supply a no-op or alternative implementation.

Design direction

  • Mirror the existing interface-injection seams in internal/web: SetDetector / SetEnabler / SetPairingSource with their Enabler / PairingSource interfaces (internal/web/enable.go, internal/web/settings.go), and the detector patterns in internal/setup. The desktop shell wires the real (macOS) implementation; tests and Linux wire fakes / no-ops.
  • Persistence builds on contacts + contact_identifiers; a merge likely needs a stable "canonical person" grouping plus manual override records that survive re-ingest.
  • Suggest a paired spec under docs/openspec/specs/contact-merge/ (spec.md + design.md) and an ADR (MADR) under docs/adr/ (next number is 0022). Related prior art: ADR-0011 (contact-facts-extraction), docs/openspec/specs/contact-facts.

Child issues

  • Pluggable ContactResolver / address-book interface + Linux no-op impl
  • macOS Contacts provider implementation
  • Cross-provider merge / de-dup engine (identifier matching, manual merge/split, persistence)
  • Settings UI for merge rules and manual overrides
  • ADR: contact-merging architecture & platform abstraction

(Child issues are also attached as GitHub sub-issues; the checklist above mirrors them.)

Relevant code

  • internal/store/schema.go (contacts, contact_identifiers), internal/store/store.go (get-or-create linking), internal/store/sources.go (per-source cleanup/merge)
  • internal/web/enable.go, internal/web/settings.go (seam patterns: Enabler, PairingSource, SetEnabler, SetPairingSource)
  • internal/setup/ (detector patterns)
  • internal/facts/ and ADR-0011 (adjacent contact work)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions