Skip to content

Releases: benyblack/ntilde

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 09:19
b58c022

0.11.0 makes Ntilde feel native on macOS, where Cmd now does what Cmd does everywhere else,
and closes several holes where program output or clipboard content could do more than it
should: a crafted paste can no longer end the paste block early, a file: link is never
executed, and a Sixel image can no longer freeze a pane. It also fixes character widths for
emoji and CJK, sends Ctrl/Shift/Alt with arrow and function keys, stops SSH jump chains from
sending a password to the wrong host, and moves to Avalonia 12.1.

Installing and upgrading

Already running Ntilde (or NovaTerminal) from the Windows installer, the macOS .pkg or
the AppImage?
Nothing to do. The update is found in the background and applied on your next
restart.

Installing fresh:

  • Windows — ntilde-Setup-win-x64-v0.11.0.exe. Per-user, no admin prompt, self-updating.
    ntilde-win-x64-v0.11.0.zip is the portable build, with no updater.
  • macOS (Apple Silicon) — ntilde-Setup-osx-arm64-v0.11.0.pkg, signed and notarized.
    ntilde-osx-arm64-v0.11.0.zip holds the same Ntilde.app for a drag install.
  • Linux — ntilde-linux-x64-v0.11.0.AppImage (or -arm64), self-updating;
    ntilde_0.11.0-1_amd64.deb (or _arm64); ntilde-linux-x64-v0.11.0.tar.gz (or -arm64),
    portable. glibc 2.35 or newer.
  • Arch — PKGBUILD-v0.11.0 and SRCINFO-v0.11.0 are attached; makepkg -si builds
    ntilde-bin.

macOS: Cmd is the shortcut key

  • Cmd+C / Cmd+V copy and paste. Cmd+C keeps the selection highlighted, as in Terminal.app
    and iTerm2.
  • App shortcuts moved from Ctrl to Cmd: Cmd+T new tab, Cmd+W close tab, Cmd+, Settings,
    Cmd+F find, Cmd+Shift+P command palette, and so on.
  • Ctrl is the shell's again. Ctrl+C is always the interrupt, even with a selection, and
    Ctrl+R, Ctrl+W, Ctrl+F and Ctrl+V reach readline instead of triggering app commands.
  • A few keep Ctrl because macOS owns the Cmd form: Ctrl+Tab / Ctrl+Shift+Tab to switch
    tabs (Cmd+Tab is the app switcher) and Ctrl+Space for Command Assist (Cmd+Space is
    Spotlight).
  • Custom shortcuts can use Cmd, and the Settings recorder captures it. Bindings you already
    saved are used exactly as written. Windows and Linux shortcuts are unchanged.

Security

  • Bracketed paste can no longer be broken out of. A clipboard snippet could assemble a
    paste-end marker that survived the old filter, so text after it arrived as typed input and
    could run commands. Every ESC and 8-bit CSI is now removed from pasted content, which leaves
    no terminator in any spelling.
  • file: links are never executed. A link's visible text can differ from its target, so a
    Ctrl+click on what looked like a web link could run a local program or, on Windows, connect to
    a remote SMB share and offer your credentials. Local file links now only reveal the file in
    Finder, Explorer or your file manager; remote ones are refused, and are no longer underlined.
    Links that name this machine's own hostname (as ls --hyperlink, rg, fd and eza write
    them) are treated as local.
  • SSH jump chains send each password only to its own host. With a password-auth bastion, the
    target's saved password could be sent to the bastion, the bastion's password replayed to the
    target, and SFTP could send one password to every hop. Each prompt now names the host asking,
    and passwords are kept per host.
  • Sixel images are bounded. A 20-byte sequence with a huge repeat count could freeze a pane
    and exhaust memory. Images are now capped at 2000 pixels per side, the same limit as kitty and
    iTerm2 images.
  • On Linux, xdg-open is started from its absolute PATH location, so a copy in the current
    directory cannot stand in for it.

Fixes

  • Character widths come from Unicode data. Emoji such as ✅ ❌ ⚡ ☕ ⭐ are now two cells, as glibc, Node and Rust measure them, and
    narrow symbols (legacy-computing sextants, alchemical symbols, arrows, chess) are no longer
    drawn wide, so TUIs stop misdrawing lines that contain them. Emoji with VS16 widen only when
    Unicode says they should, and keycaps such as 1️⃣ now take two cells.
  • Modifiers reach arrow, editing and function keys. Ctrl+Left/Right jumps words in bash,
    zsh and PowerShell, Shift+arrows select in editors, and Ctrl+Delete deletes a word, using
    xterm's encoding.
  • Theme import: importing a native Ntilde theme .json works again (it silently did
    nothing), and a theme whose name contains /, \ or : imports, saves and deletes cleanly.
  • Quake mode (Windows): hiding and showing the window no longer leaks a hotkey hook, which
    could crash the app after a garbage collection.
  • Captures and exports (agent snapshots, PNG export) no longer leave stale cells in the live
    view.

Under the hood

  • Avalonia 12.0.4 → 12.1.3.
  • NTILDE_RENDER_METRICS and the render HUD now show which Skia backend drew each frame
    (for example GPU/OpenGL, or Software when there is no GPU context). The metrics file is flushed on exit and never
    ends in a half-written line.

Full Changelog: v0.10.0...v0.11.0

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 13:12
74b6b99

NovaTerminal is now Ntilde. 0.10.0 is the first release under the new name: same
terminal, new binaries, new package names, ntilde on the command line. It also adds an
Interface scale that zooms the whole window independently of the terminal font, fixes the
lag that crept into a long-lived SSH tab, implements REP so ncurses borders and rules are
drawn at full length, and lets the SSH connection dialog scroll instead of clipping its form.

Installing and upgrading

Already running NovaTerminal from the Windows installer or the macOS .pkg? Nothing to
do. Your install keeps checking the same update feed, and the next update it finds is this
one. It applies in place on your next restart: the install folder stays where it is, the
shortcuts are renamed to Ntilde, and the app comes back as Ntilde with your settings already
migrated. (The spec for the rename assumed the old updater would not follow across the
name change. It does, and this was verified against a real 0.9.0 install before tagging.)

Running the AppImage? Same story. The updater rewrites the AppImage in place, so the file
keeps whatever name it has on disk; rename it when you like.

Installing fresh:

  • Windows — ntilde-Setup-win-x64-v0.10.0.exe. Per-user, no admin prompt, self-updating.
    ntilde-win-x64-v0.10.0.zip is the portable build, with no updater.
  • macOS (Apple Silicon) — ntilde-Setup-osx-arm64-v0.10.0.pkg, signed and notarized.
    ntilde-osx-arm64-v0.10.0.zip holds the same Ntilde.app for a drag install.
  • Linux — ntilde-linux-x64-v0.10.0.AppImage (or -arm64), self-updating;
    ntilde_0.10.0-1_amd64.deb (or _arm64), which declares Replaces/Conflicts on
    novaterminal so apt install ./the-file.deb upgrades the old package cleanly;
    ntilde-linux-x64-v0.10.0.tar.gz (or -arm64), portable. glibc 2.35 or newer.
  • Arch — PKGBUILD-v0.10.0 and SRCINFO-v0.10.0 are attached; makepkg -si builds
    ntilde-bin, which provides, conflicts with and replaces novaterminal-bin.

Still on a portable zip or tarball? Download the new one and delete the old copy. Portable
builds have no updater and cannot bring themselves forward.

What the rename means for you

Everything user-visible is renamed; the things that would cost you data are kept compatible.

Settings carry over. On first launch Ntilde copies settings, themes, connection profiles,
workspaces, snippets, backups and history from the NovaTerminal data folder into its own
(%LOCALAPPDATA%\ntilde on Windows, ~/.local/share/ntilde elsewhere). Newer file wins,
logs is skipped, the old folder is left untouched, and a marker file stops it running
twice. The marker is written only when every copy succeeded, so a transient failure retries
on the next launch instead of leaving one file permanently behind.

Saved passwords do not. Keychain, Credential Manager and Secret Service entries live under
the new name. Re-enter SSH passwords once; the profiles themselves are already there.

Old files still open. Backups now export as .ntildebackup, workspaces as
.ntildews.json, recordings with an ntilderec header, and the readers still accept
.novabackup, .novaws.json and novarec. Existing snapshots stay listed in Backup &
Restore.

The command is ntilde (was nova). Environment overrides are NTILDE_* (was
NOVATERM_*), for example NTILDE_APPDATA_ROOT. MCP tools are prefixed ntilde..

Remote shell integration writes ~/.ntilde-shell-integration.sh (or .ps1). Re-run the
installer from Settings on each host, and remove the old ~/.nova-shell-integration.* loader
line from your rc file or $PROFILE by hand.

A new icon. The ñ mark replaces the old one everywhere it is consumed: the Windows ICO,
the macOS .icns, the Linux hicolor sizes and AppImage icon, the site favicon and social
card. All derive from one master PNG, so the packaging lanes did not change.

Interface scale

Font size only ever reached the terminal. On a dense display the tabs, sidebars, dialogs
and Settings itself stayed small however large the terminal font got, and roughly 140
hardcoded font sizes across the UI meant "scale the fonts" was never going to work. 0.10.0
takes the route Warp and VS Code took: one zoom for the whole window, separate from the
terminal font.

Settings → Appearance → Window → Interface scale, 80% to 200%, with live preview. Ctrl+=
and Ctrl+- keep zooming the terminal font only.

Terminal text stays sharp. The terminal rasterizes its glyph atlas at the monitor's
render scale, and under a plain layout transform those bitmaps were then enlarged, so at 150%
terminal text was visibly soft while the tab title beside it stayed crisp. The terminal now
folds the interface scale into its render scale and rebuilds the atlas, the same path a DPI
change takes, and it reads the scale from its own window, because a window can render at a
different scale than the app (the Settings window holds the scale it opened with so the
slider does not run away from the pointer; a window the screen cannot fit renders smaller).

Fixed-size dialogs fit. Settings, the SSH connection dialog, the Connection Manager,
About, Replay, transfers, prompts and the askpass helper are sized for the scale in effect,
reduced to what the screen can hold rather than clipped, refit when the scale changes from
their current size so your own resizes survive, and left alone while maximized. A dialog
that lands on a smaller secondary monitor is re-checked against that monitor once it opens.

The askpass helper follows. OpenSSH launches it as a separate process with its own tiny
application, so it never saw the app's theme. It now installs the same window theme and reads
the saved scale from settings.json, falling back to 100% on any problem so authentication
is never blocked by a settings file.

Kitty in-band resize reports true pixels. The mode 2048 report multiplied the grid by
the cell size in DIPs, which is only pixels at 100% on a 96-dpi monitor. It now uses the
effective render scale, monitor times interface scale, so a terminal browser is told the
pixels the terminal actually occupies.

Known gap: popups (context menus, flyouts, tooltips) live in their own top-levels and stay
at 100% for now.

SSH: the tab that got laggy after a day

Field report from 0.7.0: an SSH tab left open for about 24 hours on an Apple Silicon Mac
became laggy, with a suspected memory leak. The VT core was soak-tested first, 127k lines
through the real parser at 220×50 with alt-screen cycles, marks, emoji and links: memory
saturates at 32 MB and stays flat. The leak was not in the buffer. Two defects, only on the
SSH path, compounded instead.

REP (CSI Ps b) was unimplemented. We advertise TERM=xterm-256color, whose terminfo
declares rep, so ncurses on the far end uses it to draw every run of repeated characters:
box borders, rules, padding, meter bars. Measured: - followed by CSI 39 b drew one cell
instead of forty. Every such run also fell through to the "unhandled CSI" diagnostic. REP
is implemented per ECMA-48: it repeats only the immediately preceding graphic character, the
repeatable character is tracked in its own state (the cell under the cursor cannot say what
was last written), a control or cursor move clears it, a surrogate half or combining mark
yields no repeat rather than half a grapheme, the count is clamped to one line, and the
charset-mapped glyph is what repeats, so DEC special graphics works.

