Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,47 @@ Note: historical entries below pre-date the `c11mux` → `c11` rename and refere

## [Unreleased]

## [0.67.0] - 2026-10-01

Headline: **A redesigned tab bar, and a new vocabulary: workspace, area, tab.** The tab bar scrolls, opens a full-width drawer of every tab with its agent, status and clocks, and can dock as a rail. Across the UI and CLI, a split region is now an area and a terminal, browser or markdown view is a tab. Old names keep working.

### Added

- **A redesigned tab bar.** When tabs overflow, the strip scrolls sideways with edge fades and folds into a solid count block only when under 150pt remain for tabs. The count cell sits on every bar, shows an attention dot, and opens a drawer the full width of its area. The drawer is a grid: tab number, title and description, an agent tag (`Harness · model`), a status with how long that state has held, and clocks. Columns drop by width, so a narrow area still reads cleanly. Rows drag out to other areas, reorder in place, and link to their tab on hover. Drops show a dashed ghost slot where the tab will land, and the empty gap next to the last tab now accepts drops. The title bar under the tabs shows only a live description, and takes no height when there is none. Tabs show their number in mono instead of an `N: ` prefix. The drawer slides open in about 120 ms, and skips the motion under Reduce Motion. ([#459](https://github.com/Stage-11-Agentics/c11/pull/459), [#462](https://github.com/Stage-11-Agentics/c11/pull/462), [#465](https://github.com/Stage-11-Agentics/c11/pull/465)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **A Rail layout for tabs.** A new setting, Settings > General > Tabs & Areas > Tab Layout, switches between Tabs (default) and Rail. In Rail, the count cell toggles a vertical tab list docked on the area's left edge that pushes the content over. Rows scroll, reorder and drag out to other areas. Whether each area's rail is open is remembered across relaunch. ([#465](https://github.com/Stage-11-Agentics/c11/pull/465)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **A rare tip introduces the rail.** When an area's tab bar overflows often, a small popover under the count cell explains the tab list and offers Try Rail. Try Rail switches every area to Rail and turns the popover into Undo, which puts Tabs back everywhere. Don't show again is the only dismissal, and ignoring the tip waits 30 days. ([#474](https://github.com/Stage-11-Agentics/c11/pull/474)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Agent tabs show the model they are actually using.** The tab drawer's `Harness · model` tag is now read from the harness's own session files, so a mid-session `/model` switch shows up. This covers Claude Code, Codex, pi, omp, Grok and opencode. Kimi and GitHub Copilot report that detection is unsupported rather than guessing. Reads are bounded, off the main thread, and ride the existing 10 s agent sweep. The raw id is available as `model_detected` in `c11 get-metadata` and in `sidebar-state`. ([#463](https://github.com/Stage-11-Agentics/c11/pull/463)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Per-tab clocks.** Each tab records when you last looked at it (`last_seen_at` and `being_seen` in `c11 tree --json`), and the drawer's Active clock now means "how long since something was added to this tab", per tab type. Extra clocks (`touched`, `turn`, `tools`, `tokens`) are opt in through the `c11.tabSheet.clocks` default. ([#461](https://github.com/Stage-11-Agentics/c11/pull/461), [#462](https://github.com/Stage-11-Agentics/c11/pull/462), [#463](https://github.com/Stage-11-Agentics/c11/pull/463)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **The New Workspace picker is a fast path.** A click selects and never scrolls, and a double-click creates. Pins are tiles above the list, with Cmd+1 to 9 creating from pin N and drag to reorder. Search is built in. A query starting with `~` or `/` becomes a path, with Tab completing folders. A green dot marks directories that already have a workspace open, and Option-double-click switches to it. Missing directories show struck through and refuse to create. Recents now hold 250 directories, and the cap can no longer evict a pin. A right-click menu offers New workspace here, Pin, Reveal in Finder, Copy path and Remove from recents. The picker offers the four standard layouts. ([#457](https://github.com/Stage-11-Agentics/c11/pull/457)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Recents and workspace creation from the CLI.** `c11 workspace recents list [--pinned] [--json]`, `recents pin|unpin|remove`, and `c11 workspace new --dir <path|query> [--layout] [--name] [--agent]` share the picker's model and ranking. Ties fail loudly with candidates, and creation does not steal focus. ([#457](https://github.com/Stage-11-Agentics/c11/pull/457)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Workspace root is visible and editable from the workspace.** An info button in the title bar opens a popover with the root and the focused directory, with Change, Use Focused Directory and Clear. The sidebar row menu has a matching Workspace Root submenu. `c11 get-workspace-root [--json]` pairs with `set-workspace-root`, `c11 tree` shows `root=` on workspace lines, and `new-tab --cwd` is now honored. A workspace with no root adopts the first directory its focused shell reports, once. ([#453](https://github.com/Stage-11-Agentics/c11/pull/453)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Horizontal scroll over the sidebar steps between workspaces.** A mouse's horizontal wheel, Shift+wheel or a two-finger sideways swipe moves to the next or previous workspace, with wrap-around, like the Next and Previous Workspace actions. Vertical scrolling is untouched, and a vertical scroll that drifts sideways never switches. ([#464](https://github.com/Stage-11-Agentics/c11/pull/464)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Session history.** The first time c11 is about to overwrite or remove a session file in a launch, it copies it to `session-history/` beside it, keeping the newest 10. To recover, quit c11, copy the wanted file from `~/Library/Application Support/c11/session-history/` over `session-<bundle id>.json`, and relaunch. ([#467](https://github.com/Stage-11-Agentics/c11/pull/467)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!

### Changed

- **The vocabulary is workspace, area, tab.** The UI and CLI now say workspace → area → tab. An area is a split region, and a tab is a terminal, browser or markdown view. The sidebar entry stays a workspace. 88 English strings and their translations change, Settings > Surfaces becomes Tabs & Areas, and "Show Surface IDs in Tab Titles" becomes "Show Tab Numbers in Tab Titles". The CLI gains canonical `new-tab`, `list-areas`, `focus-area`, `close-tab`, `move-tab` and more, with `--tab`, `--area`, `--before-tab`, `--after-tab` and `--in-tab`. Socket methods are now `tab.*` and `area.*`. `C11_TAB_ID` and `C11_TAB_NUM` are the canonical environment variables. Old command names, flags, refs (`surface:` and `pane:`) and socket methods keep working as hidden aliases, and JSON keeps the old keys beside the new ones for one release. List-row ref values now print `tab:N` and `area:N`. The new CLI also works against an older app. ([#472](https://github.com/Stage-11-Agentics/c11/pull/472)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Breaking for scripts: `--tab` now names a tab.** On `set-status`, `clear-status`, `list-status`, `set-progress`, `clear-progress`, `log`, `clear-log`, `list-log` and `sidebar-state`, `--tab` means a tab. A workspace ref or workspace UUID passed as `--tab` keeps its workspace meaning. A bare integer `--tab`, formerly a workspace index, now fails as ambiguous: use `--workspace N` or `tab:N`. From a shell outside c11, `--tab tab:N` needs `--workspace`. `--surface` and `--panel` from outside c11 now error instead of silently acting on the selected workspace. ([#472](https://github.com/Stage-11-Agentics/c11/pull/472)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **A workspace root decides where every new terminal starts.** A new tab, a split, and the tab-bar agent button used to follow the focused shell's directory, so a shell that had `cd`'d elsewhere pulled new agents with it. They now start in the workspace root, or in the focused tab's directory when the root is missing or cleared. Pass `--cwd .` to follow the shell, or clear the root. A new workspace created without a directory starts in the selected workspace's root. ([#453](https://github.com/Stage-11-Agentics/c11/pull/453)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Every quit asks first.** App-menu Quit, Dock Quit, the menu bar extra and AppleScript quit now show a sheet, where only Cmd+Q did before. Cancel is the default, so Return and Escape keep c11 open. The Cmd+Q checkbox is gone, and the Warn Before Quit setting is the only opt-out. Restarts, Sparkle updates and logout do not prompt. ([#456](https://github.com/Stage-11-Agentics/c11/pull/456)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Closing a window or a real workspace asks first.** The red close button, File > Close All and Cmd+Ctrl+W show a sheet with Cancel as the default. Closing a workspace asks unless it is a single idle terminal, so Cmd+Shift+W prompts more often. Destructive confirm cards and legacy close alerts default to Cancel, and Cmd+D no longer accepts a close. Closing the last window may pause up to 5 seconds while the session snapshot is saved, as quit already did. ([#469](https://github.com/Stage-11-Agentics/c11/pull/469)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Destructive commands no longer fall back to the focused target.** Closing or acting on a surface, workspace or window with an empty or stale ref now returns `not_found` or `empty_ref` and closes nothing. An agent whose `C11_WORKSPACE_ID` points at a closed workspace gets `not_found` instead of acting on the selected workspace. The CLI refuses an explicitly empty ref. ([#469](https://github.com/Stage-11-Agentics/c11/pull/469)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **The resume picker no longer costs you the previous session.** Autosave is held while the picker is open, so Skip, a partial resume, or quitting with the picker up no longer destroys the session. Esc does nothing in the picker. Skip takes a click, and Return still resumes the selection. ([#467](https://github.com/Stage-11-Agentics/c11/pull/467)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!

### Fixed

- **Closing the last window keeps your session.** It used to delete the session file, so nothing came back on relaunch. The window's full session is now written as it closes. If you open a new window before quitting, that replaces the kept snapshot. ([#469](https://github.com/Stage-11-Agentics/c11/pull/469)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Agent detection works for long binary paths.** A `claude` binary at a long absolute path was classified `unknown`, because `ps` clips its command column to 16 characters and the parser misread a padded tty column. Detection now uses the full argv[0] and `proc_pidpath`. ([#471](https://github.com/Stage-11-Agentics/c11/pull/471)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!
- **Picking "Grok latest" in Edit Launch Agents selects Grok Build.** That row is OpenRouter's `grok-latest` alias, which the `grok` CLI rejects as an unknown model id. The editor now selects the Grok Build harness with the newest model the CLI publishes, and router-only xAI ids stay on the routers that publish them. ([#468](https://github.com/Stage-11-Agentics/c11/pull/468)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!

### Removed

- **Close Other Workspaces.** It is gone from the Workspace menu, the command palette and the sidebar context menu, and `workspace-action close_others` is removed from the socket. Close Workspace, Close Workspaces Above and Close Workspaces Below stay. ([#456](https://github.com/Stage-11-Agentics/c11/pull/456), [#458](https://github.com/Stage-11-Agentics/c11/pull/458), [#469](https://github.com/Stage-11-Agentics/c11/pull/469)) — thanks [@BenevolentFutures](https://github.com/BenevolentFutures)!

### Thanks to 1 contributor!

[@BenevolentFutures](https://github.com/BenevolentFutures)

## [0.66.1] - 2026-09-13

Headline: **Background agent terminals stop burning the efficiency cores.**
Expand Down
32 changes: 16 additions & 16 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2323,10 +2323,10 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -2404,7 +2404,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
EXECUTABLE_NAME = c11;
Expand All @@ -2414,7 +2414,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
OTHER_LDFLAGS = (
"-lc++",
"-framework",
Expand Down Expand Up @@ -2445,7 +2445,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
EXECUTABLE_NAME = c11;
Expand All @@ -2455,7 +2455,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"-lc++",
Expand Down Expand Up @@ -2523,10 +2523,10 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2541,14 +2541,14 @@
buildSettings = {
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/c11.app/Contents/MacOS/c11";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@loader_path/../../../c11.app/Contents/MacOS",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.logictests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2563,14 +2563,14 @@
buildSettings = {
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/c11 DEV.app/Contents/MacOS/c11.debug.dylib";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@loader_path/../../../c11\\ DEV.app/Contents/MacOS",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.logictests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2586,10 +2586,10 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.apptests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2605,10 +2605,10 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 130;
CURRENT_PROJECT_VERSION = 131;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 0.66.1;
MARKETING_VERSION = 0.67.0;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.stage11.c11.apptests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
29 changes: 6 additions & 23 deletions Sources/TabRailTipCenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import Bonsplit
/// and whether the tab sheet is open. This type decides when the tip is on
/// screen and owns the popover. The policy decides when a new offer may start.
///
/// The popover is not modal. The first time it opens during an offer, if
/// the terminal window was key just before, that window is made key once.
/// Later refreshes do not take the keyboard back. Clicking outside ends
/// this offer and does not dismiss the tip. Undo restores Tabs and does
/// the same.
/// The popover is not modal. Each time it opens, if the terminal window
/// was key just before, that window is made key again. A refresh of an
/// already-shown tip does not take the keyboard back. Clicking outside
/// ends this offer and does not dismiss the tip. Undo restores Tabs and
/// does the same.
@MainActor
final class TabRailTipCenter: NSObject, NSPopoverDelegate {
static let shared = TabRailTipCenter()
Expand Down Expand Up @@ -38,8 +38,6 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
private var hosting: NSHostingController<TabRailTipView>?
private var shownAnchor: NSView?
private var refreshQueued = false
/// True after this offer has given key back once.
private var restoredKey = false
/// Readable from `deinit`, which may not be on the main actor.
nonisolated(unsafe) private var escapeMonitor: Any?

Expand Down Expand Up @@ -160,7 +158,6 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
reanchoring = false
anchorBeforeSwitch = nil
stamped = false
clearKeyRestore()
hidePopover()
TabLayoutSettings.setMode(.tabs)
}
Expand All @@ -187,7 +184,6 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
reanchoring = false
anchorBeforeSwitch = nil
stamped = false
clearKeyRestore()
hidePopover()
}

Expand Down Expand Up @@ -335,7 +331,6 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
reanchoring = false
anchorBeforeSwitch = nil
stamped = false
clearKeyRestore()
hidePopover()
}

Expand All @@ -357,7 +352,7 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
guard popover.isShown else { return }
installEscapeMonitor()
guard wasKey else { return }
claimKeyOnce(anchor.window)
anchor.window?.makeKey()
}

private func hidePopover() {
Expand All @@ -378,18 +373,6 @@ final class TabRailTipCenter: NSObject, NSPopoverDelegate {
}
}

/// Once per offer, and only when the terminal window was key immediately
/// before the popover opened. A later refresh must not call `makeKey`.
private func claimKeyOnce(_ window: NSWindow?) {
guard !restoredKey, let window else { return }
window.makeKey()
restoredKey = true
}

private func clearKeyRestore() {
restoredKey = false
}

private func ensurePopover() -> NSPopover {
if let popover { return popover }
let host = NSHostingController(rootView: TabRailTipView(model: model))
Expand Down
Loading