LiveCommunicationKit: Handle.displayName is never shown — the call UI and Recents show Handle.value

On iOS [27.x (build)], LiveCommunicationKit shows Handle.value on every system surface and ignores Handle.displayName. That forces a choice that CallKit doesn't:

  • Put a stable account id in value (as WWDC26 session 226 recommends, so Recents redial can identify the person), and the raw id appears on the incoming call screen, the banner and in Recents.
  • Put the person's name in value so the UI reads correctly, and a redial from Recents hands back only the name, which can't reliably identify anyone.

Minimal reproduction

let configuration = ConversationManager.Configuration(
    ringtoneName: nil,
    iconTemplateImageData: nil,
    maximumConversationGroups: 1,
    maximumConversationsPerConversationGroup: 1,
    includesConversationInRecents: true,
    supportsVideo: false,
    supportedHandleTypes: [.generic, .phoneNumber, .emailAddress]
)
let manager = ConversationManager(configuration: configuration)

let remote = Handle(type: .generic, value: "u-1234", displayName: "Jane Appleseed")
try await manager.reportNewIncomingConversation(
    uuid: UUID(),
    update: Conversation.Update(members: [remote], capabilities: [])
)

Expected: the incoming call UI and the Recents row show "Jane Appleseed". Session 226 says displayName is "what the system shows when it can't match the handle to a contact". Tapping the Recents row delivers an INStartCallIntent whose contact personHandle.value is u-1234.

Actual: the full-screen ring, the foreground banner and the Recents row all show u-1234. Tapping the row does deliver INStartCallIntent with personHandle.value == "u-1234", so redial works, but the user never sees the name.

What we tried, all with the same result (the value is shown):

  • Handle.Kind set to .generic, .phoneNumber or .emailAddress, with every kind listed in supportedHandleTypes
  • Conversation.Update(localMember:) set to the local user's handle
  • activeRemoteMembers set to the remote handle, and localMember plus activeRemoteMembers together
  • Donating an INInteraction (an INStartCallIntent whose INPerson has personHandle set to the id, displayName set to the name and customIdentifier set to the id) when the conversation ends. The donation succeeds, but the Recents row and the redial payload don't change.

CallKit comparison, same device: CXCallUpdate.remoteHandle = CXHandle(type: .generic, value: "u-1234") with localizedCallerName = "Jane Appleseed" shows the name on the ring and in Recents. A redial from Recents delivers personHandle.value == "u-1234". That's the behaviour we expected from LiveCommunicationKit.

Questions:

  1. Is Handle.displayName meant to be shown when the handle doesn't match a contact? If so, is there a configuration step we're missing?
  2. If this is a bug, is there a supported way with LiveCommunicationKit to show a name while keeping a stable identifier for Recents redial?

Filed as FB24933340. Device: iPhone 15 pro, iOS [27.0.1].

Update: more testing since posting (iPhone 15 Pro, iOS 27.0.1, FB24933340).

1. The production configuration behaves the same. The repro above lists all three kinds in supportedHandleTypes. With [.generic] only (what we ship), value is still shown on the ring, the banner and in Recents.

2. Donation is ruled out for later calls too. After a successful donation (id in personHandle and customIdentifier, name in displayName), ringing the same handle again still shows the id. Putting the name in value and donating the id doesn't help either: the UI reads correctly, but a Recents redial returns the name and ignores the donated person and its customIdentifier.

3. A redial carries nothing else to map back from. In scene(_:continue:), both when the app is running and from a cold launch, the INStartCallIntent has only the contact's personHandle.value (type unknown) and display name. callRecordToCallBack, callRecordFilter, customIdentifier and contactIdentifier are all nil, and the intent and interaction identifiers are new on every tap. There's no conversation UUID, so whatever is in value is the only way back to the person.

4. callservicesd's name lookup never checks displayName. While reporting the call it logs:

Finding the appropriate localized name to use for handles
  - handle can't/shouldn't be formatted as a phone number, so using the unmodified destination ID
Received result from SGSuggestionsService: result (null)
Suggestions: No suggested name found for '<private>'
SNAP Suggestions: Hiding suggested nickname to prevent phishing. (isDomestic = 0, handleType = 1)

That looks like Contacts, then Suggestions, then the raw value, with suggested names deliberately hidden for generic handles. That would explain both the ignored displayName and why donation doesn't help.

Revised questions:

  1. Is displayName meant to be shown for .generic handles, or only as a fallback for .phoneNumber and .emailAddress handles that don't match a contact?
  2. If LiveCommunicationKit can't show a name while keeping a stable identifier, is CallKit (with localizedCallerName) the recommended path outside mainland China, keeping LiveCommunicationKit only where CallKit isn't available?
LiveCommunicationKit: Handle.displayName is never shown — the call UI and Recents show Handle.value
 
 
Q