The interface
Sidebar, statusline, mouse, steering, and the model picker.
Sidebar & statusline
The Context Dock (sidebar) and the statusline are two forms of one workspace-
context surface. In auto they coordinate so the same value shows in only one at a time:
{
"dock": { "mode": "auto", "position": "left" },
"statusline": { "visibility": "auto" }
}
dock.position—left(default) orright. The dock is a full-height column spanning the whole viewport; the conversation, composer, and statusline share the remaining width beside it.dock.mode—auto(default; shown side-by-side on wide terminals ≥136 cols),shown, orhidden. On narrower terminals an explicit toggle overlays the dock or replaces the conversation.
The dock is a purely informational panel — it has no keyboard section navigation or
collapse. Scroll it with the mouse wheel when its content is taller than the panel; its two
actions are clickable (the MODEL row opens the model picker, Review changes opens Guided
Review) and are also reachable via /model and /diff.
statusline.visibility—auto(default),shown, orhidden. Inautothe statusline is shown unless the full-height sidebar is beside the conversation, so it disappears on wide terminals (the sidebar carries everything) and returns as a compact fallback on narrower ones. When the statusline is hidden the composer bottom-aligns for a clean, minimal look.
The full-height sidebar always carries its own MODEL section (model/context/tokens/cost/
thinking) at the bottom — its visibility is decoupled from the statusline. In auto
you see exactly one copy: beside the sidebar the statusline auto-hides, so the telemetry
lives in the sidebar; on narrower terminals the sidebar is gone and the compact statusline
carries it. Forcing the statusline on with /statusline show while the sidebar is beside
the chat shows both — the sidebar keeps its MODEL footer instead of emptying it.
(Overlay/replacement docks still defer to the visible statusline to avoid a redundant
second copy in those compact modes.)
Toggle either surface at runtime — no restart needed:
| Action | Key | Command palette / slash |
|---|---|---|
| Toggle sidebar | Alt+D | /dock · /dock show|hide|auto |
| Swap sidebar side | — | /dock left|right · “Swap Context Dock side” |
| Toggle statusline | Alt+S | /statusline · /statusline show|hide|auto |
While a turn runs, the row just above the prompt carries a sweeping bar shaded in the theme
accent — the conversation’s own footer, so “still working” sits where you left the output — followed
by the turn’s elapsed time and the live interrupt chord (1:23 · Ctrl+Esc to interrupt). The bar
replaces the older spinner-plus-”working” label: the motion already says it. Running tool cards keep
their own spinner and name, and the row is empty when nothing is running.
Mouse
Luxe captures the mouse by default. The wheel scrolls whatever is under the pointer — the conversation, the Context Dock, or, in Guided Review, the diff versus the file rail — and clicks act on what you click: a dashboard menu row launches it, a Guided Review findings-inspector row selects that finding, with more click targets arriving over time. The keyboard remains a complete interface; the mouse only augments it.
A notch moves the transcript three lines ("conversation": { "scroll_lines": 5 } to change it,
1–20), and the scroll indicator is a control, not decoration: press anywhere on it to jump
there, drag the thumb to scroll continuously — the pointer keeps steering even once it leaves the
one-cell lane — and let go to stop. Dragging to the bottom re-arms follow mode, so new output keeps
arriving in view. PageUp/PageDown still move a full viewport and Ctrl+End returns to the
bottom.
Select and copy with the mouse, no modifier needed. Capture takes the terminal’s own
click-drag selection away, so Luxe implements it: drag across the transcript or the
composer to select, and releasing copies to the system clipboard (the copy-on-select
behavior a terminal would have given you — the composer confirms it with a ✓ copied …
title). A plain click on a transcript message highlights the whole message and copies
that — the highlight and the clipboard never disagree — and clicking it again clears the
highlight without copying. Dragging past the top or bottom of the transcript scrolls it. Esc
clears the selection, and typing replaces a composer selection; Shift+arrows select from the
keyboard and Ctrl+A selects the whole draft.
Because the release already copied, you rarely need a copy key — but Ctrl+Insert,
Ctrl+Shift+C, and Alt+y all copy the selection explicitly. Your terminal’s copy shortcut
only reaches Luxe if the terminal passes it through. Most terminals consume it to copy
their selection, which a captured drag never creates. Two ways to line them up:
- Make the terminal’s copy key fall through when it has nothing of its own to copy. In Ghostty
that’s the
performable:prefix — e.g.keybind = performable:control+insert=copy_to_clipboard— after which a desktop copy shortcut that lowers toCtrl+Insert(Omarchy’sSuper+Cdoes exactly this, viasendshortcut CTRL, Insert) copies Luxe’s selection. - Or hold Shift (Option on macOS) while dragging: the drag goes to the terminal, which makes a
real terminal selection that its own copy key can grab.
/mouse offdisables capture entirely and gives every mouse interaction back to the terminal.
Luxe writes to the desktop clipboard: wl-copy on Wayland (so a Wayland-native paste — a
compositor keybinding, another app — sees it), otherwise the platform clipboard (X11/macOS/Windows),
held by one long-lived owner for the life of the process so an X11 selection doesn’t evaporate the
moment it is set. Only if none of those accept it does Luxe fall back to OSC 52, which is what
makes copy work over ssh or inside a multiplexer (tmux needs set -g set-clipboard on).
Hovering highlights. Pointer motion moves the same highlight the arrow keys set on every
list surface — the command palette, / and @ flyouts, the model, thinking, session, and
project pickers, permission and question prompts, and the dashboard menu — so clicking is
just “confirm what’s under the pointer”. Clickable chrome (the dock’s Review-changes and
model rows, Guided Review’s footer shortcuts) lights up under the pointer too.
| Action | Command |
|---|---|
| Toggle mouse | /mouse · “Toggle mouse support” |
| Enable / disable | /mouse on · /mouse off |
Set "mouse": false in config.json to start with capture off; /mouse on re-enables it
for the session.
Steering a running turn
Hitting Enter while the agent is working doesn’t start a competing turn and doesn’t silently queue behind one — it steers. The message is delivered to the running turn at its next checkpoint (between tool iterations), so the model sees it without losing the work in flight. The turn clock and token counters keep running; only a promoted message starts a new turn.
A steer sent during the turn’s final response isn’t stranded either: rather than ending, the turn takes it in and answers it. And because a reply with no tool calls is what ends a turn, the call that follows a mid-task steer is told as much — address the interruption, then pick the interrupted work back up — so a steer gets an answer without quietly abandoning the task. Whether to stop is still the model’s call, and a steer that asks it to stop is honored.
Alt+Enter (or tui.editor.queue_message, Alt+q) queues the message as a separate
follow-up turn instead, promoted one at a time, in order, as each turn ends.
Until a submission actually takes effect it is not in the transcript — dropped there it
would land wherever the stream happened to be and then scroll away under the turn’s own
output. It waits instead in a muted strip pinned just above the prompt box, marked
steering or queued #1, and joins the conversation at the point it really lands: when the
turn takes the steering in, or when the follow-up becomes a turn of its own. Cancelling
drops what is still waiting and records it in the transcript as never sent, so nothing you
typed disappears without saying so.
Model & thinking level
Switch the active model or its reasoning level mid-conversation — no restart, and the current session carries on:
| Action | Command palette / slash |
|---|---|
| Change model | /model · “Change model” |
| Set thinking level | /thinking · “Set thinking level” |
| Change theme | /theme · “Change theme” |
| New session | /new · dashboard n |
/model opens a searchable picker over every model Luxe knows for the providers it can route
to — your configured ones first, then the rest of the catalog. Selecting one switches the active
model in place, routing to that provider’s adapter (so you can jump across providers, e.g. a
local model to Claude, without relaunching), and the context window, output cap, reasoning
levels, and pricing all move with it. The list is rebuilt each time the picker opens, so editing
models.json mid-session takes effect immediately.
See Model metadata for where that metadata comes from.
/theme lists every theme Luxe ships and previews as you move: arrowing onto a name repaints
the whole app in it immediately, because a palette is not something you can judge from its name.
Enter keeps it and writes it to your config so it survives a relaunch; Esc puts back the one you
started with and records nothing. Your per-token colors overrides ride along in the preview, so
what you are judging is what you would actually get. Writing the config is surgical — the theme key
changes and nothing else in the file is touched.
/thinking lists the reasoning levels (Off … Ultra) gated by the active model: a level it
can’t honor is grayed out, labeled not supported, skipped by the cursor, and inert under a
click, with the model named on the frame — so the list only offers levels that survive the
request. (A level set from config or --thinking is still clamped to the nearest supported one,
and the statusline shows the clamped value.) --thinking <level> sets the starting level at launch
(handy for headless runs, e.g. --headless --provider anthropic --thinking high). /new
starts a genuinely fresh session (the sidebar resets); use the dashboard’s find/recent/grep
or -c/--resume to return to a prior one.
Reasoning is preserved correctly across tool calls. Both Anthropic (signed thinking
blocks) and the OpenAI Responses API (encrypted reasoning items, replayed statelessly via
store:false + include: reasoning.encrypted_content) require a model’s reasoning to be
sent back — verbatim and in its original order — alongside the tool calls it produced, or the
API rejects the follow-up request. Luxe captures each reasoning block (text + its provider
attestation) and replays it unmodified, in the exact order the model emitted it, so
extended-thinking-with-tools works on both providers without the “must start with a thinking
block” / “function_call without its required reasoning item” 400s. Reasoning is never
replayed as plain text on the chat-completions path.