Release Notes
This release is the deferred-polish + audit sweep — a session built on
four explicit phases: ship the open backlog, drive multi-persona testing
through the headless harness, fix every verified finding, and audit the
public site against ghostty.org. Across 36 commits, shell.rs split
from 4135 → 561 lines (-86%) via 10 sibling-module extractions; 21
persona-testing findings shipped (4 modifier-guard families, 4
per-window scope inconsistencies, 5 mouse-access via context menu, 4
modal-stacking, 3 headless quirks, plus the help-overlay.gif regression
re-record); 5 ghostty-comparison doc edits landed (landing reorder,
themes quickstart, install per-OS routing, About page, shell-integration
bash/fish). Final state: 544 lib tests pass, Starlight build clean at
28 pages, every audit finding adversarially verified.
Highlights:
- Shell.rs split — extracted into 8 method submodules (resize,
scroll, io, queries, render, spawn, accessors, reader) + 9 sibling
helper crates (shell_cell_attrs, shell_color, shell_config,
shell_context, shell_keys, shell_mouse, shell_osc1337, shell_prompt,
shell_pty). Submodule visibility lets each slice touch private
ShellSession fields withoutpub(crate)leakage. - Persona-testing fixes — keyboard chords no longer get eaten by
overlay filter buffers (Cmd+Shift+R no longer wipes recents), per-
window cfg overrides honored by toggle_sidebar / toggle_tab_bar,
pane management reachable from the right-click menu (Split / Close /
Zoom / Equalize), pane divider gets a hover cursor, Cmd+W closes
the topmost overlay from inside palette/find/etc., view.help no
longer stacks on other overlays, headless--appno longer
persists user config when toggle buttons are clicked. - Render perf — apply_to_grid drops the parser Mutex before the
rustybuzz ligature pass; sync-depth (DECSET 2026) early return
reads a cached cursor instead of leaking the live one; per-frame
~4KB palette_override.indexed clone replaced with a lock-held
borrow; per-framelink_grid/underline_grid/
underline_color_gridclones now gated on sticky-true*_active
flags so sessions that never see OSC 8 / extended underlines /
CSI 58 skip the clones entirely. - Settings save-failure inline error banner — Save now happens
before the overlay closes; failure stays the dialog open with a
red! save failed: <reason>banner instead of silently losing
the user's edits. - Type-to-filter the help overlay — start typing while ⌘⇧/ is
open to narrow the binding list. - 20 demos shipped across the manual — 3 layout demos
(many-tabs-horizontal / bottom-tab-bar / tab-layout-toggle), help-
filter, attention-dot, palette-filter, settings-window-scope,
discovery-install, help-themes, plus the existing 13. - Site audit fixes vs ghostty.org — landing reordered so the
plain-English value pitch precedes the jargon grid, themes
quickstart added, install page gains per-OS jump table + version
banner + Gatekeeper escape hatch, new /about page, shell-
integration grew bash + fish recipes + verification step.
Menu bar
- New Edit > Find… menu item (⌘F). Standard macOS slot, mirrors the
existing chord. Opens the in-pane find bar in the focused pane. - New Edit > Find Next and Edit > Find Previous menu items.
Work whether the find bar is visible or not — as long as a find
session is active, they advance through matches. Same match-
advance logic Enter / Shift+Enter use inside the find bar.
Intentionally no chord — ⌘G / ⌘⇧G stay reserved for the
fwd.cmd_gforwarder that sends ⌘G as ⌃G to native mnml panes
(goto-line in the editor). - New Shell > New Tab (⌘T) and Shell > Close Tab (⌘W) menu
items. Standard slots that were previously chord-only. - New View > Recents… (⌘R) menu item. Opens the recents picker —
the same overlay the welcome screen used to surface on startup.
Mirrors the existing chord; gives the recents flow a discoverable
menu-bar entry point. - New View > Search Tabs… menu item. Toggles the
tab-search overlay (filter tab chips by name). Intentionally
no chord — ⌘⇧T is reserved at the chord-registry layer for
tab.reopen_closed(browser convention). - Help > tmnl Help now opens the in-app help overlay (same surface
⌘⇧/ opens) instead of logging a placeholder. The overlay lists every
registered command + its current chord — the discoverable answer to
"what does tmnl do?" for new users.
Multi-window
- ⌘W on the last tab of one of N windows now closes only that
window, instead of quitting tmnl and dropping every other open
window with it. When the focused window's last tab closes and at
least one background window is open, swap a background window in
as the new focused window (dropping the previous one's winit
handle closes the OS window). When no background windows are
open, the historical "quit the app" behavior still fires. - ⌘W no longer cascades through every open window in one
press. Dropping aWindowStatecauses AppKit to queue a
CloseRequestedfor the just-closed window. The handler used to
process that stale event against the new focused window and
cascade-close. Now the dispatcher ignoresWindowEvents whose
WindowIddoesn't match any window we still own. - Persistence stays in sync with what's actually open —
closing one of N windows (via the red close button or via the
last-tab ⌘W path) now writes the smaller window-state immediately,
so the next launch doesn't resurrect the closed window.
Chrome strip
- Back / forward arrows on Shell panes now jump to the previous /
next OSC 133 prompt in scrollback (same as ⌘↑ / ⌘↓). They used to
forward Ctrl+PgUp / Ctrl+PgDn to the focused pane — useful on
Native (mnml) panes, no-op in bare zsh. The chord stays in place;
the arrows give it a discoverable click target.
Selection
- Shift+click now extends the existing body-selection — anchor stays
put, focus moves to the click position — instead of starting a fresh
single-cell selection. Falls back to a fresh selection when there's
no existing one on the same tab + pane. Matches the macOS Terminal /
iTerm / VS Code convention. - The selection highlight now paints during drag, not only on
release. EachCursorMovedwhile dragging force-pushes the live
bounds togpu.selection_boundsso the next frame paints them even
if the redraw chain lagged.
Modal overlay accessibility
Every modal overlay — palette, palette overlay (chip-clicked),
welcome, discovery, settings, help, right-click context menu, and
both confirm dialogs (close + paste) — now has full mouse +
keyboard parity:
- Click a row to activate / focus it (palette dispatches the
command, welcome opens the entry, discovery toggles in-rail,
settings focuses the row; second click on the focused settings
row cycles its value). - Wheel scrolls the row selection on welcome / discovery /
settings / help. - Click outside the panel dismisses (palette / palette overlay /
welcome / discovery / help / context menu); settings is
intentionally excluded to avoid silently discarding unsaved
row edits. - Confirm-close and confirm-paste dialogs grow visible
[Close] / [Cancel]and[Paste] / [Cancel]buttons. Click-outside
also cancels. - Hover pre-selects rows on welcome + discovery — standard menu
UX, lets users hover-then-Enter without a click. - Help overlay click-outside dismiss covers chrome / chip clicks
that previously slipped past the body-click-dismiss path.
Keyboard chords (closing the persona-report gaps)
- F2 — rename the focused tab. VS Code parity. Same modal as
the right-click rename path; first-class menu item lives under
Window > Rename Tab. - ⌘⇧1 … ⌘⇧9 — open launcher rail icons. Closes the "rail is
mouse-only" gap. Out-of-range indices silently no-op. - ⌘Home / ⌘End — jump to top / bottom of scrollback. Pairs
with the existing ⇧PgUp / ⇧PgDn row-by-row paging. - ↑ / ↓ in ⌘F find bar and ⌘⇧T tab-search step matches
forward / backward. Was Enter / Shift+Enter only. - ⌘⇧P palette and ⌘R recents now supersede transient input
overlays — welcome / tab_search / find / palette_overlay /
discovery / context_menu auto-dismiss as the new one opens.
The "destructive" modals (settings / help / rename / confirm
dialogs) still block. Closes the 06-10 hybrid SEV-4 #7 design
call. - ⌘D in a Browser pane forwards as ⌃D instead of unconditionally
splitting right, so mnml's multi-cursor "select next
occurrence" chord works when the editor's a Native pane. - ⌘Z / X / S / N / P / B / G / / no longer silently eat keys in
Shell context. Thefwd.cmd_*chords now gate on Native
focus; in Shell context they fall through to the pty's char
path as the user expects.
Browser pane chord coverage
Safari conventions, all gated on Browser-pane focus:
⌘[/⌘]— history back / forward.⌘L— focus the URL bar (seeds the chrome edit buffer with
the current URL).⌘R— reload (rides onview.recents's wire; falls through
to opening recents when no Browser pane is focused).
Maximize Pane (zoom)
The Shell > Splits > Maximize Pane (⌘⇧↩) verb shipped in
0.1.5 but was substantially broken — composite painted via
tab.layout.leaf_rects instead of effective_leaf_rects, so
the zoomed pane's full-area grid only painted at its small
underlying split-tree rect; pane_under_cursor mis-routed
clicks; divider drag still fired through the zoomed pane;
close / split paths left zoomed_pane stale across id shifts.
All fixed; the menu label now flips between Maximize Pane ↔
Restore Pane in sync with the state (across toggle / chord /
menu click / tab switch / window swap / close / split paths).
Active chip in the strip also gains a leading ⤢ badge while
zoomed.
Multi-window state drift
Several latent state-drift bugs that surfaced as part of the
zoom-feature audit:
- Per-tab modals (
confirm_close,renaming_tab,find,
text_selection) carrytab_idxreferences that were stale
after a tab close, tab reorder, or window swap. Now shifted /
cleared in lockstep at every site (close_tab_at_unchecked,
swap_tabs,focus_background_window,
spawn_in_process_window, red-X close, drain restore). dragging_scrollbar(PaneId),last_hover_native(PaneId),
fim_pending/ghost(AI completion source) — same pattern,
cleared at the right shape on tab close / tab switch.view.settingstoggle (⌘,) re-press now reverts live-applied
edits like Esc does, instead of silently keeping partial
changes.
Multi-window persistence (per-window override propagation)
Two runtime bugs in the per-window theme + cfg override
persistence layer closed. The on-disk [[windows]] Vec format
has worked correctly at save / restore time since v3
(acb275c); the leftover gaps were at:
spawn_in_process_window(⌘N / Window > New Window). Was
reading the globalcfg.last_window_theme/cfg.last_window_cfg
slots, which are last-write-wins — set by whichever window
most recently committed an override via Settings's window-scope
save. Spawning a new window would silently "rewind" to that
previous override instead of inheriting from the window the
user was looking at. Now: ⌘N inherits the focused window's
in-memorytheme_name+cfg_override("duplicate this
window's setup"). Initial-window restore on app launch still
reads from the per-windowwindow_state.tomlentry as before.Reset Window Settings(tmnl > Reset Window Settings) and
Apply Window Settings to All Windowscleared the in-memory
override but didn't write the change to disk. Next launch's
restore read the still-populatedcfg_override_tomlin
window_state.tomland resurrected the override. Both menu
items now callsave_window_stateafter the clear.
Theme picker discoverability
View > Themesubmenu lists every discovered theme +
(global)sentinel; pick to apply as the focused window's
override. Mirrors the⌘⌥]/⌘⌥[cycle chord with a
discoverable surface.- New
:theme.cycle_next/:theme.cycle_prevcolon-prefix
launcher-chip sentinels. Add via the+Add integration
overlay's Built-in actions category to get a one-click
theme cycle chip in the launcher rail / top strip / bottom
strip (wherever the user pinned it).
Inline images (protocol v7)
Three new client→server messages let backing apps overlay PNG
images on the cell grid:
InlineImageCreate— anchors a raster image at a cell
coordinate inside the pane (anchor stays put as the pane
scrolls). Client-allocatedididentifies the image for the
later Update / Destroy. Thecell_w × cell_hbox scales the
decoded PNG to fit the requested cells.InlineImageUpdate— replaces the encoded bytes without
moving the anchor; useful for animation or live data.InlineImageDestroy— removes the image. Dropping a pane
also drops its images.
Tmnl decodes once (image crate, PNG-only for now — the
ImageFormat enum reserves room for more formats), uploads to a
wgpu texture, and renders via a textured-quad render pass. The
quad alpha-blends over whatever the cell grid painted underneath,
so transparent pixels show the cell content through. Decode
failures paint a dim-red placeholder with a nf-md-image_broken
glyph at the image's anchor rect — the host doesn't crash on bad
input.
Gated by Caps::INLINE_IMAGES. Pre-v7 servers ignore the
messages; v7 clients should gate sends on
peer_caps().contains(Caps::INLINE_IMAGES).
SDK helpers:
Client::send_inline_image_create / _update / _destroy. End-
to-end demo in examples/inline_image_client.rs — encodes a
gradient PNG in memory and walks the Create / Update / Destroy
lifecycle. Manual page: SDK — Inline images.
GPU cache survives a tab switch (each image gets a stable
gpu_key assigned at creation; the cache keys on that, so the
same image displayed in two tabs costs one texture upload).
Per-frame upload list shares the decoded RGBA buffer via Arc —
no per-frame memcpy of pixels.
A pane resize / split that shrinks the pane below the image's
anchored cell box clips the textured-quad to the pane edge with
matching UV adjustment (the visible region samples the
corresponding texture sub-rect — no stretch). Anchors entirely
off the pane skip the draw without leaking into chrome.
Cross-platform global hotkey
- macOS now uses Carbon's
RegisterEventHotKey(657470e) which
requires NO Accessibility permission — the prior implementation
was Accessibility-gated and quietly no-op'd if the user had not
granted it. Linux + Windows use theglobal-hotkeycrate
(16977e5). Configurable via thequick_terminal_hotkeyconfig
knob; pressed → spawn a siblingtmnl --quick-terminal.
Linux first-class support
- Notification chime via
notify-rust(c305d69+56f6009):
shell BELs surface as native dock notifications on GNOME / KDE
with a configurable sound (bell_sound). Matches the macOS dock-
badge story. notify_command(56f6009) — opt-in shell-out to a custom
notify program (e.g.dunstify,terminal-notifier) when the
built-in chime isn't enough.- Install / launch / known-limitations docs (
544d37d) catch up
the Manual.
Headless commands + VHS tape recorder
- New
snapshot <path>headless command (605406e) renders the
current App state to an offscreen wgpu texture and writes a PNG
— works without a display, captures chrome correctly. - New
tmnl --tape <path>mode (87750d1) runs VHS-syntax scripts
through the headless dispatcher. Same.tapefile format mnml +
mixr already use. Output <path.gif>directive (bcc9f41) composes the per-frame
Screenshots into an animated GIF viagifski.Set FrameRate N
controls the FPS.Set DemoChrome on(9939dff) paints fake macOS traffic-light
discs at the conventional top-left position via 3 SDF instances
on the strip pipeline — sells the snapshot as a real window.
AppKit paints over them on live runs, so they only matter for
offscreen captures.- Strip-pipeline blend state migrated to
ALPHA_BLENDINGso the
SDF circle edges anti-alias correctly (4825ea9).
Benchmark framework
- New
tmnl --bench <scenario>subcommand (ee173cd,0c5cc39)
measures input latency and cross-terminal comparisons. See
docs/latency-bench.mdand the Manual's Performance page. examples/latency_glyph.rs(e8606fd) is a glyph-render test
app for sub-frame timing validation.
Install tmnl-rs 0.2.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/chris-mclennan/tmnl/releases/download/v0.2.0/tmnl-rs-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/chris-mclennan/tmnl/releases/download/v0.2.0/tmnl-rs-installer.ps1 | iex"Download tmnl-rs 0.2.0
| File | Platform | Checksum |
|---|---|---|
| tmnl-rs-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| tmnl-rs-aarch64-apple-darwin.pkg | Apple Silicon macOS | checksum |
| tmnl-rs-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| tmnl-rs-x86_64-apple-darwin.pkg | Intel macOS | checksum |
| tmnl-rs-aarch64-pc-windows-msvc.zip | ARM64 Windows | checksum |
| tmnl-rs-aarch64-pc-windows-msvc.msi | ARM64 Windows | checksum |
| tmnl-rs-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| tmnl-rs-x86_64-pc-windows-msvc.msi | x64 Windows | checksum |
| tmnl-rs-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| tmnl-rs-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |