Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra

Changelog

Notable changes, newest first. Versions follow semantic versioning, with the caveat that everything before 1.0 can still move under you.

0.8.12 — 2026-09-05

Added

  • A licence, and what it is for. eterm is free to download and free to use, all of it. A licence from elyracode.com/eterm is a thank-you that turns off the strip which says so now and then — not in the first twenty minutes of a session, not more than once every three days, and never once a licence is in. Nothing else is behind it. The licence is a signed line of text, checked on this machine against a key that can read a signature and cannot write one; nothing phones home. It goes in under settings → Licence, into the login keychain and never into settings.toml, and a licence that is not accepted says why: not a licence, a signature that does not match, or expired.
  • Knowing when a newer eterm exists, and becoming it. Once a day eterm fetches one small file from elyracode.com that says what the newest version is. Nothing about you goes with the request. A newer one is offered under the session: Install fetches the zip, unpacks it, and moves it where the running one is only after the system confirms it is signed by eterm's developer and unchanged since — a bundle that is unsigned, signed by somebody else, or altered is thrown away and the strip says which. Relaunch is a separate press and never automatic, because a terminal has shells in it. Settings → Updates turns the check off, shows what the last one said, and has Check now.

Changed

  • scripts/release.sh writes build/eterm-<version>-arm64.zip and build/latest.json alongside the disk image — the two files the updater reads, for uploading to elyracode.com/eterm by hand.

0.8.11 — 2026-09-05

Added

  • The conversation under a pull request. d puts it where the diff was, and d again puts the diff back. The description first, because it is why the change exists; then the comments under the pull request and the summary of every review with what it decided — approved and requested changes in the accent colour — in the order they were said. A review that said nothing and decided nothing is left out, since its line comments are already on their lines; so is a pending one, which is somebody's unsent draft. Read-only: your own review's summary joins it when you send, and a loose comment in it is a browser's job for now.

0.8.10 — 2026-09-05

Added

  • The code in a diff is coloured as code. Comments, strings, numbers and the language's own keywords, told apart by the file's extension for Rust, TypeScript and JavaScript, PHP, Python, Go, shell, TOML, JSON and CSS. Added and removed lines are now a tint behind the code and a colour on the marker rather than a colour on the whole line, the way a diff reads on the web. No grammar and no parser: four things a reader's eye uses to find its way, and a file it does not know comes out plain rather than wrong.

0.8.9 — 2026-09-05

Added

  • Reply in a thread. r on a line with a comment on it answers in that thread, rather than starting a new one beside it. The reply is held with the rest of your comments until you send, and then goes into the thread it answers — after the review itself, so that if anything fails it is a reply that did not land and not your verdict. Threads draw in the order they were written, and an answer sits a little further in than what it answers; one of yours not yet sent says so.

0.8.8 — 2026-09-05

Added

  • Ask the model about a hunk. cmd-e in the review window sends the selected lines, with a little context, and asks what is worth saying about them as a review comment. The answer does not become a comment: it opens the comment field with itself already typed, and you read it, change it and press return — or escape, and it is gone. The model gets the first word and you get the last. A model with nothing to say says so, and that is shown rather than turned into an empty comment.
  • The session as a runbook. cmd-shift-m puts every finished command on the clipboard as Markdown, in order, with its exit code, how long it took and what it printed — trimmed and redacted the way AI mode does it, since a runbook goes to other people by definition.
  • A strip of blocks. Down the right edge of every session, the scrollback as a map: one mark per command, red where it failed, the part on screen as a band. Where the problems are, before you scroll to them — and the gutter the documentation listed as missing.

0.8.7 — 2026-09-05

