Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions config/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions tui/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, ", "))
Copy link
Copy Markdown
Member

@andrinoff andrinoff May 13, 2026

Choose a reason for hiding this comment

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

fmt.Sprintf("%s (%s)", s.Name, strings.Join(s.Addresses, ", ")) for a mailing list with many recipients could produce a very long suggestion string and overflow the TUI layout. Let's truncate

} else if s.Name != "" && s.Name != s.Email {
display = fmt.Sprintf("%s <%s>", s.Name, s.Email)
}
if i == m.selectedSuggestion {
Expand Down
Loading