Every unhandled sequence wrote to disk, synchronously, from the parse thread. The app
logger was an open-append-flush-close per call under a process-global lock, with no size cap
and no level filter. One 50-row ncurses redraw became about 150 synchronous disk writes,
serialized against the UI and render threads, and debug.log grew for the life of the
process at 1 to 10 GB a day.

parse cost per frame debug.log growth
0.9.0, logging sink wired 1.24 ms 1–10 GB/day
0.9.0, sink disabled 0.23 ms —
0.10.0 0.82 ms bounded at 16 MB

The 0.82 ms is not like-for-like with 0.23: it now draws the 6000 cells per frame it used
to discard. Three layers replace the old sink: a minimum level (NTILDE_LOG_LEVEL, default
Info) so per-sequence diagnostics are filtered by an enum compare before any work happens;
rate limiting by message shape in front of every parser call site, with errors never muted
and a periodic report of what was swallowed; and a rotating writer with a bounded queue, one
background thread, 16 MB rotation and a drain on exit. A log storm now costs dropped lines,
counted and reported, not memory or latency.

The per-chunk UI refresh is coalesced. The pane posted a dispatcher job for every chunk
read from the session, so the queue grew with the remote's send rate rather than the UI's
drain rate, each closure doing the same work as the one ahead of it. Everything that job does
is a level, not an edge, so one pass after the last chunk is all that was ever needed. A
single-flight flag guarantees it, cleared at the start of the pass so a chunk arriving
mid-pass queues the next one instead of going unshown.

The SSH connection dialog scrolls

The Basic tab of New/Edit Connection has eleven form rows plus a Notes box inside a bare
grid, so at the default height the bottom rows (Favorite, Notes) were cut off with no
scrollbar; the only way to reach them was to resize the window by hand. Every tab now scrolls,
the footer with warnings and Cancel/Save/Connect stays pinned, and the default size grew to
760×680 with a 640×480 minimum so the common case fits without scrolling.

Also in this release

  • The Homebrew tap lane is in the repo, secret-gated in the release workflow. The tap itself
    is not live yet; the README will say when it is.
  • Debian, Arch and Velopack packaging carry the new identity, and the Velopack pack ID
    (NtildeApp) de...
Read more

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 06:05
92e7795