Added

  • Secrets on screen are drawn as a bar, and do not leave for a model. cat .env, and the tokens are on the screen — over a shoulder, on a shared screen, and in the context about to go to a model. Anything shaped like a secret is now drawn as a bar in the accent colour, shown only while the pointer is over it. Copying still copies the real text; this is for the shoulder, not for you. hide_secrets under Other turns it off.

    The same shapes become [redacted] in everything that leaves: the screen sent with a first question, the output of every step after it, and a block handed to another pane with cmd-shift-s. The model knows a value was there and does not learn what it was.

    What counts is decided by shape, chosen for precision: GitHub, Anthropic, OpenAI, AWS, Slack, Stripe and Google tokens by prefix and length, JWTs, the header of a private key, and the value of any SOMETHING_KEY=, _SECRET=, _TOKEN= or _PASSWORD= line of eight characters or more — the name stays visible, the value goes. A quoted passphrase is taken whole, spaces included. A commit hash is not a secret, and neither is KEYBOARD=us.

0.8.6 — 2026-09-05

Added

  • A tab that wants you. Agents sit and wait for an answer in panes nobody is looking at. When a program asks for you, the dot on its tab grows to full size and full colour wherever the tab is, and the Dock bounces if eterm is not the app in front. The mark clears the moment you type or click in that session.

    Four ways of asking are heard: the bell, which is what Claude Code sends by default when it needs an answer; OSC 9 as iTerm2 does it; OSC 777 as urxvt and VTE do it; and OSC 99 as kitty does it. And one way of not asking: OSC 9;4, the progress protocol Elyra speaks, where going from busy to not is "I have finished and am waiting" — so Elyra gets its tab without changing a line.

Changed

  • other.bell does something, and is on. It was a switch with nothing behind it. It now decides whether a program ringing the bell counts as asking for you.

0.8.5 — 2026-09-05

Two things a terminal can do once it knows where each command's output begins and ends.

Added

  • Hand a block to the next pane. cmd-shift-s takes the block at the cursor — the command, its exit code when it failed, and its output trimmed with both ends kept — and pastes it into whichever pane had focus before this one, across tabs. Tests in one pane, an agent waiting in the other: the distance between the failure and the question used to be a selection, a copy, a click and a paste. Focus stays where it is. With one pane, or the previous one gone, the text goes to the clipboard instead.
  • Compare a command's output with its last run. cmd-shift-c aligns the two outputs line by line and shows what changed as a diff, three lines of context around each change, in the same window that draws a pull request. Same command text exactly: cargo test and cargo test -- --nocapture are two different things. Two runs that printed the same thing say so rather than showing an empty diff; outputs past four thousand lines say they are too long rather than hanging.

0.8.4 — 2026-09-05

Three of the four things the review window said it did not do in 0.8.0.

Added

  • Other people's comments are shown, under the lines they are about and with who said them. Without this a reviewer is the only person in the room: they leave a comment somebody else left an hour ago, or miss that their question was answered in a thread they never saw. A comment on a line that has changed since — outdated, in GitHub's word — has nowhere to be drawn, and the file header says how many of those there are rather than pretending to know where they go.
  • A range of lines can be selected, with shift-↓ and shift-↑, and c comments on the whole of it. The comment is addressed to the side the cursor is on, runs from the first to the last line the selection has on that side, and comes out the same whichever way it was made. Lines on the other side are left out rather than mixed in: a selection across a removed block and the lines that replaced it is two comments in GitHub's model. Moving without shift lets the selection go, and so does escape.
  • The files are listed down the left, each with what it adds and removes, and a mark beside the ones with something said about them — filled for your own drafts, open for other people's. Click one to show it; tab still cycles. Long paths are cut from the left, because the end is the part that tells them apart.

What is still missing: replying in an existing thread, syntax colouring, and the general conversation under the pull request as opposed to on its lines.

0.8.3 — 2026-09-03

Added

  • Drop a document into a session and its path is written where you are typing. With a space after it, and several files at once become several paths. The session tints while a drag is over it, so it is clear which pane will take it.

    It is not really about the shell. A path is the thing you most often need in the middle of a prompt you are already writing — Claude Code's, Elyra's — and going to fetch one by hand means leaving the sentence.

    A path that can stand bare stands bare, lønnstrinn.pdf included: the shell reads UTF-8, and quoting that would only make it harder to read. Anything the shell would act on is quoted — a space, a $, a backtick, a ;, a glob, a tilde — in single quotes, which bash, zsh and fish all read the same way.

    macOS allows a newline in a filename, and written literally a newline is a return: dropping such a file would run whatever its name said after it. Control characters are spelled out in $'…' form instead, so nothing reaching the shell contains one.

