diff --git a/config/cache.go b/config/cache.go index a196a5c..168e533 100644 --- a/config/cache.go +++ b/config/cache.go @@ -124,10 +124,16 @@ type ContactUsage struct { } // Contact stores a contact's name, email address, and per-account usage. +// +// For regular contacts, Email holds a single address and Addresses is empty. +// For mailing-list virtual contacts emitted by SearchContacts, Email is empty +// and Addresses holds the expanded list of recipients. Callers that need to +// distinguish the two cases should check len(Addresses) > 0. type Contact struct { - Name string `json:"name"` - Email string `json:"email"` - Usage map[string]ContactUsage `json:"usage_by_account"` + Name string `json:"name"` + Email string `json:"email"` + Addresses []string `json:"addresses,omitempty"` + Usage map[string]ContactUsage `json:"usage_by_account"` } // UnmarshalJSON accepts both the current usage_by_account format and the @@ -322,10 +328,14 @@ func SearchContactsForAccount(query, accountID string) []Contact { if err == nil { for _, list := range cfg.MailingLists { if strings.Contains(strings.ToLower(list.Name), query) { - // Convert mailing list to a virtual contact + // Convert mailing list to a virtual contact. Addresses are + // stored in a dedicated slice so the Email field keeps its + // single-address invariant -- avoids corruption by + // normalizeContactEmail and exact-match lookups in callers. + addresses := append([]string(nil), list.Addresses...) matches = append(matches, Contact{ - Name: list.Name, - Email: strings.Join(list.Addresses, ", "), + Name: list.Name, + Addresses: addresses, Usage: map[string]ContactUsage{ accountID: { UseCount: 9999, // Ensure lists appear at the top diff --git a/tui/composer.go b/tui/composer.go index 9d2cecb..3f3fd53 100644 --- a/tui/composer.go +++ b/tui/composer.go @@ -347,9 +347,9 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { selected := m.suggestions[m.selectedSuggestion] var newEmail string - if strings.Contains(selected.Email, ",") { - // It's a mailing list: insert just the addresses to maintain valid email formatting - newEmail = selected.Email + if len(selected.Addresses) > 0 { + // Mailing list: emit just the addresses to maintain valid email formatting + newEmail = strings.Join(selected.Addresses, ", ") } else if selected.Name != "" && selected.Name != selected.Email { newEmail = fmt.Sprintf("%s <%s>", selected.Name, selected.Email) } else { @@ -701,7 +701,9 @@ func (m *Composer) View() tea.View { var suggestionsBuilder strings.Builder for i, s := range m.suggestions { display := s.Email - if s.Name != "" && s.Name != s.Email { + if len(s.Addresses) > 0 { + display = fmt.Sprintf("%s (%s)", s.Name, strings.Join(s.Addresses, ", ")) + } else if s.Name != "" && s.Name != s.Email { display = fmt.Sprintf("%s <%s>", s.Name, s.Email) } if i == m.selectedSuggestion {