Cockpit, terminal client and menu bar
All three are clients of the daemon. They show the same views and offer the same actions, which the daemon computes for every row; a client renders them and decides nothing.
The cockpit
Section titled “The cockpit”wloop open # opens the cockpit: in the app's window, or your browserwloop open --print # prints the URL with its token, for another browserwloop open --print --view stalled # opens on one viewwloop open '#task=<id>' # opens a cockpit route; so does a URL of the cockpit itselfwloop open 'wloop://open/#task=<id>' # opens a link copied from the cockpitwloop open <url> # focuses the pull request's workspace in its managerOn macOS, while wloop.app runs, open and a notification’s click show the cockpit in the app’s own window. There is one window, and it reopens where you left it. It is in the Dock and ⌘Tab while it shows; closing it hides it. Without the app, with window.enabled = false, or on another system, open uses the browser, which always works.
The window has no title strip: its controls sit over the cockpit’s top strip, which becomes the title bar. You drag the window by it, and it holds back and forward, the title of what shows (the list, or the task or unit whose detail is open), the copy-link button and the omnibox button, which opens the omnibox in the window. The app marks the page’s address (titlebar=inset) so the cockpit draws this only in the app’s window; in a browser tab the top strip stays as it is.
Detach. The detail of a task or unit has a button, and the key t while the focus is in it, that opens the detail in a window of its own. There is one such window per task or unit: detaching it again brings its window forward. It follows changes as the main window does, a link in it to anything else opens in the main window, and closing it closes it for good; it is not reopened when the app starts again. In a browser, detach opens the detail in a new tab.
Links. The copy-link button, in the title bar and in the detail, and the key c copy a link to what shows: wloop://open/#<route>, a list, a task or a unit. The link carries neither the daemon’s port nor its token, so it works after a restart and is safe to paste in a chat. The app registers the wloop scheme: opening such a link (from a chat, a note or open in a terminal) shows its route in the main window and brings it forward, as wloop open does with the link; with window.enabled = false the menu bar opens it in the browser. A link that cannot be read opens the default view, which says so. The cockpit in a browser copies the same link.
The global shortcut window.hotkey (ctrl+option+space; "" for none) opens a floating panel over whatever app you are in, without leaving it until you type, on the route #panel=omnibox. A link to any other route in the panel opens that route in the main window and brings it forward. A pasted unit’s actions run in the panel, asking an action’s confirm first, and the panel hides once the action is done; an action that copies, such as Copy review request, puts its copy on the clipboard. A failed action, or a write the system refuses, keeps the panel open with why and the text to copy by hand. An action that asks for text opens the unit in the main window. Every confirm and question the cockpit asks is drawn in the page, since the native window shows no browser dialog; one asked from the omnibox opens over it, and cancelling it returns there. The panel shows the omnibox alone, with no backdrop; Escape hides the panel, and so does clicking elsewhere. A shortcut that does not parse, or that the system refuses, is logged as window.hotkey with the chord; the window and the menu bar keep working. Another app holding the same chord through a system shortcut takes it without an error.
The page listens on 127.0.0.1 only and asks for the token that open hands over, and loads nothing from elsewhere: its scripts and its fonts (Geist for prose, Geist Mono for refs, counts and dates) ship in the binary. It follows the system’s light or dark appearance, and moves only in answer to what you do, not at all when the system asks for reduced motion. It opens on Focus, with the lists beside it. Needs me, Stalled, In loop, Approved and Done are lists of tasks across sources: a pull request shows there through its task. The GitHub views list what exists in GitHub:
| View | Shows |
|---|---|
| My PRs | your own pull requests, those you took over included |
| Backlog | charts of the open pull requests: who they wait on, age, size against risk, tags, failing checks |
| All | a dense list of everything in the inventory |
| Explore | search other people’s pull requests to help on |
The rail on the left starts with the product’s mark and the omnibox entry (Search or ask, ⌘K, which opens the omnibox), then Home (g h), then the views of your profile in its order but those it keeps off the rail (rail = false), then Plan, counting what waits for you or is blocked, whatever plugin runs (no view of views.toml may be named plan or home, and a plugin layer’s [views.plan] is a load problem that drops that key alone: the plan’s label is the "Plan" text of strings.toml), then Recent, the newest events of every unit the loop follows whatever plugin runs: pull requests and the units of the other sources with a card, each event with its unit’s label, its title and a link that opens the unit on its service (recent is refused as a view’s name the same way, and its label is the "Recent" text of strings.toml), then each plugin’s views under the plugin’s name, then whom the rows shown wait on, each a link that filters by them. The views that count nothing, but the one shown, fold into one Empty entry with their count, which lists them in place when opened. Each view of views.toml explains itself: its item in the list of views shows its summary as a tooltip, on hover and on keyboard focus; its heading shows the summary under the label, and Why these tasks? opens the view’s when, sort and group as your profile writes them. A view that lists nothing shows its empty.
Done and Approved offer Remove all clean, which removes the workspaces their tasks’ pull requests hold, keeping each one whose checkout holds anything not published, with why (any view whose remove_clean is set in views.toml offers it). Task lists and plugin row views draw their items with the same two-line row. The first line holds a task’s state glyph (its shape the state’s category, its hue whom the task waits on, a button that filters by the state), the title without the unit label the second line shows, a dot when the task changed since you saw it and a marker counting its pending drafts, then on the right the kind, meta and flag pieces (at most the facet’s show_max, then +n) and the actions. The second line holds the ref and why it waits, then on the right the workspace and link, whom it waits on, and the date the time rule picks with its label: since when it waits on you, or when someone last moved it (a pending round shows its countdown to the deadline of its open plan gate instead). Each line stays one line, the title and the reason cut with an ellipsis. Each piece comes from a facet its profile or plugin places on the row (see Where a facet shows on a row), and every piece that shows a facet’s value is a button that filters by it; the Sort menu orders the list. A grouped list puts each group under a sticky head with its count. A row whose provider has a strip facet shows a dot in its hue before whom it waits on (see The strip). A task’s detail panel reads top to bottom: its head (its units, the title, the state, whom it waits on and since when), a grid of its properties, the brief’s risk and size, its cards’ signals and its tags (each property, the risk and the size named and worded by the task facet that reads it, such as [facets."prop.kind"] and its values), then the cards of its units, then what waits on you (each open gate as a card, the agent’s message as markdown folded after its first blocks, a question’s options as cards with the recommended one picked, a plan gate’s item as the plan shows it: its workflow’s summary, the task it acts on by title with the conversations it came from, what applying does in words with the open items it resolves and whether it posts, why it waits, and, folded, the facts that fired it, and each pending draft as a card that says where it goes, in what language, why it was drafted and what sending it does, its mentions by name), then the verbs your profile’s detail.verbs names, with More opening the palette over the task’s other verbs, then tabs (Brief, Timeline, Asks, Related, Runs; a tab with nothing in it is left out, ] and [ switch them) and the composer. In a gate, 1 to 9 pick an intent and ⌘↵ in the answer box sends it. A unit’s label opens it in the cockpit, in its plugin’s view; a unit its plugin no longer lists, such as a closed agent session, opens the first open task that holds it. The service’s name beside it (GitHub, Slack) opens the unit there, in a new tab. A unit’s detail shows its card, then its items, open first and the resolved past the third folded behind a button, each with its state, whom it waits on (you, a person by name, or no owner) and the task that holds it as a link that opens it, then the reply box and the open tasks that hold it. The filter box (/) takes text and qualifiers such as where:, author:@me or is:stalled; see Cockpit filter.
The cockpit opens on the home (#view=home, see The home and decision queues): the briefing, whose counts link to their decision queue (#queue=<kind>) or view, the first rows of its sections with a link to the whole view, and the capture field, where Enter captures what you type as a task. A decision queue shows one decision at a time with its progress (2 of 5): a gate’s or a draft’s card as the detail draws it, or a unit’s row with its situation, then its primary action, then the next one; Enter runs the primary action, j and k move to the next or the previous decision without acting, o opens the source and Esc goes back to the home. After an action the queue reads again, so a decision settled elsewhere leaves it and a refused one shows why. #queue=list&ids=<id>,<id> walks the tasks it names, in order.
The omnibox (⌘K, the rail’s entry, the title bar’s button, and the floating panel) lists the selection’s actions and their keys while nothing is typed. Typing searches every task after a short pause and resolves the text at once: a pasted address shows its unit, its tasks and the actions its row offers (Enter runs the first); a known verb shows its operations as a checklist (Enter runs the checked ones); anything else shows the search’s groups, whom each waits on, and the views each result is in. A reply to text you have already changed is dropped, and its request aborted. ⌘↵ asks the read-only agent: it answers with a list of tasks, which Open as a queue walks, or proposes operations, each with its reason and editable before you run it, and may ask one question first, whose answer goes back with the next ask. Nothing runs before you confirm it, and each operation runs as the click it stands for. A badge says which path answers, instant or agent; an agent that fails shows its error and leaves the search as it was.
Every list, of tasks or of a plugin’s rows, has the same toolbar over the facets of its items: the Group and Sort chips, each with its value and a menu, a removable chip per filter, Filter (+), which opens a palette of the list’s fields and then the values its items hold of the one you pick, the count it shows of the view, Clear, the List and Board switch, and Copy list. The Sort menu offers the view’s own order and every sortable field of the list, those its rows show among them. The menus offer a field only when some item of the view has a value for it, so Needs me, which mixes Slack conversations and pull requests, offers where, ci and source, and My PRs offers no Slack field. A task view groups by the facet that reads the fact its group in views.toml names unless you pick another (or None), and keeps the daemon’s order until you pick a sort; the sort menu calls that order by default_sort, a row view’s by its plugin’s ui.toml. A view opens in the layout its views.toml entry names: list (the default), board, which the switch turns back to the list, or outbox, the Outbox grouped by why each draft was drafted (see The Outbox grouped by why). A board puts a lane per group, each item a card with its list row’s slots, the workspace and actions aside: the title, which opens it from anywhere on the card, then the kind, meta and flags, the ref and reason, whom it waits on and the date, each piece the same facet button as on the list and the tooltip the row’s (a draft’s card keeps the draft under its title). A plugin’s own view lists its rows with the same row. With nothing picked a board groups by the facet that colors the strip (a task view’s is the one its profile marks strip, the bundled turn; a row view’s is its plugin’s) when the view offers it, else it is one lane. Filters, grouping, order and layout stay in the URL; a view that does not offer the group or sort shows its default and leaves them in the URL for the views that do. The menus’ and Copy list’s words are [lens] in views.toml. The filter keeps the tasks the rail counts. Copy list copies what the view shows, in its order, as YAML under items:: each item’s reference (a task’s id, a row’s label), link, title and units, then its facets by id, to paste into an agent session with a prompt.
A task’s page shows, in order: its header (its units, each opening in the cockpit with the service’s name beside it, the title, the state, whom it waits on and since when), the properties your profile declares with the brief’s risk and tags, the gates and drafts that wait on you, each gate with the intents of its gate in gates.toml (1 to 9 pick one while the gate has the focus, the key beside each; an intent that takes an answer reads the box above it, which ⌘↵ sends), the primary verbs of tasks.toml with their keys and More (⌘K) over the rest, then the actions its units offer on a task’s page, each run on its unit (an action is offered there when its spec lists the task surface: the bundled one is Copy review request on an open pull request), then the tabs: Brief, Timeline (the timeline folded into facts, each with its raw events under a count, which you narrow by kind; see The timeline), Asks with the quotes they came from, Related with the task’s children, and Runs with Open workspace, Enter session and Transcript. A tab with nothing in it does not show; ] and [, hinted at the end of the strip, switch tabs. The composer comes last. Send, the default that Ctrl+Enter runs, writes to the task’s agent (see Chat); Send reply posts the text as a reply in the task’s conversation at once and Save as draft holds it until you approve it, both through the verb whose handler is reply; the one its send in tasks.toml names comes first.
The cockpit reads in the interface language (ui.locale, else the system’s; see Translate the interface): its own words are the profile’s strings.toml, the core’s event kinds and the timeline’s facts are timeline.toml’s, and dates and numbers follow the locale. It renders in English at once when the daemon does not serve the strings within a moment, and follows a language changed while it is open.
The terminal client
Section titled “The terminal client”wloop tuiThe views of your profile’s views.toml, in its order and starting on the first (the bundled Inbox, Needs me, Waiting, Chase, Fits now, Blocked by me, Stalled, In loop, Approved, Snoozed, Focus, Projects, Someday, Logbook, Done, Outbox, Review), the plan, then the plugins’ views (My PRs), updated live. A pending round’s row counts down to the deadline of its open plan gate. The / filter takes the cockpit’s qualifiers, so a list and the rail count the same tasks. A task view groups by the facet that reads the fact its group names, and a row view is ungrouped; = steps the group through the view’s own, each other field some item of the view has a value for, and none, and - steps the sort through the view’s own order and each sortable field, ascending then descending. The terminal has no board. A task’s line, a row’s line and a draft’s line in the Outbox are the same: the age (a pending round’s countdown, in the countdown_* row words), the ref (a draft’s target), the title (a draft’s text), whom it waits on with its kind (a draft’s state) from 60 columns, and the meta part from 100 columns (a project’s review facts with it); flags, the reason and the workspace are in the detail. A task’s detail shows its asks with the quotes they came from, its open gates with their intents (1 to 9 pick one; an intent that takes an answer asks for it first), and its runs with where they run and the command that resumes them. A verb’s key runs the verb your profile serves, and a verb that asks for a value asks the question its input sets. While a plugin has not reported every unit since the daemon started, a line names it and why; a row its plugin last reported before it went down shows (stale). The keys are the cockpit’s: lowercase keys file tasks, uppercase keys act on a row (R run now, S skip, G send with guidance, W answer, C continue, X dismiss, H pause, M mute, A take over, O open, V review, F address, N nudge). Ctrl+K opens the palette with every action, ? the help, where a view’s key shows its summary, q quits. A view that lists nothing shows its empty. Actions that publish or take over ask for a y first. See Keys.
wloop status prints the tasks of the views the badge counts, then the pull requests you track, once, for a script or a quick look.
The terminal client reads in the interface language, with the words of the profile’s strings.toml (English until the daemon serves them); key names stay as written.
The menu bar (macOS)
Section titled “The menu bar (macOS)”wloop app, which the LaunchAgent starts from wloop.app, puts an item in the menu bar. It shows PR <n>, the count of the views your profile’s [badge] names (the bundled Inbox and Needs me), or PR ? while the daemon does not answer. Its menu names the plugins still loading, lists up to five tasks of each badge view (a pending round’s task among them, through its open plan gate), then any section a plugin’s manifest declares ([[source.ui.menubar]]; the bundled plugins declare none), then Open cockpit (on the first badge view, in the app’s window when it has one), Sweep now, Pause automatic rounds (or Resume), the sync line and Quit. Quit stops the menu bar; the daemon keeps running. The menu bar and its notes read in the interface language (ui.locale, else the system’s; see Translate the interface), with the words of the profile’s strings.toml; until the daemon answers they read in English.
Notifications (macOS)
Section titled “Notifications (macOS)”| Notification | Actions |
|---|---|
| Round in 60 s: an automatic round is pending | Run now, Skip, Pause, and a Guide field whose text steers the round and sends it at once |
| Round held: a daily cap held a round back (once per pull request per day) | Run now |
| Agent asks, or Agent stopped | one action per option (the recommended first), Yes and No for a confirmation, a Reply field, Continue, Open |
| Waiting for a permission | Open |
| First round blocked | Open |
| In your Inbox: a task on a unit, such as a Slack thread | Draft for me (delegates headless, when its action can), a Reply field that saves your reply to the Outbox, and a Send reply field that sends it at once; a note says where it went |
Clicking a notification’s body opens its row in the cockpit. Each task in the Inbox, each task anywhere else while a question, post or proposal gate is open on it, and each proposed or failed draft notifies once; a button that runs a task verb runs the verb your profile gives that handler. The rest goes in a daily digest at notifications.digest_at (09:00, "" for none): how many tasks of the badge views moved since the last one, and each badge view’s count, worded by digest_title, digest_new and digest_view in notices.toml. A notification’s titles and buttons are the notify_* keys of notices.toml.
When notifications are off or the menu bar is not running, rounds still run at their deadline: the cockpit shows a warning and doctor names the fix (System Settings > Notifications > wloop, or wloop app install).