0.8.2 — 2026-08-31

Fixed

  • Typing æ, ø or å went wrong, and the drawing was not to blame. An application launched from the Dock inherits no LANG — launchd has none to give, and eterm never set one. A shell started without a locale works in bytes rather than characters, and the first Norwegian word anybody types shows it:

    with a locale:   æøå  characters=3
    without one:     æøå  characters=6
    

    Backspace then takes half a letter, the cursor moves two cells for one keypress, and every width the prompt calculates is wrong. It explains something that looked unrelated too: a program writing release notes in a session like that sees an environment which never promised UTF-8, and writes paa and loennstrinn instead of and lønnstrinn.

    LANG is now set at session start, from the region macOS is set to, read through CoreFoundation rather than by starting defaults. The keywords macOS hangs off a region are not part of the locale name — en_US@rg=nozzzz is en_US — a region with no installed locale falls back to en_US.UTF-8 rather than making every command print setlocale: cannot change locale, and a LANG or LC_ALL you set yourself is left exactly as it is.

    The grid itself was always fine: æøå, combining marks and emoji have rendered correctly since 0.7.4.

0.8.1 — 2026-08-29

Fixed

  • The review window's heading was drawn on top of the traffic lights. Its title bar is transparent, so the content starts in the corner where macOS puts the close, minimise and zoom buttons. The heading now sits in a strip of the same height that starts to the right of them, which is what the tab strip and the settings window already did.
  • The token row said "repo scope", which is only true of a classic token. It says what each kind needs now: fine-grained wants pull requests read and write and contents read, classic wants repo.

Added

  • Documentation for reviewing, which 0.8.0 shipped without: Review covers turning it on, the keys, why comments wait until the review is sent, and the five things it does not do yet. The keybindings table, the configuration reference and troubleshooting have caught up with it too — including the three different dead ends the window can report, which are three different problems.

0.8.0 — 2026-08-29

Added

  • Review a pull request without opening a browser. cmd-r opens a window on the repository the focused session is standing in: the pull request for your branch when there is one, otherwise the open ones to choose from. ↑↓ moves through the diff, tab changes file, c comments on the line the cursor is on, and cmd-return sends the whole review with a verdict — comment, approve or request changes.

    A window rather than an overlay, because reviewing is not something you do instead of using the terminal. You read a hunk, run the tests, look at the file it calls into, and come back.

    Comments are held until you submit. That is the difference between a review and eleven interruptions: whoever wrote the branch gets one conversation and one notification.

    Nothing shells out. Which repository a session is in and which branch it is on are read from .git/config and .git/HEAD; there is no gh to install and no git to start. The token is a personal access token with the repo scope, stored in the login keychain beside the model key — cmd-,ReviewSet key… — and never in settings.toml.

    The whole feature has a switch. Off, the command does not exist and the window cannot be opened.

    This first version shows your own drafts and not the comments other people have already left, has no key for selecting a range of lines, does not colour the diff by syntax, and cycles files with tab rather than listing them.

Changed

  • The keychain module is no longer part of AI mode. It takes an account name rather than a provider, because two things store a secret now. Nothing moved in the keychain itself: an existing model key is where it was.

0.7.7 — 2026-08-26

