Skip to content

feat: config option to use the List Layout (prototype variant C) for the Picker - #53

Merged
hammadmajid merged 11 commits into
mainfrom
feat/picker-list-layout
Sep 19, 2026
Merged

hammadmajid merged 11 commits into
mainfrom
feat/picker-list-layout

Conversation

@hammadmajid

Copy link
Copy Markdown
Contributor

Closes #44.

Adds a config key that selects the Picker's Layout, defaulting to the current one:

[picker]
layout = "grouped"        # or "list" for the flat fzf-style layout

What list draws

The List Layout renders prototype variant C from the kept prototype/picker-look branch: a flat run of Projects in History order with never-visited ones last, kind/ muted before each Project name, one status cluster per row, the relative time right-aligned, the filter prompt below the list where fzf users expect it, and a bar plus a background highlight on the selected row — with the same boxed preview pane on the right.

Fuzzy matching, both key maps, the status glyphs, the colours and the narrow-terminal rules stay shared with the Grouped Layout. ComputeLayout needed no change: both Layouts spend the same total width on their fixed columns, so the List Layout just hands it kind/name as the name column's content.

Unknown values are a config error (picker.layout must be grouped or list, got "fancy"), matching how every other key is validated.

Notes for the reviewer

  • A bug the review caught, not present in the prototype. The name column originally gave the Kind all the room it asked for and truncated the Project name with what was left, so a Kind long enough to fill the column cut the name to a rune or two and rendered it muted along with the Kind. The name now keeps a floor of its own and the Kind is the part that gives way (8dda3c4).
  • 703cf5b touches the Grouped Layout's render pathstatusClusterOn, and a match style derived from the row's base style — so the selected row's background runs through the glyphs and the matched runes. Output in the Grouped Layout is unchanged; its existing tests were not modified.
  • Shared behaviour is now tabled over both Layouts (frame height, alternate screen, equal-width rows, filter-line position), rather than each Layout keeping near-copies.
  • CONTEXT.md gains a Layout entry. Naming the values Grouped Layout and List Layout keeps the Picker's own _Avoid_ list intact: a bare "list" still must not mean the Picker.

Known follow-up

The glossary entry sharpens a naming collision already in the code: picker.Layout is a struct holding per-frame sizing (NameWidth, ListHeight, ShowPreview), which is not the domain concept. By the glossary the arrangement should own Layout and the sizing struct is really frame geometry. Renaming it is an exported-API change across the package, so it is deliberately left out of this PR.

Verification

go build, go vet, golangci-lint (0 issues), go test ./..., all files under the 300-line limit (view.go went 265 → 202). Also driven end-to-end under a pty against a real config to confirm the frame the binary actually draws.

🤖 Generated with Claude Code

hammadmajid and others added 11 commits September 20, 2026 03:24
The list layout marks its selected row with a background rather than a
caret, so every segment of a row has to be rendered through a base style
that carries it. statusCluster gains statusClusterOn, taking that base,
and highlightMatches derives its match style from the base it is given
instead of building a fresh one. The theme grows the selected row's
background and foreground. Nothing renders differently yet: the derived
match style is the same accent-bold-underline the old one was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LayoutStyle names the two layouts the Picker can draw, Options.Layout
selects one (the zero value staying LayoutGrouped, the accepted look),
and the Model remembers it. visibleRows states the row order the cursor
indexes into for the active layout: grouped by Kind as before, or flat
History order for the list layout. The View still draws the grouped
layout either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prototype variant C, from the kept prototype/picker-look branch: a flat
list with no Kind headers, "kind/" muted before each Project name, one
status cluster per row, the relative time right-aligned, the filter
prompt below the list where fzf users expect it, and a bar plus a
background highlight on the selected row.

View now dispatches on the Model's layout, with the frame size, the
column budget, the preview pane and the footer shared between the two.
Both layouts spend the same total width on their fixed columns, so
ComputeLayout needs no change: the list layout just hands it "kind/name"
as the name column's content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
[picker] layout selects which layout the Picker draws: "grouped", the
default and the look accepted in the prototype review, or "list". Any
other value is a config error naming the two it accepts, matching how
every other key is validated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolve already forwards the vim key map; it now forwards picker.layout
alongside it, so the config key reaches the Picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The name column gave the Kind all the room it asked for and truncated the
Project name with whatever was left, so a Kind long enough to fill the
column cut the name down to a rune or two and rendered it muted along
with the Kind. The name is what the user is reading for: it now keeps a
floor of its own and the Kind is the part that gives way, each segment
still carrying its own style.

listFrame also draws m.visibleRows() rather than m.visibleMatches(), the
same order the cursor and choose() index, so the drawn order and the
chosen Row cannot drift apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ding

The two layouts had grown three copies of the same shapes: the
frameSize/computeLayout pair, the block that clips a body to ListHeight
and scrolls the cursor into it, and a plain and a base-styled pair of
padding helpers. Sizing and clipping move to layout.go, where the rest of
the per-frame geometry lives; the padding helpers collapse onto the
base-styled pair in styles.go, with the grouped layout passing the zero
style as statusCluster already does. view.go drops from 265 lines to 202.

listView is renamed groupedBody: it draws the grouped body, and the name
read as the list layout's once that layout existed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The frame height, the alternate screen and equal-width rows are shared
behaviour, so they run as subtests over both layouts instead of the list
layout keeping near-copies of each. The filter line's position is the one
that differs, and it is now pinned from one table too: the top of the
grouped frame, the line below the body in the list layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quoteList existed for one call site with one fixed slice. The error now
reads "picker.layout must be grouped or list".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Picker now draws one of two arrangements, and the concept had no
agreed name: "layout", "variant" and "style" were all in play across the
issue, the prototype and the code. Layout is the term, with the Grouped
Layout and the List Layout as its two values, and the README uses them.

Naming the values Grouped Layout and List Layout keeps the Picker's own
_Avoid_ list intact: a bare "list" still must not mean the Picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hammadmajid

Copy link
Copy Markdown
Contributor Author

Follow-up filed as #54: the Layout naming collision this PR's glossary entry sharpens. Deliberately out of scope here.

@hammadmajid
hammadmajid merged commit e4809bb into main Sep 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: config option to use the fzf-style list layout (prototype variant C) for the Picker

1 participant