The first and only screen: a search field, the cards under it, a footer.
Typing filters. There is no mode to enter and no key to press first — the search field has the keyboard from the moment the UI opens, because searching is what this tool is for and anything else is a key you have to remember. So every printable character is the search field's, and everything the application does of its own is bound to a key the field does not want: the vertical arrows, Enter, ESC and a handful of Ctrl chords.
^I the card in full ^O ⏎ open the card in a browser
^F the filter switches ^R reload the board
^A what this is ESC clear the search, then quit
↑ ↓ ^P ^N move the selection ^C quit
PgUp PgDn a screen at a time^I is the byte the Tab key sends — a terminal cannot tell the two apart —
so Tab opens the card too, and the switches moved to ^F to make room.
q is a letter, so here it goes into the search field like any other. In the
popups, where there is nothing to type into, it closes — see
Gitpro.Views.Detail, Gitpro.Views.Filters and Gitpro.Views.About.
^A is the one binding taken off the search field: Atui.TextInput has it as
readline's "start of the line", and Home still does that.
Loading
A board of a few hundred cards is several round trips, so the cards are
fetched in an Atui.Fetch and the first frame is drawn before any of them
have arrived. The UI is usable while it loads — the search field takes what
you type, and the query is already applied when the cards land.
Who holds the filter
This view does. Gitpro.Views.Filters is pushed over it as a popup and sends
the filter back on every toggle, so the list behind the popup narrows while
the switches are being set rather than when the popup closes. While either
popup is up, this view — which as the root view sees every key first — claims
nothing, or the search field would eat the keys meant for them.
The switches for the labels and the assignees cannot exist before the cards
do, so load/1 hands the filter the ones it found every time the board
arrives. Switches already set survive that, and a popup that is open while it
happens is told as well.
Summary
Functions
What is said where the rows would be when there are none.
The footer, as much of it as fits.
The filter with the switches the cards themselves decide.
How the widths of a row are divided at width columns.
The title bar: what repository this is, and which board is being shown.
Functions
What is said where the rows would be when there are none.
Which of the two ways of narrowing emptied the list is the one thing worth saying here — being told to clear a search you never typed is how a filter that is quietly on stays on.
@spec observed(Gitpro.Filter.t(), [Gitpro.Item.t()]) :: Gitpro.Filter.t()
The filter with the switches the cards themselves decide.
The columns come from the board, so they are known before anything loads. The states, labels and assignees are only what the cards happen to show — a board with no pull requests on it has no "merged" switch to offer — so they are read off the cards each time they arrive. Switches already set are kept, which is what makes a reload keep the filter.
How the widths of a row are divided at width columns.
Returns %{title:, column:, repo:} — the fixed parts (the marker, the state
glyph and the number) are the same at every width, and what is left goes to
the title. The repository is dropped first when there is not enough for
everything, then the board column: a board is usually one repository's, and
the column is on screen in the flags anyway.
@spec title(map(), pos_integer()) :: String.t()
The title bar: what repository this is, and which board is being shown.
Shortens rather than spilling — the board's title is the first thing to go, because the repository is what tells you the tool is pointed at the right place.