Added

  • eterm answers when an application asks about its keys. 0.7.6 made shift-return send ESC CR, which is the convention, and that was enough for Claude Code. It was not enough for anything that asks first.

    An application that wants certainty sends CSI ? ucan you tell my keys apart? — and waits. eterm answered neither that nor xterm's modifyOtherKeys fallback, so a prompt that asked twice and heard nothing twice was left with a shift-return that did nothing at all.

    The kitty keyboard protocol is now answered, the flags pushed with CSI > 1 u are kept, and the keys the old encodings cannot express are reported as CSI code ; modifiers u: return, tab and backspace with a modifier, escape even on its own, and ctrl-i, ctrl-m and ctrl-[ — which are not merely ambiguous but identical to tab, return and escape.

    Everything else keeps the encoding it has always had, which is what that flag on its own means: arrows, function keys, ctrl-a and a plain return are unchanged. Only the first flag is honoured; an application that also asks for key release events or alternate keys gets fewer events than it requested rather than wrong ones.

0.7.6 — 2026-08-26

Fixed

  • shift-return sent the message instead of adding a line. Return runs the line; return with a modifier means not yet, I am still writing. xterm has no way to say that — CR goes down the pty for return, shift-return and option-return alike — so in Claude Code, Elyra and anything else with a prompt that takes several lines, shift-return submitted, and no key added a line at all.

    Both now send ESC CR, the sequence those prompts are built for and the one Claude Code's own /terminal-setup writes into iTerm2 and VS Code. Option does not wait for option_as_meta: that setting exists because option carries the dead keys on a Mac layout, and return types nothing there is to break.

    cmd-return is unchanged and still opens AI mode. Nothing with cmd in it has ever been terminal input.

0.7.5 — 2026-08-22

Fixed

  • Your shell history went into eterm's directory instead of your own. A regression from 0.7.3, when eterm started pointing zsh at a ZDOTDIR of its own: macOS sets HISTFILE from ZDOTDIR in /etc/zshrc, and /etc/zshrc runs before $ZDOTDIR/.zshrc — exactly the window where ZDOTDIR is ours. So everything typed in eterm was written to ~/.config/eterm/zdotdir/.zsh_history rather than the file every other terminal on the machine uses, and nothing said so.

    It cost more than a misplaced file. eterm reads ~/.zsh_history for its own suggestions, so it was reading a file its own sessions had stopped writing to: within a session the commands are held in memory and suggestions work, but after a restart everything typed since 0.7.3 was gone from them. A command run in eterm never appeared in any other terminal's history either.

    HISTFILE is put back when it points inside eterm's directory, after your own .zshrc has run, so one you set yourself is left exactly as you set it. History already written to the old location stays there — merging somebody's history file is not a thing to do behind their back, and it is one cat away from the right place for anyone who wants it.

0.7.4 — 2026-08-22

A pass over the places where something outside eterm reaches something inside it, after a review of the same. Nothing here changes how anything looks.

Fixed

  • A paste could run commands you never approved. Pasted text went to the shell with only its newlines changed, so a clipboard containing ESC [ 201~ closed the bracketed paste eterm had opened around it and everything after it arrived as if typed — newlines already turned into returns, which is a command line that runs itself. Copying an innocent-looking line from a web page was the whole attack. Control characters are stripped now, ESC above all.
  • Quoted text was skipped even when something was going to run it. The risk filter dropped anything in quotes before reading it, so ssh host 'rm -rf /', bash -lc 'rm -rf /' and python3 -c '…rmtree("/")' were all ordinary and ran on one press of return. Quotes are kept whenever the line hands a string to something that executes it, recognised by shape rather than by spelling.
  • Only curl and wget counted as code from elsewhere. cat installer | sh and echo … | base64 -d | sh end in the same place. Anything piped into a shell or an interpreter is now flagged, whatever produced the text.
  • Output is handed to the model as data. What a command prints is written by whatever ran, and any of it can print a sentence addressed to the model. It arrives fenced and labelled at both ends, with the reminder after it so the last word is eterm's. A mitigation, not a fix: the thing that holds is the approval gate, which is why the two entries above mattered more.
  • A wedged child leaked a process for every pane closed. A shell that had stopped reading its input left the writer thread blocked, so Shutdown was never read, the pty was never dropped and the child was never hung up on. Closing a session now hangs up on the child directly.
  • Command marks landed on the wrong line under fast output. The line was read when the UI thread got round to the mark rather than when the mark arrived, so a quick command could fold somebody else's output. Each mark now carries the line it arrived on.
  • An unterminated OSC could exhaust memory. An ESC inside the string was buffered without checking the limit, so printing the right file at eterm grew it without bound. It ends the string now, which is what the emulator's own parser does with it.
  • Combining characters shifted the colours along the rest of the line. The marks went into the row's text without going into the run describing it. macOS hands out decomposed filenames, so ls of a directory with an é in its name was enough to see it.
  • The emulator's lock was held through text shaping, which is the pty thread blocked for most of every frame under heavy output. Cells are collected under the lock and shaped after it.
  • Composing with an input method after non-ASCII text could crash. The selection was measured against the whole field and then offset by the start of the marked text, counting the beginning of the line twice.
  • bash's line went into a file a login shell never reads. .bashrc was true about the file and false about the shell: on a Mac with no .bash_profile, installing reported success and nothing happened. The line goes to the first of .bash_profile, .bash_login and .profile that exists. sh is no longer treated as bash, since it reads neither.
  • A silent provider left AI mode saying "answering…" forever. The client had no timeout at all; it now has three.
  • The shader surface could show a frame of black. eterm draws it on its own Metal queue and gpui samples it on another, and Metal does not order work between two queues — measured, the drawing finishes 0.5ms after commit when idle and 9ms at worst under load, against a deadline of about a millisecond. The buffer handed over is now one drawn during an earlier frame, which costs no waiting.
  • Smaller ones. > /dev/null 2>&1 & is no longer flagged as emptying a file, and a proposal of more than one command now needs the second key, since one press is an approval of one command. Cycling tabs no longer divides by zero in the moment after the last one closes. width = nan in the session file no longer makes a window nobody can find. An rc file that is not UTF-8 is read rather than treated as empty and appended to again. A slow trackpad scroll accumulates instead of rounding away to nothing.

0.7.3 — 2026-08-22

Added

  • Shell integration installs itself, for zsh. Everything eterm knows about a command — where its output starts, when it ended, what it exited with — comes from OSC 133, and OSC 133 came from a line somebody had to go and add to their rc file. Nobody goes and adds it. On a machine whose owner never opened the settings window, eterm was a plain terminal with no colour on the command line, no folding and no prompt jumps, and nothing on screen said so.

    zsh reads its startup files from ZDOTDIR, so every zsh session eterm starts is now pointed at a directory of its own: four files in ~/.config/eterm/zdotdir, each sourcing your own .zshenv, .zprofile, .zshrc or .zlogin and then handing ZDOTDIR back. Your configuration runs exactly as it would have, a shell started inside the session is an ordinary shell again, and nothing is written into your home directory. ZDOTDIR in [shell.env] overrules all of it.

    bash keeps the line, and the settings window keeps the button for it: --rcfile is read only by interactive non-login shells and BASH_ENV only by non-interactive ones, and neither is the login shell a terminal starts.

  • A shell that reports nothing now says so. Two returns from a session that has never reported anything raises a strip above the footer — this shell is not reporting: nothing folds, nothing jumps, and the command line stays grey — with a button that adds the line to the rc file it belongs in. Once per window, and only when there is something to offer: a shell nobody here ships a script for would be a complaint with no remedy attached.

Fixed

  • A prompt framework could throw the prompt mark away. zsh hooks run in the order they were added, and starship and powerlevel10k rebuild PROMPT in their own precmd. Registered first, ours decorated a prompt that was about to be replaced, and the mark that says where typing starts never reached the screen — so the command line stayed grey for exactly the people most likely to notice. The script removes its hooks before adding them now, which also means sourcing it twice leaves one hook rather than two.
  • A ~ in [shell.env] reached the shell as a tilde. Only PATH was expanded, and the shell never sees these values to expand them itself, so ZDOTDIR = "~/.config/zsh" was a variable pointing at a directory that does not exist. A leading ~/ is expanded in any of them now.

0.7.2 — 2026-08-22

Fixed

  • A long answer ran off the right edge of the panel. It now wraps. That is not tidiness: a command you can only see the first half of is not a command anyone can approve, and approving is one keypress.

    The first attempt made it worse — telling the flex child it could shrink let it shrink to nothing, and the command disappeared entirely — because gpui wraps text in a block and not in a flex child. The marker gives up its colour and joins the text in one block instead.

  • It was not clear that a follow-up question was possible. The question stayed in the field after the answer arrived, so return asked the same thing again and nothing suggested the conversation could continue.

    The line empties when an answer lands, what you asked moves above the answer, and the hint says what return will do. The rule is short enough to guess: return on an empty line takes the answer, return on something you have typed asks it.

0.7.1 — 2026-08-22

Added

  • The AI conversation belongs to the session, not to the panel. Ask something, approve the command, watch it run — and the panel used to close with the thread inside it. Opening it again two seconds later met a model that had never heard of what you had just done together. The thread stays now; reopening shows how many exchanges there are and the last thing that was said, and cmd-delete ends one.
  • What you did while the panel was closed is part of it. eterm knows every command that ran and how each one ended, so a resumed conversation opens with it — "Since we last spoke I ran: cargo buld --relase (failed with exit code 101)". Asked what they did wrong, the model answers without being told what they did. The last ten commands are reported; anything older is counted rather than recited.

Fixed

  • A settings file that could not be read was replaced by defaults. Not at once: settings are saved on every change, so defaults loaded into memory became defaults on disk the moment anyone touched a toggle, and the only warning went to stderr where a Dock launch cannot show it. One bad character cost a font, a palette and a model.

    An unreadable file is now moved to settings.toml.broken and the settings window says why along the bottom, on one line and with the line number. The previous file is kept as settings.toml.bak before each save, and saving writes a temporary file and renames it over the old one, because a rename is atomic and a write is not.

    Upgrading never touched settings — they live in ~/.config, not in the bundle — but a file that would not parse did, and that was the real hazard.

0.7.0 — 2026-08-22

Added

  • The command palette offers what this project can do. cmd-shift-p in a directory with a package.json, composer.json, Makefile or justfile lists the commands written down in it — npm run dev, composer test, make deploy — above the application's own, with the file each came from beside it. Choosing one runs it.

    The list is read from the directory the focused session is standing in, so moving to another project gives another project's commands. Nothing is guessed: a command is offered because a file in front of you declares it.

    The package manager comes from the lockfile, not from habit: pnpm-lock.yaml means pnpm run, yarn.lock means yarn, bun.lock means bun run.

    Hooks are left out. Composer's post-update-cmd and npm's prebuild run themselves, and in a Laravel project the four composer hooks pushed test and dev off the top of the list, which is the opposite of the point.

0.6.1 — 2026-08-22

Fixed

  • cmd-e was missing from the keymap and the command palette. The key worked; the two places that tell you what the keys are had never heard of it. There were three hand-kept lists — the bindings in main, the rows in the Keys pane, and the palette's commands — and a new key had to be added to all three by hand.

    They are now generated from one table in ui::actions, so a binding cannot exist without appearing where people look for it. Four tests hold the table to its own rules: everything that fires in a session is listed and named, a row is contextual exactly when its key context is, and no two commands claim the same key in the same place.

0.6.0 — 2026-08-21

Both of these come from the same small change: a block now remembers what was run and how long it took. That was already known at the moment output started and thrown away a line later, and nobody can recover it afterwards by reading the screen.

Added

  • The Dock icon asks for you when a long command finishes. Only when eterm is not the application in front — you are already looking at it otherwise — and only past other.notify_after seconds, which defaults to 20 and is turned off by zero. A command that succeeded bounces once; one that failed keeps bouncing until you come back, because a single bounce in an empty room is not a message.

    The Dock rather than a notification: notifications need permission that has to be granted, and do not arrive at all if it was refused three months ago. A bouncing icon has needed no permission since 1997 and means exactly this.

  • cmd-e asks why the last command failed. Everything the question needs is already known — what was run, how it ended, what it printed — so nothing has to be typed. The answer arrives in the same panel as any other proposal and goes through the same approval, so an explanation becomes a fix without leaving the keyboard. Tried against a real failure: cargo buld exited 101, and one key later cargo build was waiting to be approved.

  • ETERM_SELF_TEST=explain and =notify drive both from inside the app.

0.5.1 — 2026-08-21

Fixed

  • Return now takes the completion you chose. Choosing README.md in the list and pressing return submitted cat re — the half-written line — and the shell said no such file. Return was never intercepted, on the reasoning that it must always be able to submit. That holds while nothing is chosen, and stops holding the moment somebody has deliberately picked an item out of a list: every list anyone has ever used takes return. It now accepts when something is selected, and submits when nothing is.
  • A fully typed path is no longer offered as a completion of itself, which left the list on screen after it had been accepted. A directory is still offered once more, because resources really can become resources/.

0.5.0 — 2026-08-20

Added

  • Suggestions from history. The rest of a command you have run before appears in grey after the cursor, and the right arrow takes it. A match from the directory you are standing in beats a more recent one from somewhere else, because make deploy in one project and make dev in another are not interchangeable.

    History comes from two places that cover for each other: the shell's history file, which knows everything up to the last time a shell exited, and eterm's own record of this session, which knows the last ten minutes exactly. zsh does not write its file until the session ends.

  • Completions while typing. A list of commands, options or paths under the word being typed, whichever of the three it is. Commands come from PATH and the builtins, with the ones you have actually run offered first — cargo before carbon. Options come from what that command has been given before, which is a smaller list than a database of every tool's flags and a truer one. Paths come from the filesystem, with directories marked.

    Nothing is selected until you press an arrow, so the right arrow keeps meaning what the grey text says. and choose, takes it, escape closes the list and still reaches the shell.

    Tab is not touched. The shell's own completion knows about git branches, ssh hosts and remote paths, and ours does not; taking that key would be a downgrade dressed as a feature.

    Both can be turned off under Shell in settings.

0.4.1 — 2026-08-20

Fixed

  • Line height, padding and letter spacing could not be changed. Every stepper on a pane was built with the same two element ids — stepper-- and stepper-+ — so gpui routed all of their clicks to the first row on the pane. Pressing + beside Line height changed the font size instead. The id now carries the row's own label, and the helper requires one, so the mistake cannot be made silently again.
  • Stepping a number lands on the number it shows. Adding 0.05 to 1.40 in binary floating point gives 1.4499999, and that is what was being written into settings files.

Added

  • scripts/testing/scroll.swift, which posts a real scroll wheel event and, like the other two helpers, refuses unless eterm is frontmost. Verifying anything below the fold needed it.

0.4.0 — 2026-08-20

Added

  • The command line is coloured as you type it. The first word turns green when it is a command that exists on your PATH or a shell builtin, quoted strings are yellow, and flags, operators and comments are dimmed. Every command in a pipeline is checked, sudo apt marks apt as the real command, and RUST_LOG=debug cargo test still knows cargo is what runs.

    A word we do not recognise is left exactly as it was rather than marked wrong: it may be an alias, a shell function, or something about to be installed. A missing highlight goes unnoticed; a wrong one teaches people to distrust the colour.

    Cells the shell has already coloured are left alone, so prompt frameworks and zsh-syntax-highlighting keep their own work. It needs shell integration, which is what says where the prompt ends, and can be turned off under Appearance. The cost is not measurable: 0.013ms per row against 0.014 without it, which is the difference between two runs.

  • Shell integration now emits OSC 133;B, the mark for where the prompt ends and typing begins. It lives inside PROMPT/PS1 rather than in a hook, because the prompt is drawn after the hook has returned.

  • The list of runnable commands is re-read half a minute after a command finishes, so something you have just installed lights up without a restart.

0.3.0 — 2026-08-20

AI mode can now finish a job rather than hand you the first step of one.

Added

  • Approve and run. A proposed command is shown, and return runs it. The rule has not changed — nothing runs that a person did not approve — but approval is now one key rather than a trip to the input line. cmd-return still puts it on the input line to edit instead.
  • A second thought where it belongs. A command that looks destructive is named as such — this deletes files recursively or without asking — and return will not run it. That takes cmd-shift-return, a chord nobody presses by reflex. Around forty patterns, from rm -rf and dd of= to git reset --hard, curl | sh and kubectl delete.
  • Tasks, not just commands. After an approved command finishes, the model is told the exit code and the output, and proposes the next command. Each step is approved separately, a task stops after eight commands, and the model ends it with a sentence rather than another command. Watching it read its own output and correct itself is the point: asked for the largest source file, it ran wc -l, saw that it had only produced a total, and fixed its own command.
  • An Install button for shell integration, in settings under Shell. It appends one marked line to .zshrc or .bashrc and touches nothing else.

Fixed

  • Shell integration never worked in zsh. local status=$? is correct bash and an assignment to a read-only variable in zsh, so the hook failed on its first line at every prompt and emitted nothing at all: no working directory, no command marks, no folding — in the default shell on macOS. scripts/testing/shell-integration.sh now runs both shells in CI and reads what they emit.
  • Folding the command you just ran did nothing. It required the cursor to be inside the block, and after a command finishes the cursor is on the next prompt — which is the only moment anyone reaches for it. Standing at a prompt now folds the block that just finished.
  • An approved command is no longer joined onto whatever was half-typed on the input line.
  • The system prompt told the model it could only suggest, so asked what it could do, it correctly answered that it could not do anything.

0.2.0 — 2026-08-20

The release where eterm became usable as a daily terminal rather than a rendering experiment.

Added

  • Tabs and splits. A window holds tabs, a tab holds a tree of panes, and dividers can be dragged. Splitting along an axis a pane already sits in adds a sibling instead of nesting.
  • Command blocks and folding. With shell integration, cmd-shift-enter folds a command's output to one line, and cmd-up/cmd-down jump between prompts. Shell integration for bash and zsh ships in shell/.
  • AI mode. cmd-enter asks Anthropic or OpenAI for a command. The answer is put on the input line and never run for you. Keys live in the keychain.
  • Find in session, with match highlighting, using the emulator's own regex search over the scrollback.
  • A command palette over every action, filtered by subsequence.
  • Mouse reporting, so full-screen programs see clicks, drags and the wheel, in both the packed and SGR encodings. Word and line selection, and cmd-click on OSC 8 links through a scheme allowlist.
  • Input methods. The grid registers as an input client, so dead keys and input methods compose before anything reaches the shell.
  • A real application. An app bundle with an icon, a menu bar, cmd-n windows, Dock reopen, and a session that restores window size, tabs, splits and directories.
  • PATH control. shell.path puts directories in front of the inherited PATH, which is what makes tools reachable when launched from the Dock, and the Shell pane reports which of them are found.
  • A shader surface behind the grid for palettes that ask for one, drawn by a Metal fragment shader at 0.10ms a frame.
  • Continuous integration: formatting, clippy with warnings denied, tests and the bundle script on every push.

Changed

  • gpui now comes from a pinned checkout of zed's main rather than the crates.io release, with scripts/gpui.sh to fetch it and build.rs warning on drift.
  • Sessions start in the home directory unless the settings, a restored session, or the pane you are in says otherwise. A new tab or split opens where you are working.
  • Printable characters are no longer sent from the key event; they arrive through the input handler, which is what allows composition. The option key is a setting (keys.option_as_meta) because it cannot be both a compose modifier and meta.
  • The renderer line in settings asks the device instead of claiming "metal 3".
  • Local installs are signed with a Developer ID when one is present: the keychain grants access per signature, and an ad-hoc build asks again after every rebuild.

Fixed

  • Escape closes the command palette wherever focus went, and an overlay replaces the key context rather than extending it, so cmd-v reaches the field it is aimed at rather than the shell behind it.
  • Hit testing uses the bounds the grid actually painted at, rather than deriving them from padding, which was two rows off once tabs and panes existed. Text selection had the same fault.
  • The API key field no longer pushes its own save button off the window.
  • The keychain refusing access is reported as a refusal rather than "not set".
  • Opening the settings window makes it the window receiving keys, so the shortcuts its own footer advertises work.
  • The font catalogue is read once rather than once per frame, which cost 20ms of every frame.

0.1.1 — 2026-08-19

Fixed

  • Sessions start in the home directory rather than wherever the process was launched from, and cmd-n opens a new window instead of restoring the saved session into it.

0.1.0 — 2026-08-19

First release. A terminal on gpui: pty and emulation through alacritty_terminal, a custom element that paints one shaped line per row, 22 palettes, and a settings window backed by a TOML file.