Pre-flight
Problem / motivation
In the dashboard Chats view, 1:1 conversations are listed by their raw identifier instead of the name the contact is saved under:
- a bare phone number, or
- after WhatsApp's LID migration, a raw
…@lid id (e.g. 116745901723874@lid).
Both the sidebar chat list and the open-conversation header show this raw id, which makes it very hard to tell who you are talking to. Earlier versions (reported as far back as v0.1.8) showed the saved contact name in the Chats list, so on the current Baileys engine this is effectively a regression.
Example (Baileys engine, session ready): every 1:1 row in the Chats sidebar shows a number / @lid, never a name.
Root cause (already located)
The dashboard is not the problem — it already prefers a name and only falls back to the id:
// dashboard/src/pages/Chats.tsx (sidebar row ~L703, room header ~L737)
{chat.name || chat.id.split('@')[0]}
The name is dropped server-side, in the Baileys chat mapper:
// src/engine/adapters/baileys-session-store.ts -> toNeutralChat() (~L124)
return {
id: c.id,
name: c.name ?? this.userPart(c.id), // <-- only Baileys Chat.name, else raw user part
...
};
Baileys Chat.name is typically empty for individual chats (it is mainly populated for groups / custom-named chats), so ChatSummary.name becomes the raw user part of the JID/@lid.
The saved name is already available in the same store, it is just never cross-referenced from the chat mapper:
// src/engine/adapters/baileys-session-store.ts -> toNeutralContact() (~L107)
name: c.name ?? c.verifiedName, // saved address-book name / business verified name
pushName: c.notify, // sender's self-set display name
The contacts map is fed from contacts.upsert / contacts.update / messaging-history.set (see baileys.adapter.ts ~L177-183), and for @lid chats the store also already holds a lidToPn map + resolvePhone() (~L92).
Proposed solution
Enrich toNeutralChat() so the chat name falls through a small priority chain before giving up on the raw id:
Baileys Chat.name (groups, custom-named chats) — unchanged.
- Contact lookup by chat id in the existing
contacts map: name ?? verifiedName ?? notify.
- For
@lid chats: resolve @lid → phone JID (via the existing lidToPn / phoneNumber) and look up that contact, so hidden-number contacts still resolve once their lid↔pn pair is known.
- Raw user part of the JID — current behavior, last resort only.
Illustrative sketch (maintainer can refine):
private toNeutralChat(c: Chat): ChatSummary {
const last = this.lastMessages.get(c.id);
return {
id: c.id,
name: c.name ?? this.resolveContactName(c.id) ?? this.userPart(c.id),
isGroup: c.id.endsWith('@g.us'),
unreadCount: c.unreadCount ?? 0,
timestamp: last?.timestamp ?? this.toUnixSeconds(c.conversationTimestamp),
lastMessage: last?.text,
};
}
private resolveContactName(id: string): string | undefined {
const pick = (c?: BaileysContactWithPhone) =>
c?.name ?? c?.verifiedName ?? c?.notify ?? undefined;
const direct = pick(this.contacts.get(id));
if (direct) return direct;
if (id.endsWith('@lid')) {
const pn = this.lidToPn.get(id) ?? this.contacts.get(id)?.phoneNumber;
if (pn) {
return pick(this.contacts.get(pn))
?? pick(this.contacts.get(`${this.userPart(pn)}@s.whatsapp.net`));
}
}
return undefined;
}
Notes / caveats:
- The locally saved name (
Contact.name) is the most accurate and should win; verifiedName (WhatsApp Business) and notify (pushName) are reasonable fallbacks. pushName is the sender's own chosen display name, not the saved contact name — still far better than a raw id.
- Baileys only knows the saved address-book name if WhatsApp delivered it via history sync / contact events. For some hidden-number
@lid contacts that never arrives, in which case notify (pushName) is the best available — but the chat row should still prefer it over the raw id.
- This keeps the fix engine-neutral at the API level:
ChatSummary.name simply becomes "best known display name", which is what the dashboard already expects.
Alternatives considered
Scope
Pre-flight
WaIdidentity value object + alid <-> phoneresolution table #349, but those are about resolving@lid→ phone number. This request is about displaying the saved contact / push name in the dashboard Chats list, which is a separate, name-only concern (it should work even when the phone number cannot be resolved).Problem / motivation
In the dashboard Chats view, 1:1 conversations are listed by their raw identifier instead of the name the contact is saved under:
…@lidid (e.g.116745901723874@lid).Both the sidebar chat list and the open-conversation header show this raw id, which makes it very hard to tell who you are talking to. Earlier versions (reported as far back as v0.1.8) showed the saved contact name in the Chats list, so on the current Baileys engine this is effectively a regression.
Example (Baileys engine, session
ready): every 1:1 row in the Chats sidebar shows a number /@lid, never a name.Root cause (already located)
The dashboard is not the problem — it already prefers a name and only falls back to the id:
The name is dropped server-side, in the Baileys chat mapper:
Baileys Chat.nameis typically empty for individual chats (it is mainly populated for groups / custom-named chats), soChatSummary.namebecomes the raw user part of the JID/@lid.The saved name is already available in the same store, it is just never cross-referenced from the chat mapper:
The
contactsmap is fed fromcontacts.upsert/contacts.update/messaging-history.set(seebaileys.adapter.ts~L177-183), and for@lidchats the store also already holds alidToPnmap +resolvePhone()(~L92).Proposed solution
Enrich
toNeutralChat()so the chat name falls through a small priority chain before giving up on the raw id:Baileys Chat.name(groups, custom-named chats) — unchanged.contactsmap:name ?? verifiedName ?? notify.@lidchats: resolve@lid → phone JID(via the existinglidToPn/phoneNumber) and look up that contact, so hidden-number contacts still resolve once their lid↔pn pair is known.Illustrative sketch (maintainer can refine):
Notes / caveats:
Contact.name) is the most accurate and should win;verifiedName(WhatsApp Business) andnotify(pushName) are reasonable fallbacks. pushName is the sender's own chosen display name, not the saved contact name — still far better than a raw id.@lidcontacts that never arrives, in which casenotify(pushName) is the best available — but the chat row should still prefer it over the raw id.ChatSummary.namesimply becomes "best known display name", which is what the dashboard already expects.Alternatives considered
GET /sessions/:id/contactsand joining on chat id — possible, but it duplicates lookup logic in the UI and is racy; the backend already has the data in one place.@lid → phoneresolution, tracked in Baileys engine: @lid -> phone never resolves (senderPhone / contacts/:id/phone always null); #263 feature non-functional on Baileys #362 / [Feature]: typedWaIdidentity value object + alid <-> phoneresolution table #349) — still a bare number, not the saved name the user expects, so it does not solve this request.Scope