A Workspace is built by folding two together, but there was no single
move that took one back apart — you dragged each pane out to the
Navigator one at a time, and the last one could not be dragged out at
all, because a pane alone in its Workspace has nothing to detach from.
Separate Panes on a merged Workspace's row is the inverse of merge: one
Workspace per pane, in layout order, sessions untouched.
The status bar was a docked strip with a hairline above it, reading as
window chrome bolted under the content. It is a glass capsule now,
floating in the trailing corner of the pane it describes. It hugs its
text rather than spanning the width — a full-width bar over a terminal
covers whole lines of output, and the prompt is on the left — and stops
growing at `maxWidth` so a deep working directory truncates instead.
Floating means it no longer takes layout space, so switching to a
Workspace stops resizing the content area. It also means it sits over the
pane, so the command generator and the upload notices now clear it by
`StatusBar.overlayClearance` — a constant rather than a measurement, and
applied whether or not the capsule happens to be showing, because a
notice that moves depending on whether a transfer is running is worse
than one that is always in the same place.
844 tests pass.
Three fixes and a feature, all in the titlebar's blast radius.
A toolbar with nothing in it is shorter than one holding a search field,
and AppKit takes the window's content up with the titlebar when it
shrinks. Connecting to a host from the sidebar changes the destination
from Vault to a Workspace, which swapped a Find field for nothing — so
the connection journey appeared and immediately jumped upward. The find
item now reserves its height at zero width on destinations with nothing
to search, so the toolbar is one size everywhere.
The search field lost the keyboard after one letter because being
searched is not a passive act: Ghostty focuses its terminal surface when
a `search:` binding runs, and the SFTP list is held focused
declaratively and takes it back when filtering rebuilds the rows. The
field now checks, immediately after a text change, that it still has the
keyboard — and only then, so clicking away deliberately still works.
Folding Pinned or Chats crashed. The expansion flag lived in the list
item, so folding changed that item's value, and ListViewKit answered by
reconfiguring the header's hosting view — replacing the root view of the
Button still dispatching the click. The row reads and writes the flag
itself now; the item carries only the count, so a fold adds and removes
conversation rows and never touches the header.
The titlebar names what is in front: the page, the Workspace, or — in
the Assistant — the conversation, because "Assistant" is where you are
but the chat is what you want to read from across the desk. Welcome is
named after the app. Resolved through a catalog of providers rather than
a switch in WindowModel, so the window does not have to know that a
Workspace is named after its host; the same shape ContextPanelDetailCatalog
already uses.
842 tests pass.
The window had no toolbar at all and four competing Find fields: a strip
on each Vault page, a bar inside the SSH pane, a field in the SFTP path
bar, and the sidebar footers. Which one Command-F meant depended on where
the caret happened to be, and two could be on screen at once.
There is now one native NSSearchField in the titlebar, pointed at whatever
content is in front — see WindowModel.findKind, which resolves a split
Workspace to its focused pane rather than to "any session in it". Creation
verbs sit opposite it in a Notes-style square.and.pencil control, chosen
from the main content and never from the sidebar mode. The dashed New
tiles and the page toolbars are gone; the card canvas answers a right-click
with the same verbs, scoped to the section under the pointer.
Return and Command-G used to disagree about which way a terminal search
walks. Both now mean Ghostty's .previous, which is the direction that finds
matches when a search starts at the bottom of the buffer.
The sidebar's own bottom search stays, because it narrows the sidebar's
list rather than the content in front. Its three history modes are one card
with four fillings on one list technology, so their edges line up and
opening a host no longer makes Recents jump under the pointer. Every row
draws its right-click menu natively and outlines itself on the filled
shape; SwiftUI's contextMenu highlighted the row plus its margin.
Fixes found along the way:
- The terminal re-claimed first responder on every SwiftUI update, so
typing in any field outside the pane lost the caret after each keystroke.
- Folding Pinned/Chats reconfigured the header's hosting view from inside
the click that asked for it.
- The Assistant's drop container cached the dragged id from a render pass
AppKit's modal drag loop can start before, hiding the insertion line.
- The host chooser inferred sheet-vs-column from the front destination, so
the sheet raised from the Snippets shelf over Vault never dismissed, and
it had no height to show a host in.
Steering, the composer and the two composer popovers now share one glass
surface. Tags are derived from hosts, so deleteTag goes with the popover
that called it.
838 tests pass. The visual claims are unverified: screencapture returns
black frames in this environment.