Repository navigation
Releases: onurkacmaz/mesa
Release list
v1.8.2 — Google sign-in, and no more accidental reloads
Google sign-in works in a browser pane
Signing in to a Google account from a browser pane stopped at the email step with "This browser or app may not be secure". It now goes through.
On Google's sign-in page alone, the pane presents itself as Firefox. Every other site sees the same browser it did before.
Cmd+R no longer reloads the window
The View menu bound Cmd+R to reloading the whole window, so one stray keystroke restarted every terminal on the canvas. The shortcut is gone. A browser pane still reloads from its own toolbar and menu.
The Touch Bar keeps up (from 1.8.1)
When a workflow ran out of panes, the Touch Bar stopped following changes until the app was restarted. It now picks up new panes, closed ones, focus and names again as soon as there is something to show.
Sessions from 1.8.x
Read unchanged. Nothing needs to be moved or deleted by hand.
v1.8.0 — Your panes on the Touch Bar
Your panes on the Touch Bar
On a Mac with a Touch Bar, the bar now lists the panes of the workflow you are in: the same windows the dock shows, in the same order, under the same names. Tap one and the canvas goes there, keyboard included, exactly as a click on the dock does.
Switch workflows and the strip follows. A workflow with no panes hands the bar back to macOS.
Drawn to match the dock
Each pane is painted for the bar rather than written in the system font, so it keeps what the dock row tells you at a glance:
- the prompt or page mark, so a terminal and a browser pane are told apart without reading
- the name in three tones: the folder, a quiet
->, and the branch a step behind - the amber brackets around the pane that holds the keyboard
Focus changes colour, never width, so nothing on the strip moves under your finger when the selection does. The bar is always drawn in the dark palette, since it is black glass whatever theme the app is in.
Long names
A name that does not fit gives way the way the dock's does: the branch is shortened first, then dropped, and only then is the folder cut, always at the end. A pane you renamed yourself stays one name, even if it has an arrow in it.
Macs without a Touch Bar
Nothing changes.
Sessions from 1.7.x
Read unchanged. Nothing needs to be moved or deleted by hand.
v1.7.0 — A list of what you actually run
Tab takes the completion
Tab is the key a terminal has always used for completion, so it is the key that accepts one now — and Enter is left doing what Enter does, which is run the line.
With the list closed, Tab is untouched and goes straight to your shell, so compsys, zsh-autosuggestions and anything else you have bound to it keep working wherever Mesa has nothing of its own to offer.
The list is ordered by what you actually run
The previous release ordered a command's subcommands by whatever order they appeared in the schema file, which meant typing git opened with archive, blame, commit, config. Nobody's most-used git command is archive.
A schema knows what a command can do and has no idea what you do with it. The history knows exactly. Every schema entry is now credited with the runs of the past commands that start with it, so it is ranked beside them on the same scale rather than ahead of them by category:
gitopens withmergeandcheckout— the two you usedockeropens withcomposeandexecnpmopens withrun
A directory listing is ordered the same way, by what you last touched. ls used to offer node_modules/, dist/ and release/ ahead of the file being worked on, because entries had no quality signal at all and came out in whatever order the filesystem returned them.
Eight rows, eight different ideas
Ranking alone answers the wrong question. It puts the best candidate first, and the second-best is usually a variation of the first — typing claude filled five of the eight rows with claude-work --resume followed by a session id nobody will ever retype, and docker c gave three rows of docker compose --profile. Every one of those really is a command that was run, so they rank alike and crowd out everything else.
Two rules now run after the ranking:
- At most two rows may be variations of one idea. A flag is not a different command, so
ls -l,ls -aandls -lacount as one, and the freed rows go to something else. - A command run once that sits a character or two from one run often is dropped. That is what a typo, a stray trailing slash and an abandoned half-typed line all look like:
docker compoe,cd Projects/app/,npm ryb dev.
The plain command belongs in the list too. Bare ls is the most-run entry in a real history by a distance, and it was being dropped for being exactly what had been typed — leaving only its flag variants, as though running it plainly were not an option.
And an executable you have never run gets at most two rows. ls shares its first two letters with lsvfs, lsmp, lsbom, lsappinfo and lsm; five rows of those is not discovery, it is the alphabet.
Also
- The list sits exactly on the line you are typing on, and never over it. It was positioned by row number, which quietly required its own height to match the rows it had reserved — it never does, so at some zoom levels it hung in mid-air above the prompt.
- The best match is nearest the cursor: at the bottom when the list opens upward, which is the ordinary case since a prompt sits at the bottom of a pane.
Sessions from 1.6.x
Read unchanged. Nothing needs to be moved or deleted by hand.
v1.6.0 — Completions that come from the shell itself
A list of what you could type next
Start typing at the prompt and the commands worth offering appear under the cursor: what you have run before, the files and folders around you, the executables on your PATH, and the subcommands and flags of the command you are actually writing.
↑ ↓ choose, ↵ accepts, esc dismisses. Tab is never taken — it goes to your shell exactly as it always did, so compsys, zsh-autosuggestions and anything else you have set up are untouched.
The best match sits nearest the line you are typing on: at the bottom when the list opens upward, which is the ordinary case, since a prompt lives at the bottom of a pane.
It asks the shell what you typed
Warp can show a list like this because it does not run your shell's line editor — it replaces the input line with its own. Mesa runs real zsh, where ZLE owns the line, so the only honest source for "what is currently typed" is ZLE itself. A hook reports it on every redraw.
The alternative was reconstructing the line from keystrokes, which desyncs the moment you press ↑, paste, or let the shell complete a path — and a list built on a buffer that lies is worse than no list. So there is no fallback: in a shell that cannot report its line, the list simply never opens.
496 commands it knows the shape of
Imported from warpdotdev/command-signatures (MIT). tar - lists its real flags with descriptions, docker its 57 subcommands, git checkout the branches in this repository, npm run the scripts in this package.json.
Nothing from a schema is ever executed. The upstream format carries JavaScript meant to be evaluated by the host, and the import strips it rather than filtering it: a schema may name a generator Mesa already implements, and may do nothing else.
What it offers, and in what order
Ordered by how often you have run something and how recently — a command you use every day outranks one you ran once, and today outranks last month. Matching is by prefix, on the whole line for a past command and on the word under the cursor for a flag. Nothing is offered at a bare prompt: the list should arrive because you started saying something.
Also
- A command run in Mesa is offered again at the very next prompt, rather than waiting for zsh to flush it to disk.
~/.zsh_historyis read and never written to. It is also decoded properly now — zsh metafies that file, and a command with non-ASCII characters in it used to come back as mojibake and never match itself again.- Accepting a multi-line command reproduces it whole instead of running the first line.
Requires zsh
The line feed is a zsh hook. Other shells get no list, and no errors.
Sessions from 1.5.x
Read unchanged. Nothing needs to be moved or deleted by hand.
v1.5.0 — A first launch, and your editor a keypress away
Mesa introduces itself now
A new install used to open on an empty canvas with a legend of keys. It opens on four cards instead: what a place is, how a pane is opened and moved, how the canvas is panned and zoomed, and what a workflow is. Each card draws the part of Mesa it is describing, in Mesa's own panes.
Shown once, and only in front of a canvas that has never held anything — a workspace you already have is not interrupted by it. Skip leaves at any point.
⌘E opens the folder in your editor
The selected terminal's current folder, the one its shell last reported, handed to the editor you already use. The first ⌘E asks which one and remembers the answer; after that it is one key.
⇧⌘E, or a right-click on a pane's title bar, lists the editors on your Mac with the current one ticked — picking another opens the folder and becomes the new default- The applications are read at the moment you ask, so what is offered is what that Mac actually has
- Anything the built-in list has not heard of is reachable through Other application…, and is offered alongside the rest from then on
Mesa does not embed an editor and will not: a native window cannot be adopted into another app on macOS, and the editors that could be embedded are not the ones people use. Handing over the folder is the honest version.
Also
- A restored terminal knows where it is immediately, instead of waiting for its shell to print a prompt. The registry that answers "which folder is this pane in" had been seeded from the pane since panes became boxes of sessions, and a pane has not carried a folder since — so the seeding had quietly been doing nothing.
- What the app remembers about you rather than about your work now lives in
flags.json, beside the session rather than inside it. A session set aside for being unreadable no longer costs an answer you already gave.
Sessions from 1.4.x
Read unchanged. Nothing needs to be moved or deleted by hand.
v1.4.0 — The ropes are gone
The ropes between panes are gone
Panes could once be tied together with a rope: a line saying two windows belong together. The handles that started one were removed when panes gained tabs in 1.3.0 — the tab strip sat where the top handle did — and nothing was left to tie a rope with. The layer behind them stayed in the app regardless.
This release removes the rest of it: the rope layer, its state, its palette and its styles. 1,246 lines out.
- The empty-workspace legend no longer lists Connect or Edit a link
deleteno longer removes anything on the canvas- The minimap draws panes only
Sessions from 1.3.x
A session file written by an earlier build still carries its ropes. It is passed over on read, and the workflow holding it opens with every pane, tab, folder and address exactly as it was. Nothing needs to be moved or deleted by hand.
v1.3.0 — Tabs, session restore, startup commands
Tabs
A pane is a box of sessions now. A window keeps to one kind — terminals or pages, never mixed.
- ⌘N / ⌘B add a tab to the selected window when it can take one, and open their own window otherwise
- ⌘1..9 move between tabs, ⌥⌘1..9 between workflows
- ⌘W closes the innermost thing aimed at: the tab in front, then the window, then the workflow
- Drag a tab along the strip to reorder it. The strip only appears once there is more than one session, so a single-tab window looks exactly as it always has.
Every tab stays mounted and only the one in front is shown, so switching tabs never kills what is running behind them.
Session restore
The app reopens where it was left. Workflows, panes, ropes, the canvas view, every terminal's folder and every browser's address are written to session.json and read back on launch.
A pty dies with the app, so a restored terminal is a fresh shell standing in the folder it was left in — the layout comes back, the processes do not. Sessions from 1.2.x are migrated on first launch.
The file is written atomically and version-stamped; one that cannot be understood is set aside as session.json.bak rather than overwritten.
Startup commands
A tab can carry a command that is submitted at its prompt when the session opens. Stored stripped of every control character, so one saved command can only ever be one command.
Closing only asks when it matters
An idle prompt and a browser page close on the spot. A command actually in flight is worth the question — at the tab, the window and the workflow.
Detecting what is running relies on the zsh prompt hook; other shells report nothing and close without asking.
v1.2.1
Full-screen tools — OpenCode, Claude Code, nano, vim, less — now read in a pane the way they read in any other terminal.
Block and box characters no longer come apart. Running OpenCode in a pane came out shredded: the block letters of its logo sliced by white seams, the prompt box missing its borders. The cause was the renderer. xterm's DOM renderer draws every cell as a text span and takes box-drawing and block characters from the font, and those glyphs only tile seamlessly when a cell lands on a whole pixel — but a pane on this canvas is shown through a scale transform, so at any zoom but 100% each glyph is rasterised at a fractional offset and hairline gaps open between them. Panes now use the canvas renderer, which draws those glyphs as cell-exact vectors that meet at every zoom. Canvas rather than WebGL on purpose: every open tab keeps a live terminal, and WebGL contexts are capped per document with the oldest silently dropped, which would have surfaced as an old pane going blank rather than as anything that reads like a rendering fault.
The shell's command blocks stay off a full-screen tool's screen. Bands of lit rows and hairline rules were being drawn straight across OpenCode's output, at the shell's taller row height. A decoration is painted from the row it marks without regard for which screen that row belongs to, so blocks built at the prompt went on painting through the alternate screen underneath a running tool. They are now taken down when a tool takes the screen and put back, intact, when it gives it up.
Command blocks follow the theme. A block stored its colour at the moment it was made, so every block created in the dark theme stayed a black band on the light screen with its own command text unreadable inside it. A block now stores its role — which row, and whether the command succeeded — and its colour is resolved from the live theme each time it is drawn, so switching themes repaints the whole scrollback.
A full-screen tool follows the app theme instead of being forced dark. The alternate screen used to be pinned to the dark palette whatever the rest of the app was doing, to protect tools that assume a dark terminal. That was far too broad: nano and less emit no colour of their own and simply inherit the terminal's, so on the light theme opening one turned the pane into a pure black slab. The tools it was meant to protect never needed it — OpenCode only queries the terminal's colours and paints every cell of its own background itself.
The dark terminal keeps the app's own warm black. The palette a colour probe is answered with is this app's warm black and amber rather than another terminal's cool blue-black, so a tool that dresses itself from those answers comes out at the same temperature as everything around it.
Also in this release: terminals advertise 24-bit colour, so tools that would otherwise fall back to a 256-colour palette use their full one.
The dmg is unsigned; on first launch, open it from the right-click menu to get past Gatekeeper.
v1.2.0
Terminals that open where you already are, a title bar rebuilt around the app's own mark, one question before anything is closed, and fullscreen that stays inside its pane.
A new terminal opens in the folder the last one you used is in. Every session already reports its working directory on each prompt; that is now kept, and a new terminal starts there instead of in your home directory. Which terminal counts as the last one needs no bookkeeping: the stack already knows, so the topmost terminal wins, browser panes are passed over, and a session that has not printed a prompt yet is skipped rather than sending the search home. A folder that has been deleted or unmounted since it was reported falls back to home, because spawning a shell into a directory that no longer exists left the pane dead with nothing on it.
Closing asks first, whichever way you close. ⌘W asked. The × on a pane's title bar and the × in the dock did not — they went straight to killing the process group and everything running in it. Every route goes through the same question now. The × targets the pane it is attached to even when several are selected; ⌘W still targets the whole selection. Closing a workflow had the same hole from the other side: it asked, except when the workflow was empty, where it closed on the spot.
The title bar is a rail, and the mark is what points. There are no boxes up there any more — no wells around the actions, no container around the theme words, no highlighted tab. State is ink: the workflow you are in is at full strength, the others a step down, the actions below those, the theme words smaller again.
The app's own chevron does the pointing. There is exactly one of them in the window, it stands in front of the live workflow, and it travels along the rail when you switch — the same mark that sits at the prompt inside every pane, so the brand is also the answer to "where am I". Every slot holds the same lane open for it, so no name moves when it arrives.
Two things fixed by measuring rather than looking: the cut between slots sat half a pixel high, and the × on a workflow was drawn at 3.3:1 contrast, which is not a quiet control but an unreadable one. The + that makes a workflow now sits outside the scrolling rail — inside it, it scrolled out of reach exactly when there were enough workflows to need it.
Fullscreen stays inside its pane. A video that went fullscreen in a browser pane used to take the whole app with it, and the whole screen with that. Electron offers no way to confine it: a guest in real fullscreen is pinned to the window however small its pane is. So the request is answered inside the page instead — the element the site asks to fullscreen fills the pane's page area, the page is told it got what it asked for, and Escape puts everything back. The window and the desktop are never involved.
Unsigned build: macOS will warn on first open. Right-click → Open, or xattr -dr com.apple.quarantine /Applications/Mesa.app.
v1.1.0
Shortcut displays, and an English interface.
Shortcuts now have one representation. The same combination was written three different ways — a cap in the canvas legend, a hand-rolled span in the confirmation rail, plain text inside a tooltip — and they had drifted apart:
- Fit-to-content read
⌘⇧0, where macOS composes⇧⌘0 - A plus sign meant "hold this and scroll" in one place and the plus key in another
- A
⏎symbol sat beside the wordescin the same row
Key legends now follow what is printed on the keyboard: the modifiers are the glyphs on the key, the named keys are the words on the key (esc, return, delete).
The caps are laid out rather than padded into shape. A one-character cap and a six-character one are the same object at two widths, centred on the surrounding text. Their fill reads on both the canvas and the confirmation rail, in either theme — in the light theme the old white fill was sitting on a near-white rail and effectively disappeared.
The interface is English. It was previously half Turkish, including the message a terminal prints when node-pty fails to load.
Also fixed: an unstyled button does not inherit the page's type, so the theme switch and every key cap inside a button were rendering in Arial.
Unsigned build: macOS will warn on first open. Right-click → Open, or xattr -dr com.apple.quarantine /Applications/Mesa.app.
Full Changelog: v1.0.0...v1.1.0