From 0be3857487532374da723b476a3488e56ae2bc8d Mon Sep 17 00:00:00 2001 From: Bharat Kunwar Date: Mon, 22 Jun 2026 13:59:46 +0100 Subject: [PATCH] feat: search assistant messages and mark named sessions in the list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two search/topic UX gaps from the codebase review: - The list filter only matched user messages, but the HITS column and preview highlighting already counted all messages - so a conversation was unfindable by something only Claude said, while the UI implied it was searchable. buildItems now includes assistant text in searchText (and searchLower), making the filter consistent with HITS/preview. - Named sessions (custom/ai title) and rows that fall back to the first user message looked identical in the TOPIC column. Named rows now get a leading ✎ marker so a curated name is distinguishable from a truncated opening line. --- main.go | 17 +++++++++++++---- main_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 1fba656..3ed08c0 100644 --- a/main.go +++ b/main.go @@ -388,7 +388,15 @@ func (m model) formatListItem(item listItem, selected bool) string { project = project[:19] + "..." } - topic := truncate(getTopic(item.conv), 40) + // Mark named sessions (custom/ai title) so they're distinguishable from + // rows that fall back to the first user message. ponytail: the glyph is + // ambiguous-width, so a named row may sit one cell narrow on CJK-width + // terminals - cosmetic only, truncate is rune-safe. + topic := getTopic(item.conv) + if item.conv.Title != "" { + topic = "✎ " + topic + } + topic = truncate(topic, 40) // Message count msgs := len(item.conv.Messages) @@ -861,10 +869,11 @@ func buildItems(conversations []Conversation) []listItem { searchParts = append(searchParts, formatTimestamp(conv.FirstTimestamp)) searchParts = append(searchParts, formatTimestamp(conv.LastTimestamp)) + // Include assistant messages too so a conversation is findable by + // what Claude said, matching the HITS column and preview which already + // count all messages. for _, msg := range conv.Messages { - if msg.Role == "user" { - searchParts = append(searchParts, msg.Text) - } + searchParts = append(searchParts, msg.Text) } searchText := strings.Join(searchParts, " ") diff --git a/main_test.go b/main_test.go index 39490c4..3434d83 100644 --- a/main_test.go +++ b/main_test.go @@ -186,6 +186,11 @@ func TestBuildItems(t *testing.T) { t.Errorf("search text should contain all user messages, got %q", items[0].searchText) } + // Search text should also contain assistant messages (findable by what Claude said) + if !strings.Contains(items[0].searchText, "response 1") || !strings.Contains(items[0].searchText, "response 2") { + t.Errorf("search text should contain assistant messages, got %q", items[0].searchText) + } + // Search text should contain session ID if !strings.Contains(items[0].searchText, "session1") { t.Errorf("search text should contain session ID, got %q", items[0].searchText) @@ -697,6 +702,28 @@ func TestFormatListItem(t *testing.T) { } } +func TestFormatListItemNamedSessionMarker(t *testing.T) { + named := listItem{conv: Conversation{ + SessionID: "s1", + Title: "Refactor auth flow", + LastTimestamp: "2024-01-15T10:30:00Z", + Messages: []Message{{Role: "user", Text: "hi"}}, + }} + unnamed := listItem{conv: Conversation{ + SessionID: "s2", + LastTimestamp: "2024-01-15T10:30:00Z", + Messages: []Message{{Role: "user", Text: "just a first message"}}, + }} + m := initialModel([]listItem{named, unnamed}, "", nil) + + if got := m.formatListItem(named, false); !strings.Contains(got, "✎ Refactor auth flow") { + t.Errorf("named session should show the marker, got %q", got) + } + if got := m.formatListItem(unnamed, false); strings.Contains(got, "✎") { + t.Errorf("first-message fallback should not show the marker, got %q", got) + } +} + func TestUpdateKeyboardNavigation(t *testing.T) { items := []listItem{ {conv: Conversation{SessionID: "test-1"}, searchText: "first"},