nextcloud / nextcloud/contacts
RFC: Homonym-safe duplicate detection & merge for Contacts
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 642
- Forks
- 220
- Avg merge
- 14h 39m
- Merged PRs (30d)
- 51
Description
Summary
I'd like to contribute a duplicate-detection and merge feature to Contacts — the capability tracked
by #70 (open since 2016), #1088, #2757 and #286. It's built, and used daily on a real
~11,000-contact address book running the current app (Vue 3 / @nextcloud/vue v9) on Nextcloud 33. I'd
like to upstream it as a series of small, focused PRs — but I want your input on design and scope first.
The guiding principle throughout is safety: it must never merge two different people on its own, and
never lose contact data. Every merge is undoable.
Problem
Address books accumulate duplicates — the same person imported twice, synced from two accounts, or entered
by two people. Contacts has no built-in way to find and merge them. This is a long-standing, much-requested
gap:
- #70 "Merge duplicate contacts" — the canonical request (had a bounty). Also asks for "a command to
scan all your contacts and consolidate them" → addressable later as anocccommand (see follow-ups). - #1088 "Merge and tidy-up contacts feature" — describes almost exactly this proposal: mark contacts as
duplicate and merge, scan for possible duplicates and ask what to do, and detect the same field (ORG,
TITLE) defined multiple times in one contact. - #2757 "Add the possibility to delete duplicated contacts", #286 (similar entries as duplicates).
The risk with any auto-merge is fusing two different people (homonyms) or silently dropping data — so
safety has to be designed in, not bolted on.
Proposed design
A Duplicates view that sorts clusters into clearly-separated lanes by safety posture:
- Duplicates to merge — share an exact email/phone and compatible names → safe to auto-merge.
- Needs your choice — confident duplicate but members disagree on a single-valued field (usually the
name) → interactive editor only, never auto-merged. - Entries to review — share a contact point but names are incompatible (a shared family mailbox) →
review only, low confidence. - Possible homonyms (opt-in) — share a name but no email/phone → review-only, never bulk-merged.
- Kept separate / Incomplete — housekeeping lists.
The view has group-scope and free-text filters (search across name/email/phone/address) so large books
stay navigable.
Key safety mechanisms:
- Exact-key grouping only for auto-detection (shared email/phone); fuzzy/name-only matching is confined
to the opt-in, review-only homonym finder — never auto-merged, never in "Merge all". - Data-preserving union merge: multi-valued fields unioned, single-valued gap-filled, exactly one FN
(the CardDAV validator rejects >1). Repeated ORG/TITLE within a card are surfaced (addresses #1088). - Persist-before-delete, session undo/redo, and a source is deleted only if it can be re-created.
- A confidence score modelled on human judgement (identical name + shared contact → near-certain; a
contradicting birthday drops it out of the auto-merge band). - An interactive N-way editor for full curation: pick which name survives, keep/discard each value,
and include/exclude which contacts are actually the same person.
Architecture and the data-safety invariants are documented in docs/duplicate-detection.md, plus a user
guide.
Status
- Low integration cost. Implemented for
main(Vue 3 /@nextcloud/vuev9); it currently applies,
builds,npm testes and lints cleanly against the latestmain— no conflicts, ~200 unit tests green,
ESLint 0/0 on the feature files. (Naturally I'll adapt it to whatever you'd like changed.) - Reviewable. The pure logic (detection, merge, confidence, homonym finder, store, search) is unit-
tested with no dependency on the Contact model, and the change is split into small dependent PRs
(below) rather than one large drop. - Battle-tested, not a prototype. It's been in daily use on an ~11k-contact address book — which
is how the real-world edge cases (and a couple of Vue-3-runtime bugs) were found and fixed. - I've put real effort into correctness, data-safety, accessibility, i18n and matching the project's
idioms — but I'd genuinely welcome your review and will follow your lead on design and conventions. - SPDX/REUSE headers on new files; strings are single translatable units; deep imports follow the v9
convention.
Questions for maintainers
- Would you accept this feature in principle? If so, as a series of focused PRs (see below)?
- Client-side vs server-side merge. It currently merges client-side (CardDAV writes) to stay
self-contained and require no server changes. Would you prefer a server-side merge endpoint, or is
client-side acceptable for a first version (with a server endpoint as a follow-up)? - Scope of a first version. Is the opt-in homonym finder (name-only, review-only) welcome in the
initial contribution, or would you rather it land as a separate later PR? - UX conventions. Any preferences on the entry point (nav item vs. an action), and on the component for
the dense merge editor (NcCheckboxRadioSwitchvs. styled native inputs with radiogroup semantics for
the value-picker matrix)? - CSV export — an independent, generic CSV export (configurable columns/delimiter/encoding) that grew
alongside this; it addresses the long-standing request #764 (and is relevant to #1621, which
asks for export formats beyond.vcf). In scope here, or better as its own unrelated PR?
Proposed PR breakdown (details in docs/contribution/pr-split-plan.md)
- Detection + lanes + filters + data-preserving auto-merge + undo/redo (the foundation).
- The interactive N-way merge editor.
- The opt-in homonym finder.
- (Optional/independent) CSV export.
Each PR is cut as a build- and test-gated commit (every commit DCO Signed-off-by) so the tree builds
and tests green at every step. Happy to adjust the split, scope, and design to fit how you'd like this to
land.
Possible follow-ups (not in the initial PRs)
- An
occ contacts:dedupcommand for headless scan/merge (explicitly requested in #70). - A server-side merge endpoint, if preferred over client-side CardDAV writes.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with docs/duplicate-detection.md and docs/contribution/pr-split-plan.md to understand the proposed architecture and staged work. This is an RFC, so wait for maintainer decisions on client-side integration, homonym detection, editor scope, and CSV export before choosing an implementation entry point. Done means the agreed feature slice is implemented in focused, build- and test-gated PRs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 28/100