Skip to content

fix: resolve recents number collisions to the external contact (WT-1024)#1371

Merged
SERDUN merged 1 commit into
developfrom
fix/recents-contact-collision-order
Jun 9, 2026
Merged

fix: resolve recents number collisions to the external contact (WT-1024)#1371
SERDUN merged 1 commit into
developfrom
fix/recents-contact-collision-order

Conversation

@SERDUN

@SERDUN SERDUN commented Jun 9, 2026

Copy link
Copy Markdown
Member

Overview

Fixes the WT-1024 surface: in the Recents list (and the contact opened from it via
"View Contact"), a phone number shared by a local device contact and an external
(PBX) contact resolved to an arbitrary source, so the list/contact card could show
a different identity than the call screen - read as mixed name and avatar.

recents_dao joined the call-log number to a contact with no source tie-break, so
the winner was left to SQLite's unspecified row order (the first row kept by
_rowsToRecent). This builds on the centralized policy from #1369.

Changes

  • watchLastRecents and getRecentByCallId now order the joined rows by
    contactsTable.sourcePriorityOrder() (external/PBX first), so _rowsToRecent
    keeps the external contact deterministically.
  • watchLastRecents keeps createdAt/hungUpAt as the primary ordering and adds
    the source priority only as a per-call-log tie-break. Note: orderBy on a joined
    statement replaces the term list (it does not append), so the full ordering is
    passed explicitly - otherwise the newest-first list order would be lost.
  • No UI change: the tile and "View Contact" already read everything from the single
    resolved contact, so a deterministic pick fixes both at once.

Testing

  • New recents_dao_test.dart: external wins on collision for both
    watchLastRecents and getRecentByCallId, both insertion orders; the list keeps
    newest-first order; non-colliding numbers resolve to their single contact.
  • Full app_database suite green (281 tests); dart analyze clean.

Follow-up

The same collision exists in voicemail (voicemail_dao), which additionally
duplicates a voicemail row on a sender collision. Tracked separately; not part of
this PR.

The recents list and the contact opened from it ("View Contact") joined a
call-log number to a contact with no source tie-break, so when a number was
shared by a local and an external (PBX) contact the winner was left to SQLite's
unspecified row order (the first row kept by _rowsToRecent). That let the list
and the opened contact card show a different source than the call screen, which
reads as mixed name/avatar.

watchLastRecents and getRecentByCallId now order the joined rows by
contactsTable.sourcePriorityOrder() so the external contact is kept
deterministically. watchLastRecents keeps createdAt/hungUpAt as the primary
ordering (orderBy on a joined statement replaces the term list, so the full list
is passed) and applies the source priority only as a per-call-log tie-break.

Covered by recents_dao_test (both insertion orders, list order preserved,
non-colliding numbers unaffected).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Resolves WT-1024 by making Recents contact resolution deterministic when a call-log number matches both a local device contact and an external (PBX) contact, ensuring the external contact consistently wins.

Changes:

  • Add source-priority ordering to the Recents contact join to deterministically pick the external contact on number collisions.
  • Preserve recents newest-first ordering while applying source priority only as a per-call-log tie-break.
  • Add DAO tests covering collision resolution (both insertion orders), list ordering, and non-collision behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/data/app_database/lib/src/daos/recents_dao.dart Adds source-priority ordering to joined recents queries to resolve local-vs-external collisions deterministically.
packages/data/app_database/test/recents_dao_test.dart Introduces regression tests validating deterministic external-first behavior and preserving list order.

final callsQuery = select(callLogsTable)..where((t) => t.createdAt.isBiggerOrEqualValue(clock.agoBy(period)));

final sourcePhone = alias(contactPhonesTable, 'source_phone');
final contactPhones = alias(contactPhonesTable, 'contact_phones');

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, but this is pre-existing and out of scope for this PR. The change here only adds the source-priority ordering for the resolved contact (contactData); it does not touch the contact-phones plumbing.

The alias vs non-aliased mismatch (the joined contact_phones alias is unused by _rowsToRecent, which reads the base contactPhonesTable, and the presence join is also on the base table) is already on develop. I verified empirically that recents contact phones still come back populated and the read does not throw - a recent for a contact with phone "7" returns phones=[7] - because drift pulls the base table in via the presence ON-clause. So nothing is missing in the common case; the latent risk is a cross-join when a contact has multiple phones.

Aligning recents on the alias (like favorites_dao) also affects getRecentByCallId, which joins the base contactPhonesTable, and their shared _rowsToRecent, so I'm tracking it as a separate cleanup rather than widening this PR.

@SERDUN SERDUN merged commit afd1268 into develop Jun 9, 2026
5 checks passed
@SERDUN SERDUN deleted the fix/recents-contact-collision-order branch June 19, 2026 11:35
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.

2 participants