The terminal UI
Running way with no flags opens the terminal UI: every task in the repository, in one searchable list, with the one thing a browser cannot do — pressing Enter runs it, in this terminal.
It is dependency-free: raw mode, ANSI escapes and one frame string per keystroke, no TUI toolkit. Collection is asynchronous, so a refresh runs in the background while you keep typing; the details of that are in Refresh below.
Searching
Section titled “Searching”Type to filter. Search is fuzzy by default: terms are whitespace-separated
and ANDed, so photo dev narrows the list to a photo app’s dev server —
every term has to match somewhere on the row. A leading ' on a term forces
that term to match as an exact substring instead of a fuzzy subsequence.
Matching is case-insensitive throughout.
Ctrl+E switches the whole query to exact mode, where every term must
match as a substring rather than a fuzzy subsequence. The current mode is
shown in the top-right corner of the screen, next to the tab row.
| Key | Action |
| ----------------------- | ---------------------------------------------------------------------------------------------------- |
| type to filter | narrow the list by fuzzy (or exact) match |
| Enter | run the selected task, handing it this terminal — or, for a task with parameters, ask for them first |
| Ctrl+Y | copy the selected task’s command |
| Tab / Shift+Tab | next / previous runner tab |
| Ctrl+E | toggle exact search instead of fuzzy |
| Ctrl+T | show only services — tasks that run until you stop them |
| Ctrl+S | trust this repository, after a y/n confirmation |
| Ctrl+R | refresh — re-probe the repository |
| , | this project’s configuration, read-only |
| ↑ / ↓ | move the selection by one row |
| Page Up / Page Down | move the selection by a screenful |
| Home / End | jump to the first / last row |
| Backspace | delete the last character of the query |
| Ctrl+U | clear the query |
| Esc / Ctrl+C | quit |
Ctrl+S is offered only when runners were actually withheld for want of
trust; press it when nothing is withheld and it says so and does nothing. It
arms a question rather than acting — the status line asks trust this repository? y/n, and only y goes through, because Ctrl+S is flow control
on many terminals and arrives unintentionally. Answering y writes a real,
persistent grant to the trust store, exactly as the trust command does, then
re-probes in place so the runners appear where the note was. See
Trust & execution.
The primary actions — run, copy, tab, exact, services, trust, refresh, quit —
are always shown in the footer line at the bottom of the screen. Movement
(arrows, Page Up/Down, Home/End) and line editing (Backspace, Ctrl+U)
aren’t listed there, but both work as described above.
Runner tabs
Section titled “Runner tabs”Tab and Shift+Tab cycle through a tab per runner plus a combined “all”
view, each labelled with how many tasks it holds. Switching tabs narrows the
list to that runner without touching the query.
The services filter
Section titled “The services filter”Ctrl+T toggles a filter for services — tasks marked persistent, meaning
they run until you stop them rather than finishing on their own (a dev
server, for example). Toggling it on hides everything else; toggling it off
restores the full list. A * next to a row in the list marks it as a
service. Ctrl+T is the one keystroke worth remembering here: press it, then
type the app’s name.
What Enter will run
Section titled “What Enter will run”The command Enter would execute is always shown on screen, above the key
line, along with any parameters it takes — so running a task is never a
surprise. Tasks are spawned from an argument vector, never through a shell,
so a recipe with <placeholders> cannot be mangled into something else on
the way. Ctrl+Y copies that same command to the clipboard instead of
running it.
Tasks that take arguments
Section titled “Tasks that take arguments”A recipe like just deploy <env> [tag] cannot be run as it stands: its
argument vector holds just deploy and nothing else, because the placeholders
are not values. Enter on such a task therefore collects them first. The key
line becomes a command line with the command already on it and the cursor
after it:
$ just deploy ▮ takes <env> [tag] enter run esc backType the arguments, Enter runs the whole line, Esc goes back to the list
with nothing run. Backspace deletes a character and Ctrl+U clears what you
have typed.
Only the tail is yours to type — the command itself is fixed. Quoting works as
a shell’s word splitting does, so "two words" is one argument, but nothing
else a shell does happens: no globbing, no variables, no operators. *.log
reaches the command as those five characters.
A task that takes no parameters is unaffected: Enter runs it, with no line to
dismiss.
Settings
Section titled “Settings”, replaces the list with this project’s configuration as waypost resolved it
— runners, execution, discovery, excluded paths, in the config schema’s own key
order, each section naming which layer supplied its value. Esc goes back.
It is the same four sections the web view shows, so the two surfaces describe a project the same way.
Reading only. The page can edit because it has a server to POST to and a
pointer to click with; the terminal points you at way config set instead.
Startup, and the cache
Section titled “Startup, and the cache”The first frame comes from the stored probe, not from a new one, so a repository whose slowest runner takes seconds shows its tasks at once instead of an empty frame. A real probe runs behind it and the rows update in place. Nothing is painted from a cold store: with execution off every exec runner already contributes a withheld report, so a cold store would yield a catalog of nothing but withheld rows — an empty list dressed up as an answer, which is worse than the empty frame it replaced.
Rows that came out of the store and are out of date are marked at the runner’s
TAB with a leading ~, not per row: a whole runner’s rows are as old as each
other, and marking each one would repeat a single fact hundreds of times.
The status line beside the task count then says one of three things:
- Fresh — nothing. There is nothing to report, and a line that is always there is a line nobody reads.
- Stale —
cached rows — re-probing just (~149ms)while the probe is in flight, orcached rows: just 35s old — ^r to re-probeonce it is over and the rows on screen are still the stored ones. - Stale and slow to probe here — painted red, because this is the case the
cache exists for:
cached rows — moon (~2.4s) is slow to probe here, so this takes a moment. Warning about a runner this repository has MEASURED at several seconds in the same voice as a 200ms refresh would waste the one warning that matters.
Slowness and refresh cost are measured in THIS repository, from a rolling window
of the last five probe durations reported as their median. A runner that is quick
here is never called slow because it is slow somewhere else, and where nothing
has been measured yet the line says never timed here rather than inventing a
number.
If a task you run turns out not to exist — a recipe renamed since the stored probe — the spawn failure is followed by a line naming the cached listing and its age, so an unexplained failure becomes one you can act on.
Refresh
Section titled “Refresh”Ctrl+R re-probes the repository. It is ALWAYS a real probe, never a read of
the store: a refresh that could be answered from a cache would make the one key
that guarantees accuracy indistinguishable from doing nothing. It writes the
store on the way out, so the next start still paints instantly. While a refresh
is in flight, “refreshing” appears next to the task count; a refresh that fails
reports the error in the same place instead of losing the list you already had.
The selection is held by the task’s identity, not its row number, so a refresh that reorders or drops rows cannot silently move the cursor onto a different task than the one that was on screen.