feat: implement the Picker (Bubble Tea v2, variant D) - #38
Merged
Merged
Conversation
charm.land/bubbletea/v2 v2.0.9, charm.land/lipgloss/v2 v2.0.6, charm.land/bubbles/v2 v2.2.1, github.com/sahilm/fuzzy v0.1.3, per the bubbletea-stack research (the github.com/charmbracelet import paths fail with a module mismatch on the v2 line). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Row carries a Project (Kind, Name, absolute Path), LastVisit and Visits. Options carries Vim and an initial Query. Run's rows arrive already in History order; the Picker never computes that order itself. Init fires one tea.Cmd per Row through tea.Batch, bounded by a semaphore of 8, keyed by Project path so results land correctly even as fuzzy filtering reorders the visible rows. Rows are grouped by Kind, ordered by first appearance among the currently visible rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Default key map: typing filters, up/down and ctrl+p/ctrl+n move, enter chooses, esc cancels, ctrl+u clears. Vim key map: the list is focused on open, j/k move, g/G jump to the ends, f or / focuses the filter, esc in the filter returns to the list keeping the query, esc or q on the list cancels, enter chooses from either mode. Tests drive Update with tea.KeyPressMsg and a fake StatusFunc, and check Kind-group ordering and fuzzy filtering through the exported Model. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RelativeTime renders the long form used in the row and the preview pane
("2 weeks ago", "yesterday"). RelativeTimeShort renders the compressed
form narrow terminals fall back to (now, 5m, 2h, 3d, 2w, 3mo, 1y). Both
return "" for a zero time (never visited).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ComputeLayout sizes the list to its natural content width (caret, longest name, status cluster, relative time), capped at 55% of the terminal, and drops the preview pane once the remainder falls under 30 columns. Below the list's natural width, relative time compresses to its short form first, then names truncate with "..." to a floor of 8; the status cluster never shrinks. Under 15 rows the footer's legend line is dropped, under 10 the keys line too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lipgloss.LightDark palette (muted, green, yellow, blue, red, accent, rule) selected from tea.RequestBackgroundColor. Status glyph cluster (clean/modified, untracked, ahead/behind, unknown, not-a-repo, loading), its plain-text width for column alignment, the legend line, the keys line, and fuzzy-match highlighting (accent bold underlined). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Filter line with the accent prompt; left pane rows grouped under bold Kind headers with a rule, selected row gets a caret and accent name, no background highlight; right pane preview box (kind/name, path, branch, status words, sync, last visit absolute plus relative, visits); footer rule, legend and keys line with the match count. Empty History renders the "run cdd scan to seed History" hint instead of the list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The preview column was joined against a block whose first line was the filter line, so the box's top border landed on the "❯ type to filter" row instead of aligning with the list body. Prepend a leading newline to the preview column, matching the accepted prototype's `"\n" + box.Render(...)`, so it renders under a matching blank line. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Layout.ListHeight was only consumed by the preview box's Height; the list itself drew every row, pushing the footer off screen once rows exceeded the terminal height and leaving a cursor past the fold invisible. Port the prototype's windowing: track the cursor's line index (counting Kind header lines), scroll so it stays on screen, and emit exactly ListHeight lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The unselected caret was " " + " " (3 columns) but the selected
caret was "›" + " " (2 columns), so every column on the selected row
shifted one place left as the cursor moved. Use a fixed 3-column
gutter (" " / " › ") on both branches, matching the spec and the
accepted prototype.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TestModel_Update_StatusResult asserted nothing about the result, ending with a comment claiming "arrival without panic is the assertion". Rewrite it with a fake StatusFunc that returns a distinct git.Status per row's path, feed the resulting messages through Update in reverse order, and assert the rendered View shows the right glyph on the right row, proving the keying is by Project path and not by arrival index. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements the Picker:
internal/picker, built on Bubble Tea v2 (charm.land/bubbletea/v2 v2.0.9,charm.land/lipgloss/v2 v2.0.6,charm.land/bubbles/v2 v2.2.1,github.com/sahilm/fuzzy v0.1.3, per the bubbletea-stack research).Run(rows []Row, status StatusFunc, opts Options) (Row, bool, error).rowsmust already be in History order; the Picker never computes that order itself. Draws on/dev/ttyviatea.OpenTTY, falling back to stderr; stdout is never written.Rowcarries aProject(Kind, Name, absolute Path),LastVisit, andVisits.StatusFuncis called once per Row via onetea.Cmdeach, fanned out throughtea.Batchand bounded by a semaphore of 8, keyed by Project path.OptionscarriesVimand an initialQuery.tea.WindowSizeMsgwithout losing state.Split across
picker.go(public API, Run),model.go(Model, Init, grouping/filtering),update.go(key maps),view.go(rendering),styles.go(palette, glyphs),relative.go(relative-time formatting),layout.go(width/height degradation) — every file well under 300 lines.Why
Closes #20.
Testing
go build ./... && go vet ./... && go test ./...all pass. Tests are table-driven, stdlib-only, inpackage picker_test, and exercise:Model.Updatewithtea.KeyPressMsgand status messages via a fakeStatusFunc, Kind-group ordering, fuzzy filtering, relative-time formatting, and the width/height degradation rules — all without a real terminal.A handful of small helpers (
Model.Chosen,RelativeTime/RelativeTimeShort,Layout/ComputeLayout) were exported specifically so the external test package could exercise them directly, per this repo's testing convention.Notes for the reviewer
internal/jump(rows already ordered) and thecli/jumpcommand that callspicker.Runand prints the chosen path are follow-ups (the ticket notes#27/#21depend on this).[keys] vimconfig plumbing (config.toml) is not part of this ticket;Options.Vimis ready for a caller to set it.🤖 Generated with Claude Code