diff --git a/README.md b/README.md index ab452b6..7f4ed45 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Globally search and resume [Claude Code](https://claude.ai/claude-code) conversa - Delete conversations with confirmation prompt - Prune bloated conversations losslessly (`ccs prune`) - Pass flags through to `claude` (e.g., `--plan`) -- Mouse wheel scrolling support ## Installation @@ -82,7 +81,6 @@ ccs buyer -- --plan - `Ctrl+D` - Delete selected conversation (with confirmation) - `Ctrl+R` - Prune selected conversation - shrink it losslessly (with confirmation) - `Ctrl+J/K` - Scroll preview -- `Mouse wheel` - Scroll list or preview (context-aware) - `Ctrl+U` - Clear search - `Esc` / `Ctrl+C` - Quit diff --git a/main.go b/main.go index 858b512..cbc546f 100644 --- a/main.go +++ b/main.go @@ -82,11 +82,10 @@ type model struct { previewScroll int width int height int - listHeight int // Calculated list height for mouse detection + listHeight int // Calculated visible list height selected *Conversation quitting bool claudeFlags []string - mouseInPreview bool // Track if mouse is in preview area confirmDelete bool // Are we in delete confirmation mode? deleteIndex int // Index of item to delete confirmPrune bool // Are we in prune confirmation mode? @@ -144,7 +143,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height - // Calculate list height for mouse detection + // Calculate visible list height m.listHeight = m.height * 30 / 100 if m.listHeight < 3 { m.listHeight = 3 @@ -152,35 +151,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Clear so a shrink doesn't leave wider stale rows behind. return m, tea.ClearScreen - case tea.MouseMsg: - // Determine if mouse is in preview area (below list + separator) - listAreaHeight := 2 + m.listHeight // search line + separator + list - m.mouseInPreview = msg.Y > listAreaHeight - - switch msg.Button { - case tea.MouseButtonWheelUp: - if m.mouseInPreview { - m.previewScroll = max(0, m.previewScroll-3) - } else { - if m.cursor > 0 { - m.cursor-- - m.previewScroll = 0 - } - } - return m, nil - case tea.MouseButtonWheelDown: - if m.mouseInPreview { - m.previewScroll = min(m.previewScroll+3, m.maxPreviewScroll()) - } else { - if m.cursor < len(m.filtered)-1 { - m.cursor++ - m.previewScroll = 0 - } - } - return m, nil - } - return m, nil - case tea.KeyMsg: // Handle delete confirmation mode if m.confirmDelete { @@ -1314,7 +1284,6 @@ Key bindings: Ctrl+D Delete conversation (with confirmation) Ctrl+R Prune conversation - shrink it losslessly (with confirmation) Ctrl+J/K Scroll preview - Mouse wheel Scroll list or preview (based on position) Ctrl+U Clear search Esc, Ctrl+C Quit @@ -1432,9 +1401,12 @@ func main() { os.Exit(1) } - // Run TUI + // Run TUI. Mouse reporting is intentionally NOT enabled: under a heavy + // frame the terminal emits mouse-wheel reports faster than bubbletea reads + // them, and the fragmented sequences leak into the search box as text. + // Scrolling is keyboard-only (arrows / Ctrl+J/K / PgUp/PgDn). m := initialModel(items, filterQuery, claudeFlags) - p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) + p := tea.NewProgram(m, tea.WithAltScreen()) finalModel, err := p.Run() if err != nil { diff --git a/main_test.go b/main_test.go index b9850f1..4cd34b5 100644 --- a/main_test.go +++ b/main_test.go @@ -1096,65 +1096,11 @@ func TestRenderPreviewLongMultibyteMessageStaysValidUTF8(t *testing.T) { t.Error("preview of a long multibyte message produced invalid UTF-8") } } - -func TestUpdateMouseScroll(t *testing.T) { - // Give each conversation enough messages that the preview is scrollable. - msgs := make([]Message, 10) - for i := range msgs { - msgs[i] = Message{Role: "user", Text: "message text line", Ts: "2024-01-15T10:00:00Z"} - } - items := []listItem{ - {conv: Conversation{SessionID: "test-1", Messages: msgs}, searchText: "first"}, - {conv: Conversation{SessionID: "test-2", Messages: msgs}, searchText: "second"}, - {conv: Conversation{SessionID: "test-3", Messages: msgs}, searchText: "third"}, - } - - m := initialModel(items, "", nil) - - // Set window size first to initialize mouse tracking - result, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 30}) - m = result.(model) - - // Mouse wheel up in list area (Y=5, which should be in list) - m.mouseInPreview = false // Explicitly set for test - result, _ = m.Update(tea.MouseMsg{Button: tea.MouseButtonWheelUp, Y: 5}) - m = result.(model) - // Should not move cursor from 0 - if m.cursor != 0 { - t.Errorf("wheel up at top should keep cursor at 0, got %d", m.cursor) - } - - // Move to second item - result, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown}) - m = result.(model) - if m.cursor != 1 { - t.Errorf("should be at cursor 1, got %d", m.cursor) - } - - // Mouse wheel down in list area should move cursor forward - m.mouseInPreview = false // Ensure we're scrolling list not preview - result, _ = m.Update(tea.MouseMsg{Button: tea.MouseButtonWheelDown, Y: 5}) - m = result.(model) - if m.cursor != 2 { - t.Errorf("wheel down should move to cursor 2, got %d", m.cursor) - } - - // Mouse wheel in preview area - m.previewScroll = 0 - m.mouseInPreview = true - result, _ = m.Update(tea.MouseMsg{Button: tea.MouseButtonWheelDown, Y: 20}) - m = result.(model) - if m.previewScroll != 3 { - t.Errorf("wheel down in preview should scroll preview, got %d", m.previewScroll) - } -} - func TestPreviewScrollClampedToContent(t *testing.T) { conv := Conversation{SessionID: "s1", Messages: []Message{ {Role: "user", Text: "only message", Ts: "2024-01-15T10:00:00Z"}, }} m := initialModel([]listItem{{conv: conv}}, "", nil) - m.mouseInPreview = true maxScroll := m.maxPreviewScroll()