You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Merge duplicate contacts #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 an occ command (see follow-ups).
Merge and tidy-up contacts feature #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.
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 Merge and tidy-up contacts feature #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/vue v9); it currently applies,
builds, npm testes and lints cleanly against the latest main — 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 (NcCheckboxRadioSwitch vs. 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 Export a list with contacts #764 (and is relevant to Export to other file formats than .vcf, e.g. XML #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)
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.
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/vuev9) on Nextcloud 33. I'dlike 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:
scan all your contacts and consolidate them" → addressable later as an
occcommand (see follow-ups).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.
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:
name) → interactive editor only, never auto-merged.
review only, low confidence.
The view has group-scope and free-text filters (search across name/email/phone/address) so large books
stay navigable.
Key safety mechanisms:
to the opt-in, review-only homonym finder — never auto-merged, never in "Merge all".
(the CardDAV validator rejects >1). Repeated ORG/TITLE within a card are surfaced (addresses Merge and tidy-up contacts feature #1088).
contradicting birthday drops it out of the auto-merge band).
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 userguide.
Status
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.)
tested with no dependency on the Contact model, and the change is split into small dependent PRs
(below) rather than one large drop.
is how the real-world edge cases (and a couple of Vue-3-runtime bugs) were found and fixed.
idioms — but I'd genuinely welcome your review and will follow your lead on design and conventions.
convention.
Questions for maintainers
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)?
initial contribution, or would you rather it land as a separate later PR?
the dense merge editor (
NcCheckboxRadioSwitchvs. styled native inputs with radiogroup semantics forthe value-picker matrix)?
alongside this; it addresses the long-standing request Export a list with contacts #764 (and is relevant to Export to other file formats than .vcf, e.g. XML #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)Each PR is cut as a build- and test-gated commit (every commit DCO
Signed-off-by) so the tree buildsand 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)
occ contacts:dedupcommand for headless scan/merge (explicitly requested in Merge duplicate contacts #70).