What's Changed

  • site: bring the Install section and feature list up to date for 0.8.0 by @benyblack in #396
  • fix(assist): apply the OSC 133 lifecycle gate on the thread the bytes arrive on by @benyblack in #448
  • fix(security): resolve the SonarCloud code-scanning alerts by @benyblack in #421
  • ci(release): stop untrusted code writing the release caches by @benyblack in #450
  • fix(app): enable X11 drawn decorations so Linux gets caption buttons by @benyblack in #451
  • fix(app): measure the caption-button reserve instead of hardcoding 140px by @benyblack in #452
  • feat(vt): kitty graphics end-to-end — native APC on Windows, t=f transport, o=z, raw RGBA, in-band resize, CSI 14/16 t by @benyblack in #449
  • feat(arch): gated novaterminal-bin AUR package (#385) by @benyblack in #455
  • fix(input): smooth wheel gesture reaches its target and survives live output by @benyblack in #456
  • fix(release): create the tag on the commit being built by @benyblack in #457
  • fix(vt): make the kitty snapshot-gate test ordering-determined, not clock-determined by @benyblack in #458
  • fix(tests): make the tab-overflow precondition hold by construction, not by 3px by @benyblack in #462
  • fix(tests): keep AgentHost socket paths under the macOS sun_path limit by @benyblack in #461
  • ci: run the unit test suite on linux-arm64, nightly and on dispatch by @benyblack in #459

Full Changelog: v0.8.0...v0.9.0

v0.8.0 — Markdown agent output, an About window, icon glyphs out of the box

Choose a tag to compare

@github-actions github-actions released this 08 Sep 13:10
c2fdec0

NovaTerminal 0.8.0 gives agent output a reader: a side panel that renders what an agent
printed as markdown, with fenced code, tables and diffs, instead of as a wall of terminal
text. It also adds an About window with a working update check, bundles the icon glyphs a
fresh install was missing, and fixes a command palette that rendered blank in released
builds — along with the release gate that would have caught it.

Installing and upgrading

On Windows? If you installed with NovaTerminal-Setup-win-x64-*.exe, 0.8.0 arrives on
its own — check for the update toast and it applies on your next restart. You can also ask
now, from About NovaTerminal… in the title-bar flyout.

On macOS? Same, via NovaTerminal-Setup-osx-arm64-v0.8.0.pkg. Signed and notarized, as
of 0.7.0.

On Linux? Three ways, unchanged from 0.7.0 — pick one:

  • NovaTerminal-linux-x64-v0.8.0.AppImage (or -arm64) — chmod +x it and run.
    Self-updating, like the Windows and macOS builds.
  • novaterminal_0.8.0-1_amd64.deb (or _arm64) — sudo apt install ./the-file.deb.
    Desktop entry, icons, nova on your PATH, a man page. No APT repository yet, so it
    does not update itself — download and reinstall each release.
  • NovaTerminal-linux-x64-v0.8.0.tar.gz (or -arm64) — portable, no integration, no
    updater.

Which Linux distros? Ubuntu 22.04+, Debian 12+, Fedora 36+, and current rolling
releases. The floor is glibc 2.35.

Still on a portable zip or tarball? Run an installer once by hand. Portable builds have
no updater and cannot bring themselves forward.

Agent output, rendered as markdown

Coding agents print markdown into a terminal that cannot render it. 0.8.0 adds a side panel
that does, opened per pane, over the output you already have.

It shows the latest response, not the whole pane. A long agent conversation is rounds of
prompt → response on one grid, so trimming the raw tail cannot describe "the response" —
prompts are interleaved throughout and the tail is a fragment of several rounds at once.
Prompt-shaped lines act as segment boundaries, and the panel renders the last segment with
content. A mis-split degrades to a later fragment of the current answer, never to an earlier
round.

Prompt lines are trimmed off both ends, so opening the panel after a command finished
does not show you the prompt that launched it and the one that followed. PowerShell PS …,
cmd drive paths and POSIX user@host shapes are all recognised, with or without the
echoed command riding the line. Prompt-shaped lines inside a response are left alone.

Fenced blocks get handlers. A diff fence colours lines by leading marker, testing the
three-character file headers before the one-character ones so +++ b/file is a header and
not an addition. A markdown fence recurses back through the renderer's own block walk, so
every block type the panel supports works inside a fence — capped at one level, so a fence
inside a rendered fence stays source. Copy always yields the raw source, whatever the block
rendered as.

The md toggle appears only when there is something to toggle. While the panel is
closed, a lazy cadence looks for distinct structural signals in the recent output — fences,
ATX headings, GFM table delimiters, task lists — and the toggle shows or hides as the verdict
changes. Two strong signals, or one plus a couple of list lines, is the bar; a log separator
or a stray # never triggers it.

An About window, and an update check that answers in place

There was no way to see which version you were running, and the manual update check existed
only as a palette command that answered with a toast.

About NovaTerminal… in the title-bar flyout now shows the icon, name and version, with
the update check inline: it reports the answer in the window, and offers Restart now when
an update is staged. Non-installer builds get a link to the releases page instead, because
they cannot update themselves.

Both surfaces run through one coordinator and share the in-flight guard and announce-once
state, so a check from About cannot race the palette's or the one at startup, and the wording
is centralized so the two cannot drift.

Icon glyphs out of the box

A fresh install had no icon glyphs. The one bundled font covered ASCII, box drawing and
powerline — so starship and oh-my-posh arrows worked — but not the dev, file-type and brand
icons those prompts also draw.

Three fonts now ship:

font size role
JetBrains Mono NL 204 KB new default terminal face
Cascadia Mono PL 360 KB kept
Symbols Nerd Font Mono 2,549 KB icon glyphs, fallback only

That follows WezTerm rather than Ghostty: a single shared symbols font is paid for once and
serves every face — including a system font you pick yourself — where per-style patched faces
cost about the same while serving icons only to whoever selected that one font.

The command palette rendered blank in released builds

Reported from a fresh Windows install: the command palette opened with blank rows. Not an
empty list, not an error — rows with nothing in them.

The palette's item template was the only place in the app that opted out of compiled
bindings, so its two bindings resolved their paths by reflection at runtime. Releases publish
with NativeAOT, and ILC had already trimmed both property getters away, because those two
XAML strings were their only readers in the whole program. The bindings then produced nothing
at all: no exception, no log line, just blank rows. It could not reproduce in any dev build,
which are JIT with full metadata.

Releases now have to start before they ship

That palette bug is the kind that only exists in the artifact users download, so 0.8.0 also
closes the gap that let it through.

Trim and AOT warnings now fail the publish. IL2026 and IL3050 are errors during the
release publish, so the class of break behind the palette bug cannot reach an asset.

The Windows bundle is started before it is archived. Every user-facing step — the release
asset, the installer, the update feed — is now downstream of a smoke launch that runs the
published bundle from a pristine profile and asserts it wrote no startup error, stayed alive,
got managed Main through startup, brought up a themed terminal view, and spawned a shell
with a live child process. The natives it loads are the prebuilt ones the release ships, not
freshly compiled copies, so this is the only place the shipped combination is ever executed.
A bundle that cannot start now fails the tag instead of being published.

Linux has had an equivalent gate since 0.7.0. macOS does not yet — launching a .app on a
macOS runner is a different problem, and it is deliberately left for a follow-up rather than
guessed at.

Terminal correctness

Splitting a pane no longer blanks it. The pane being split loses about half its height,
and the shrink path used to take every row it needed off the top of the viewport — so a
transcript in the top half was evicted into scrollback while the empty bottom half was kept,
and the pane came back with a lone prompt. Empty rows below the input line are spent first
now, which in the ordinary case means nothing reaches scrollback at all.

Sixel probes no longer scroll the screen. A control sequence is named by its final byte
together with its leader and intermediate bytes, and the CSI dispatch keyed on the final byte
alone for most cases. DA1 advertises sixel, so sixel-capable clients probe XTSMGRAPHICS
as a matter of course — and every probe ran the bare meaning of S, scrolling the screen.

Text after an inline image starts at column 0. All three image protocols reserve the
picture's cells and stopped without a final newline, leaving the cursor at the end of the
image's last row; whatever the program printed next was indented, or overran the last column
and wrapped mid-word. iTerm2 compounded it by eating the newline the shell sent to escape.

Agent screenshots keep glyphs the primary font lacks. capture_screen rendered notdef
boxes where the live pane showed the glyph, because per-codepoint font fallback was consulted
on only one of the two draw paths.

Typing while scrolled up returns you to the input line. Keystrokes, typed text, paste and
drag-drop all snap the viewport back to the live line first, including in panes receiving
broadcast input. Output keeps the existing stay-put rule, and non-writing sends — focus
reports, mouse reports, device replies, copy — do not scroll.

Also in this release

  • capture_screen gains a live mode that carries the pane's background image and window
    opacity, and a caller-named scale from 1 to 3 so text stays readable in a capture.
  • SSH path resolution honours NOVATERM_APPDATA_ROOT.
  • A pane refuses a degenerate size instead of clamping it to 1×1.
  • Resizes are recorded where they are dispatched, and a resize cancelled before it ran is
    re-sent.
  • A large reliability pass on the headless test lane, so a run that is cut short or aborted
    fails the gate instead of reporting green.

v0.8.0-rc.1

v0.8.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 08 Sep 12:29
d6e894b

What's Changed

  • fix(tests): contain the Avalonia platform boot in its own test process by @benyblack in #395
  • feat(agent-output): render command output as markdown in a side panel by @benyblack in #378
  • feat(agent-output): act on a fenced block's language — nested markdown and diff colouring by @benyblack in #397
  • docs: correct stale facts in README and the main docs, and document what shipped by @benyblack in #400
  • fix(tests): run the AgentOutput suites on the headless session thread by @benyblack in #399
  • feat(agent-host): capture_screen gains live mode and scale, and drops its own gate by @benyblack in #401
  • feat(fonts): bundle JetBrains Mono NL as the default, with a symbols-only Nerd Font fallback by @benyblack in #402
  • Return viewport to the input line on user input by @benyblack in #377
  • fix(agent-host): capture_screen lost every glyph the primary font lacked by @benyblack in #403
  • fix(tests): put the last snapshot-path booter in the PlatformBoot lane, and make the guard see transitive boots by @benyblack in #409
  • ci: fail the headless lane when a run is cut short, instead of summarising it by @benyblack in #410
  • fix(vt): return the cursor to column 0 after an inline image by @benyblack in #407
  • fix(ssh): honour NOVATERM_APPDATA_ROOT when resolving SSH paths by @benyblack in #408
  • fix(agent-output): contain the debounce tick, which was aborting CI collections by @benyblack in #411
  • fix(vt): stop the post-image newline swallow leaking past other output by @benyblack in #413
  • test(command-assist): wait on the pass's commit point, not a property it sets early by @benyblack in #415
  • ci(app-tests): a runner-level abort now fails the gate, hang or no hang by @benyblack in #414
  • feat(app): About window with embedded manual update check by @benyblack in #412
  • fix(command-assist): stop a stray pass from claiming Avalonia's UI-thread identity (#81) by @benyblack in #416
  • test(app): dispose the windows the tests build, and the shells behind them by @benyblack in #417
  • fix(command-assist): make disposal terminal, not just a cancellation by @benyblack in #418
  • docs: retract the disproven #81 explanations still recorded in the tree by @benyblack in #420
  • fix(app): make process-wide cached brushes immutable by @benyblack in #422
  • fix(app): exit zoom before disposing a tab strip that is being replaced by @benyblack in #419
  • fix(app): stop TerminalPane reading Avalonia's UI-thread static (#423) by @benyblack in #426
  • build: fail a test run that aborted, whatever its summary says (#425) by @benyblack in #427
  • test(command-assist): wait on the assist surface, not on a fixed delay (#424) by @benyblack in #428
  • build: refuse to run on a toolchain that cannot compile (#347) by @benyblack in #429
  • ci(macos): make the dSYM leak assertion unable to pass vacuously (#390) by @benyblack in #430
  • fix(app): refuse a degenerate pane size instead of clamping it to 1x1 by @benyblack in #431
  • fix(vt): identify a CSI by its leader and intermediates, not its final byte alone by @benyblack in #433
  • test(app): make the window-teardown tests' precondition deterministic by @benyblack in #435
  • test(app): stop the suite writing a session into the real app-data profile by @benyblack in #436
  • fix(session): keep an unhydrated tab's panes when capturing during startup restore by @benyblack in #437
  • test(app): assert the preview recompute instead of racing a live shell's output by @benyblack in #439
  • fix(vt): spend blank padding before evicting the transcript on a height shrink by @benyblack in #440
  • fix(app): record a resize where it is dispatched, not where it is computed by @benyblack in #441
  • fix(app): re-send a resize dispatch that was cancelled before it ran by @benyblack in #442
  • fix(app): compile the command palette's item bindings so release builds render them by @benyblack in #443
  • ci(aot): fail the publish on trim/AOT warnings, and start the Windows bundle once by @benyblack in #444
  • ci(release): block the Windows release on a smoke launch of the bundle by @benyblack in #445
  • test(command-assist): give the grid-truth harness the pane's dispatcher by @benyblack in #446

Full Changelog: v0.7.0...v0.8.0-rc.1

v0.7.0 — Signed macOS builds, Linux packages, inline images, live themes

Choose a tag to compare

@github-actions github-actions released this 03 Sep 12:05
95deb0c

NovaTerminal 0.7.0 is the first release Apple has notarized, the first with real Linux
packages instead of a broken zip, and the first where inline images actually render. It
also rebuilds the tab strip for people running several coding agents at once, and makes a
theme change apply the moment you pick it.

Installing and upgrading

On Windows? If you installed with NovaTerminal-Setup-win-x64-*.exe, 0.7.0 arrives
on its own — check for the update toast and it applies on your next restart.

On macOS? Same, via NovaTerminal-Setup-osx-arm64-v0.7.0.pkg — and Gatekeeper will
not warn you any more. See below.

On Linux? Newly supported, three ways — pick one:

  • NovaTerminal-linux-x64-v0.7.0.AppImage (or -arm64) — chmod +x it and run.
    Self-updating, like the Windows and macOS builds.
  • novaterminal_0.7.0-1_amd64.deb (or _arm64) — sudo apt install ./the-file.deb.
    Gives you a desktop entry, icons, nova on your PATH, and a man page. Updates come
    from your package manager, so it does not update itself yet.
  • NovaTerminal-linux-x64-v0.7.0.tar.gz (or -arm64) — portable, no integration,
    no updater.

Which Linux distros? Ubuntu 22.04+, Debian 12+, Fedora 36+, and current rolling
releases. The floor is glibc 2.35. Debian 11 and RHEL 8/9 will not run this build.

Still on a portable zip or tarball? Run an installer once by hand. Portable builds
have no updater and cannot bring themselves forward. They remain available for anyone who
prefers them.

macOS builds are signed and notarized

This is the first NovaTerminal release Apple has seen. Until now every macOS build was
unsigned, so first launch meant a Gatekeeper dialog and a trip through System Settings to
allow an app "from an unidentified developer".

  • The .app is deep-signed with a Developer ID Application certificate — the bundle
    and every dylib inside it, including the Rust native libraries.
  • The .pkg installer is signed with a Developer ID Installer certificate.
  • Both were submitted to Apple, notarized, and stapled. The ticket travels with the
    file, so verification works even offline. The build then runs Apple's own spctl
    assessment on each, and fails the release if either is rejected.

That covers both macOS downloads: the .pkg installer and the portable
NovaTerminal-osx-arm64-v0.7.0.zip, which is the same signed .app in a zip.

Nothing is required of you. Already-installed unsigned copies keep working and update
to 0.7.0 normally; the update just happens to be signed from here on.

Windows installers are still unsigned, so SmartScreen will warn on first run. That
leg is tracked in #91.

Linux gets real packages

Linux used to ship as a single portable zip that was broken two ways at once.

  • The binary arrived without its executable bit. The zip was written by
    Compress-Archive, which cannot store Unix mode bits at all.
  • It excluded the most-deployed LTS. The build pinned a glibc floor that Ubuntu 22.04
    and Debian 12 could not meet, so the two distros most people are on could not run it.

Both are fixed by replacing it with an auto-updating AppImage, a system-integrated .deb,
and a portable tarball — for x64 and arm64 alike.

The .deb's dependency list was the subtle part: of the nine X11 and fontconfig libraries
the app needs, eight are invisible to ldd, because Avalonia loads them at runtime and
only libfontconfig1 is linked directly. Deriving dependencies the obvious way would have
shipped a package that installs cleanly and then cannot open a window. Nothing reaches this
release page until a smoke gate has installed the .deb and launched the real bundle in
two pristine ubuntu:22.04 containers.

Still to come: a signed APT repository (#383), nova -e and x-terminal-emulator
integration (#384), and Flatpak/AUR/RPM/Snap (#385).

Inline images actually render

Sixel, iTerm2 (OSC 1337), Kitty and tunneled OSC 1339 payloads were documented as
supported and silently decoded to nothing. Everything around the decoder existed —
placement, scrolling, eviction, drawing — but the decoder itself had only ever been wired
up in tests, so in a real terminal every image protocol was a no-op.

It is now wired in every live pane and in replay windows, so a replay renders images the
same way the session did.

Evicted images also release their memory now. Pruning an image used to drop its last
reference and leave the native bitmap to a finalizer; handles are disposed at the render
frame boundary instead, after a grace period long enough that no in-flight frame or agent
screenshot can still be drawing one.

Tabs, for when you are running five agents

The vertical tab strip was serviceable for a few tabs and painful for a dozen. This is the
pass that fixes that.

Drag a tab to reorder it, in either orientation. An insert gap shows where it will
land, the strip auto-scrolls when you drag to its edge, and Escape cancels. A short click
still just selects — dragging starts only after about 5 DIP of movement. Reordering reuses
the existing tabs, so your panes, sessions and recent-tab order are untouched, and the new
order persists.

Status markers moved out of the title. The bell, activity, agent-typed and
agent-reading markers used to eat into a title that was already truncated; they are now
trailing chips, and the title gets its full width back. Tooltips and the tab list still
spell them out.

One dot tells you the state, with a clear precedence: needs attention, then
agent-typing, then running, then agent-reading. The colors match the agent segment inside
the pane.

A thinking agent no longer looks idle. The old read on "is this tab busy" was a
two-second output heuristic, so an agent that had gone quiet to think decayed to idle
between bursts. Running state now comes from the agent-session registry directly.

Overflow and keyboard moves. A "+N more" pill sits at the bottom of the vertical
sidebar and opens the tab list. Ctrl+Shift+PageUp / Ctrl+Shift+PageDown move the
current tab without the mouse, and both are in the command palette as "Tab: Move
Previous/Next".

The selected vertical tab also gets a 3px left accent bar. Horizontal tabs keep their
underline.

Themes apply when you pick them

Switching themes used to need a restart, and even then parts of the screen kept the old
palette. Several separate causes, all fixed:

  • Cached rows were serving pre-switch colors forever. Scrollback snapshots were pinned
    as immutable, so the cell and row caches never noticed a theme change — and a frame in
    flight during the switch could re-seed stale colors after the caches were cleared.
    Snapshots now carry a theme epoch that invalidates both.
  • Applying a theme was quietly making colors explicit. The buffer's live style state
    resolved "default foreground" into a literal color and dropped the flag that said it
    was the default. Every cell written afterwards stored a fixed color that no later theme
    switch could move, and the state itself stayed pinned to the old palette. This was the
    root of the whole "one switch behind" family of bugs.
  • Panes nested inside scroll and border wrappers were skipped entirely when settings
    were applied, so they got neither the new theme nor new fonts.
  • Title-bar foregrounds were being discarded by the rebuild that ran right after the
    theme was applied — and the tab-list button was repainted white on every layout pass,
    which is why that one icon vanished on light themes.

Light themes work on the Settings page. The title-bar item cards, shortcut binding
cards and Command Assist snippet rows had dark backgrounds hardcoded. They now follow the
theme, along with the pill, icon and sidebar hover tints.

The default theme's blue is easier to look at. The UI accent is now derived from the
theme's blue with its hue preserved but saturation and lightness pulled into a comfortable
band. The terminal palette itself is untouched.

Output an application painted with an explicit truecolor still does not move when you
switch themes. That is correct terminal behavior — the app asked for that exact color —
and it is deliberate, not an oversight.

Eight more built-in themes

The built-in set grows from 5 to 13: Nord, Tokyo Night, Catppuccin Mocha,
Gruvbox Dark, Cobalt2, and light counterparts to themes already shipping —
Solarized Light, One Half Light, GitHub Light.

Dracula is fixed. Its foreground shipped as #FF5F1F, a bright orange, instead of
Dracula's #F8F8F2, so all normal text rendered orange.

New and corrected themes are seeded on next launch. As has always been the case with
built-ins, the packaged file wins — so a hand-edited copy of a built-in theme gets
overwritten. Save your own under a new name.

Also

  • Connection Manager is a real window (Ctrl+Shift+K), centered on the main window and
    resizable, instead of an overlay drawn inside it with a hand-made title bar.
  • Unix sessions start your actual login shell. The default was a hardcoded
    zsh → bash → sh existence probe, so a bash user got zsh whenever zsh happened to be
    installed, and fish or nushell users could never be the default at all. NovaTerminal now
    asks the system what your login shell is, and fresh installs lead with a "Default Shell"
    profile. Existing profiles are untouched.
  • dotnet build progress stays in one place instead of scrolling a new block of output
    per refresh. The .NET 10 terminal logger drives this with CSI E/CSI F, which the
    parser did not handle.

For contributors

  • The VT capability contract is executable now (#376) — what the terminal claims to
    support is asserted, not just described in a matrix.
  • xunit collections are serialized (#373), which fixes the intermittent full-suite hang
    (#371) and exposed eight tests that had been depending on ord...
Read more

v0.7.0-rc.1

v0.7.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Sep 09:49

What's Changed

  • feat(app): wire inline image decoding for sixel, iTerm2, and Kitty by @benyblack in #369
  • test(vt): decoder-wired e2e coverage for OSC 1337 and Kitty image paths by @benyblack in #372
  • fix(vt): dispose pruned image bitmaps at the render frame boundary by @benyblack in #374
  • test: serialize xunit collections (fixes #371 suite hang; exposes 8 ordering-sensitive UI tests) by @benyblack in #373
  • fix(vt): keep dotnet terminal logger updates in place by @benyblack in #375
  • Add an executable VT capability contract by @benyblack in #376
  • feat(app): open connection manager as a real window by @benyblack in #379
  • fix(pty): default unix sessions to the user's login shell by @benyblack in #380
  • Expand built-in themes and fix Dracula foreground by @benyblack in #381
  • ci: wire macOS codesigning + notarization behind MAC_* secrets by @benyblack in #382

Full Changelog: v0.6.1...v0.7.0-rc.1

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 28 Aug 23:11
f1f8075

A bug-fix release for Windows, from a real first-run report. If you installed with
Setup.exe or the .pkg, this arrives on its own — check for the update toast and it
applies on your next restart. No manual cleanup is needed, including if 0.6.0 already
left a PowerShell tab misbehaving.

PowerShell tabs work again

Three problems that compounded into one bad first run.

A stale launch line was being replayed forever. A pane saved the command line it was
launched with, not the one you configured — so the shell-integration bootstrap ended up
written into your session file. On the next start NovaTerminal saw that bootstrap in
"your" arguments, concluded you had supplied your own script, and stepped aside — leaving
Command Assist off and the old bootstrap running anyway. Every launch re-saved it, so it
never recovered on its own. Panes now store what you configured; the launch details stay
where they belong.

Sessions already saved are repaired on load, so upgrading is enough. Only arguments
NovaTerminal itself injected are removed, identified by resolving the full path; your own
-File or -EncodedCommand is left exactly as you wrote it, spacing included.

The PowerShell startup banner no longer trips the execution policy. It was run from a
temporary .ps1, which Windows blocks under its default Restricted policy — so tabs
opened with a red UnauthorizedAccess error. Those statements are now sent straight to
the shell, so no script file is loaded and nothing is written to %TEMP% at all.

Also

  • Shell-integration tests no longer write into the config directory of whoever is running
    them.

Full changelog: v0.6.0...v0.6.1

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 15:17
9d53607

NovaTerminal 0.6.0 brings your configuration under your own control, and gives macOS
the same one-click installer and background updates Windows got in 0.5.0.

Installing and upgrading

macOS now has an installer. Download NovaTerminal-Setup-osx-arm64-v0.6.0.pkg. It
installs NovaTerminal as a proper .app, and from here on it updates itself in the
background like the Windows build does.

On Windows? If you installed 0.5.0 with NovaTerminal-Setup-win-x64-*.exe, you get
0.6.0 automatically — check for the update toast, and it applies on your next restart.

Still on a portable zip, on any platform? Run the installer once by hand. Zip builds
have no updater and cannot bring themselves forward. The zips remain available and
unchanged for anyone who prefers them.

Installers are not code-signed yet, so Windows SmartScreen and macOS Gatekeeper will
warn on first run. Signing is tracked in #91.

Configuration backup and restore

Your settings, themes, connections, workspaces, policy and snippets can now be exported
to a single portable .novabackup file and imported on another machine — or back onto
the same one after something goes wrong.

  • Pick what travels. Six categories — Settings, Themes, Connections, Workspaces,
    Policy and Snippets — each independently selectable on export and import.
  • Merge or replace. Import either layers the bundle over what you have or replaces
    it outright, so moving to a new machine and topping up an existing setup are both
    first-class.
  • Automatic snapshots. NovaTerminal now snapshots your configuration in the
    background as it changes, deduplicated by content hash so unchanged config costs
    nothing, with retention limits so they don't accumulate. A snapshot is also taken
    immediately before any import or restore, so the operation is always reversible.
  • Restore asks first. Rolling back to a snapshot overwrites live configuration, so
    it confirms before doing anything.
  • Imports are staged, then committed. The bundle is unpacked beside your live
    configuration and swapped in as one step, so a failure part-way through leaves your
    existing setup intact rather than half-replaced.

Available three ways: Settings → Backup & Restore, the command palette, and the CLI
(backup export, backup import, backup list, backup restore) for scripted setups.
Agents can read backups too, through read-only export and list MCP tools.

Connection passwords are not included in a bundle. They live in your OS credential
store — Windows Credential Manager, macOS Keychain, Linux Secret Service — not in
NovaTerminal's config folder, and a portable file is the wrong place for them. Imported
SSH profiles arrive complete apart from their passwords; re-enter those on first
connect. NovaTerminal tells you this in the import result rather than letting you find
out at the wrong moment.

Windows first-run fixes

Two problems that only ever showed up on machines that aren't a developer's, both
reported from a real install:

  • The "PowerShell" profile no longer points at a shell you may not have. It targeted
    pwsh.exe (PowerShell 7, a separate download) unconditionally, so on stock Windows it
    was a dead entry that failed to launch. Default profiles are now filtered to shells
    that are actually installed. The same applied to Zsh on Linux.
  • Command Assist works under the default execution policy. Its shell-integration
    bootstrap was loaded with -File, which Windows blocks under the default Restricted
    policy — so every PowerShell tab opened with a red security error and shell integration
    silently did nothing. It no longer touches disk to start, so no policy applies, and
    your own execution policy is left exactly as you set it.

Tabs

  • The vertical sidebar's preview line now tracks the last meaningfully-changed row, so
    a pane running a full-screen TUI shows actual content instead of static chrome.
  • Preview recomputation is gated behind a dirty flag and throttled, so a chatty pane no
    longer costs you frames.

Fixes

  • Themes repopulate and the selected profile re-points when settings change on disk
  • Command-palette usage data saves atomically, so a crash mid-write can't corrupt it
  • The Command Assist snippet list refreshes after an import or restore

For contributors

docs/CONFIG_STORAGE_CONTRACT.md now covers the backups\ directory and why recovery
data has to live outside the install root. If you touch anything that reads or writes
user configuration, read it first.

Full changelog: v0.5.0...v0.6.0

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 20:40
2b89b67

NovaTerminal 0.5.0 is a big one — 145 commits since 0.4.0. Three things stand out:
Windows finally has a real installer that keeps itself up to date, tabs can live in a
vertical sidebar, and the native SSH backend has grown up enough to be the default for
new connections.

Installing and upgrading

Windows now has an installer. Download NovaTerminal-Setup-win-x64-v0.5.0.exe. It
installs per-user — no admin prompt, no UAC — and adds Start Menu and Desktop shortcuts
plus an Add/Remove Programs entry.

Coming from 0.4.0? Run the installer once by hand. 0.4.0 shipped as a zip and has no
updater, so it cannot bring itself forward. From 0.5.0 on, NovaTerminal checks for updates
in the background, downloads them quietly, and applies them the next time you restart —
no modal, no interrupted session. Turn it off under Settings → Automatic update checks
if you'd rather update yourself.

Prefer portable? The zips are unchanged — NovaTerminal-win-x64-v0.5.0.zip, plus
Linux x64 and macOS arm64. Portable builds have no updater.

The installer is not code-signed yet, so Windows SmartScreen will warn the first time you
run it. Signing is tracked in #91.

Vertical tab sidebar

Press Ctrl+Shift+L (or run Tabs: Toggle Vertical Tab Sidebar from the command palette)
to move the tab strip down the side of the window. Rows are richer than the horizontal
strip: each shows a status dot and a preview of the pane's most recent output, so you can
see which tab is working, which wants attention, and which is idle without switching to it.
The sidebar is resizable by dragging its edge, and the width persists.

Native SSH

The native SSH backend — no ssh binary required — is now the default for newly created
profiles. It gained the features that were keeping it experimental:

  • Multi-hop jump chains, so bastion hops work end to end
  • Remote port forwards, alongside the existing local and dynamic ones
  • ssh-agent authentication
  • A Settings → SSH tab, including the toggle to opt existing profiles in or out
  • A warning when a profile carries ProxyCommand/mux options or extra SSH args the native
    backend won't honour, instead of silently ignoring them

Existing profiles keep using whichever backend they were already using. It remains
opt-in for them, and you can switch any profile back.

Command Assist

Command Assist was rebuilt around shell integration marks rather than guesswork:

  • Reads the command line from the terminal grid between the OSC 133;B mark and the
    cursor, so it no longer maintains a shadow copy that could drift out of sync
  • A one-line installer for shell integration on remote hosts, and mark detection over SSH
  • Fix mode is real — failing command output is captured and used to propose a correction
  • A command catalogue derived from tldr, plus snippet management
  • A passive suggestion bubble with rebindable shortcuts, and history search where
    accepting a row replaces what you typed
  • Works in sessions with no marks at all, by capturing straight-through-typed commands

There's also an opt-in seam for an external content provider, with structural redaction
applied before anything leaves the process.

Connections

You can now delete a connection, and deleting it purges the saved password rather than
orphaning it in the vault. A connection's saved password can also be viewed and cleared
on its own, without deleting the profile.

Terminal correctness

  • Kitty keyboard protocol (disambiguate tier) — modified and special keys are now
    distinguishable by apps that ask for it
  • OSC 52 clipboard writes, settings-gated; reads stay denied by design
  • DECRQM mode queries answered with DECRPM, and OSC 10/11 colour queries answered
  • OSC 8 hyperlinks carry their identity, not just the URI, so split segments of one link
    behave as one link
  • Hover motion reported under ?1003 any-event mouse tracking
  • Graphemes and hyperlinks survive every resize path, scrollback paging, and
    ICH/DCH/insert-mode cell shifts
  • A completed ZWJ emoji no longer swallows the character after it
  • CSI s/u/r guarded against leader-prefixed variants

Rendering

  • One glyph per grapheme cluster rather than per rune
  • Glyph ink bounds packed in the atlas instead of the advance box, fixing clipped glyphs
  • Flags, keycaps and standalone emoji routed to the colour atlas
  • Box-drawing primitives on by default; rounded corners are arcs, not chamfers
  • Sixel HLS colours decoded correctly
  • The cursor blink timer stops while a pane is unfocused

Shells and panes

  • The ConPTY host is sideloaded, so a conhost crash can no longer take the pane with it
  • A dead pane says so, and child exit is actually detected
  • A killed shell no longer reports exit code 0 on Unix
  • Win32 spawn handles have owners instead of leaking
  • Local shell commands resolve by the platform they were written for
  • ShellExitPolicy defaults to Graceful

Security and hardening

  • Session passwords are no longer retained as unclearable managed strings, and Rust-owned
    credential copies are wiped
  • SFTP rejects unsafe remote entry names and no longer truncates downloads; UNC-style
    remote paths normalise correctly in the files sidebar
  • SSH rejects invalid UTF-8 in connect arguments instead of mangling it
  • Password-capture holes in Command Assist closed
  • Two FFI abuse-test gaps closed

Also

  • Customisable title bar icons
  • Agent access shows live per-pane indicators, and a pane can be rendered to PNG via
    novaterminal.capture_screen
  • The command palette lists every installed theme, not two hardcoded ones
  • Performance: SSH event payloads no longer copied twice, the native SSH event queue is
    bounded with producer backpressure, and the VT write path no longer allocates a string
    per grapheme
  • A project landing site

For contributors

New: docs/CONFIG_STORAGE_CONTRACT.md
— where user state lives, what an update deletes versus what an uninstall deletes, and why
secrets are not in the config folder. Read it before touching anything that reads or writes
user configuration.

Full changelog: v0.4.0...v0.5.0