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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public nonisolated enum ActionCatalog {
"diffViewerScrollHalfPageUp": "diffViewerHalfPageUp",
"diffViewerScrollToBottom": "diffViewerGoToBottom",
"diffViewerScrollToTop": "diffViewerGoToTop",
// Cmd-T's id named a setting value; it opens the tabs.newTabKind kind (cmuxterm-hq#1829).
// Cmd-T's id named a setting value; it opens the New Tab page (cmuxterm-hq#1829).
"newTab.sameKind": "newTab.default",
// New Workspace Like This keeps the directory and the group (cmuxterm-hq#1829).
"workspace.newInGroup": "workspace.newLikeThis",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
nonisolated enum TabActionCatalog: ActionCatalogGroup {
static func descriptors() -> [ActionDescriptor] {
[
// Cmd-T: the kind `tabs.newTabKind` names (the New Tab page by default). Was
// Cmd-T: the New Tab page, where the kind is picked (Leo 2026-10-12). Was
// `newTab.sameKind`, which named a setting value, not what it does (legacyAliases).
ActionDescriptor(
id: "newTab.default",
Expand All @@ -12,8 +12,8 @@ nonisolated enum TabActionCatalog: ActionCatalogGroup {
category: .tab, symbol: "plus.square", surfaces: [.palette, .keyboard, .menu],
arguments: [CatalogArgument.cwdString.optional], targets: [.tab], cliName: "tab new", mainMenu: .file
),
// New <Kind> Tab (cmuxterm-hq#1829): the selected tab's kind, whatever
// `tabs.newTabKind` says. Option-click on the strip's +; the tab menu
// New <Kind> Tab (cmuxterm-hq#1829): the selected tab's kind, an explicit
// pick. Option-click on the strip's +; the tab menu
// shows the kind ("New Browser Tab"). No default key: Opt-Cmd-T closes
// other tabs.
ActionDescriptor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public nonisolated struct AgentPageOps {
static let methods: [String: String] = {
var methods = Dictionary(uniqueKeysWithValues: [
"pane.checkpointAvailability", "pane.framePacing", "pane.painted", "pane.renderRate", "pane.saveLog", "pane.saveFile", "pane.showContextUsage", "pane.showDictation",
"pane.edit", "tab.open", "tab.typeAhead", "tab.jump", "tab.setDefaultKind",
"newTab.remember", "newTab.setTemplate", "newTab.inputReady", "newTab.touched", "newTab.context", "shortcut.edit", "action.run", "file.open", "browser.open",
"pane.edit", "tab.open", "tab.typeAhead", "tab.jump",
"newTab.remember", "newTab.inputReady", "newTab.touched", "newTab.context", "shortcut.edit", "action.run", "file.open", "browser.open",
"project.list", "project.browse", "workspace.chooseFolder", "workspace.useFolder", "chat.folder.choose", "app.action", "chats.open", "chats.openInTerminal", "chats.page",
"quick.dismiss", "quick.openInWindow", "quick.startInBackground", "quick.setOnTop", "quick.setCollapsed", "quick.floatingState", "quick.context", "pane.action", "pane.tabState", "chat.archive", "chat.markUnread", "chat.toolCard.respond", "chat.upload", "chat.sideChat", "chat.unsentDraft", "chat.scrollAnchor", "chat.scrollAnchor.get",
"shell.run", "shell.read", "shell.stop", "harness.health",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ public final class AgentPaneModel {
@ObservationIgnored public var onJump: ((AgentPaneJumpTarget, String) -> Void)?
/// The new tab page asked to change a kind's shortcut.
@ObservationIgnored public var onEditShortcut: ((AgentPaneTabKind) -> Void)?
/// The new tab page's "default: X" toggle and template dots (`tab.setDefaultKind`, `newTab.setTemplate`).
@ObservationIgnored public var onNewTabSetting: ((AgentPaneNewTabSetting) -> Void)?
/// Runs an app action requested by an empty-state or new-tab control.
@ObservationIgnored public var onRunAction: ((String) -> Bool)?
/// Resolves the explicit Browse… fallback in the project picker.
Expand Down Expand Up @@ -257,8 +255,6 @@ public final class AgentPaneModel {
case .rememberNewTab(let pick): guard let onRememberNewTab else { return Self.unsupported("newTab.remember") }; onRememberNewTab(pick); return AgentPaneReply.success()
case .runAction(let id): return runAction(id)
case .jump(let target, let id): guard newTab != nil, let onJump else { return Self.unsupported("tab.jump") }; onJump(target, id); return AgentPaneReply.success()
case .setDefaultKind(let kind): return write(.defaultKind(kind), method: "tab.setDefaultKind")
case .setNewTabTemplate(let template): return write(.template(template), method: "newTab.setTemplate")
case .chooseFolder where remoteChat: return Self.unsupported("workspace.chooseFolder")
case .chooseFolder: return await chooseFolder()
case .chooseChatFolder: return await chooseChatFolder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,11 @@ public nonisolated struct AgentPaneNewTab: Codable, Sendable, Equatable {
public var omnibar: AgentPaneOmnibar
/// Recent local project folders from agent history, classic sessions and git roots.
public var projects: [String]
/// What Cmd-T opens (`tabs.newTabKind`: "same-kind", "terminal",
/// "browser", "agent", "page" or "auto"), for the "default: X" toggle.
public var defaultKind: String?
/// The design to show; nil is the page's default (B).
public var layout: AgentPaneNewTabLayout?
/// The template (`tabs.newTabTemplate`: "default", "composer", "threads", "console",
/// "classic" or "terminal"); nil follows `layout`. The page validates it.
public var template: String?
/// true shows the template dots (Debug Settings `newTab.templateSwitcher`, on by default
/// since cx-l1jv); nil hides them.
public var templateSwitcher: Bool?
/// The agent last picked.
public var lastAgent: String?
/// The harness and model picked last, anywhere and per folder; nil when none was.
Expand All @@ -56,9 +50,9 @@ public nonisolated struct AgentPaneNewTab: Codable, Sendable, Equatable {

public init(kind: AgentPaneTabKind, hotkeys: [AgentPaneTabKind: String] = [:], cwd: String? = nil,
location: String? = nil, omnibar: AgentPaneOmnibar = AgentPaneOmnibar(), projects: [String] = [],
defaultKind: String? = nil, layout: AgentPaneNewTabLayout? = nil,
layout: AgentPaneNewTabLayout? = nil,
lastAgent: String? = nil, lastPicks: AgentPaneLastPicks? = nil, home: String? = nil,
template: String? = nil, templateSwitcher: Bool? = nil) {
template: String? = nil) {
self.kind = kind
self.hotkeys = Dictionary(uniqueKeysWithValues: hotkeys.map { ($0.key.rawValue, $0.value) })
self.cwd = cwd
Expand All @@ -67,10 +61,8 @@ public nonisolated struct AgentPaneNewTab: Codable, Sendable, Equatable {
// Keep the host DTO lossless. The web page owns presentation limits and
// validation after the Codable handshake crosses the bridge.
self.projects = projects
self.defaultKind = defaultKind
self.layout = layout
self.template = template
self.templateSwitcher = templateSwitcher
self.lastAgent = lastAgent
self.lastPicks = lastPicks
self.home = home
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,3 @@ public nonisolated struct AgentPaneOpenTab: Equatable, Sendable {
self.keep = keep
}
}

/// A setting the new tab page writes: its "default: X" toggle or a template dot. The App checks the value.
public nonisolated enum AgentPaneNewTabSetting: Equatable, Sendable {
case defaultKind(String)
case template(String)
}

extension AgentPaneModel {
/// Only while the tab is still the page.
func write(_ setting: AgentPaneNewTabSetting, method: String) -> [String: Any] {
guard newTab != nil, let onNewTabSetting else { return Self.unsupported(method) }
onNewTabSetting(setting)
return AgentPaneReply.success()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,6 @@ extension AgentPaneRequest {
} else {
self = .unsupported(method)
}
case "tab.setDefaultKind":
if let kind = params?["kind"] as? String, !kind.isEmpty, kind.count <= 32 {
self = .setDefaultKind(kind)
} else {
self = .unsupported(method)
}
case "newTab.setTemplate":
if let template = params?["template"] as? String, !template.isEmpty, template.count <= 32 {
self = .setNewTabTemplate(template)
} else {
self = .unsupported(method)
}
case "project.browse": self = .browseProject
case "workspace.chooseFolder": self = .chooseFolder
case "chat.folder.choose": self = .chooseChatFolder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ public nonisolated enum AgentPaneRequest: Equatable, Sendable {
case jump(AgentPaneJumpTarget, id: String)
/// The new tab page asked to change a kind's New shortcut.
case editShortcut(AgentPaneTabKind)
/// The new tab page's "default: X" toggle: what Cmd-T opens
/// (`tabs.newTabKind`; the App checks the value).
case setDefaultKind(String)
/// The new tab page's template dots (`newTab.setTemplate`): the
/// template to save as `tabs.newTabTemplate` (the App checks the value).
case setNewTabTemplate(String)
/// The new tab page asked the app to run a user facing action.
case runAction(String)
/// The new-tab project picker asked for the explicit Browse… fallback.
Expand Down
2 changes: 0 additions & 2 deletions Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentTabs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ final class AgentTabStore {
model.onSessionChange = { [weak self] session in
guard let self else { return }
let key = resolve(provisional)
newTabPages[key]?.handler.becameChat()
newTabPages[key] = nil
views[key]?.applyTheme() // now the agent chat surface (R55)
sessions[key] = session
Expand All @@ -374,7 +373,6 @@ final class AgentTabStore {
model.onRememberNewTab = { [weak self] pick in self?.pageHandler(provisional)?.remember(pick) }
model.onJump = { [weak self] target, id in self?.newTabPage(provisional)?.handler.jump(target, id) }
model.onEditShortcut = { [weak self] kind in self?.newTabPage(provisional)?.handler.editShortcut(kind) }
model.onNewTabSetting = { [weak self] setting in self?.newTabPage(provisional)?.handler.write(setting) }
model.onRunAction = { [weak self] id in
guard let self else { return false }
// On this tab's pane: the New Tab page opens beside the tab that asked.
Expand Down
2 changes: 0 additions & 2 deletions Packages/macOS/CmuxNext/Sources/CmuxNextApp/AppServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ final class AppServices {
service.start()
return service
}()
/// The kinds of tabs opened on purpose, by folder, for `tabs.newTabKind: auto`.
var newTabKinds = NewTabKindMemory()
/// Each pane's own Show Tab Bar choice (`PaneTabBar`).
let paneTabBars = PaneTabBarChoices()
/// Pane controller mounts and releases in any window (`PaneMounts`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ enum DebugNewTab {
spares.append(.object(entry))
}
return .object([
"likely": .bool(pool.isLikely), "spares": .array(spares), "openings": .array(pool.openings.map(opening)),
"spares": .array(spares), "openings": .array(pool.openings.map(opening)),
"target_window": pool.target.map { .number(Double($0.windowNumber)) } ?? .null,
"last_retarget_ms": pool.lastRetargetMilliseconds.map { .number($0) } ?? .null,
"last_recycle_refusal": pool.lastRecycleRefusal.map { .string($0) } ?? .null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ enum AddHarnessHandler {
// The same pane path as New Agent Chat: from Home a new workspace on the Harnesses
// page; from a settling workspace its first pane, then the tab there.
AgentHandlers.withAgentPane(invocation, context: context, seed: seed) { pane in
if invocation.origin == .user { context.services.newTabKinds.record(.agent, folder: nil) }
pane.openAgentTab(seed: AgentPaneSeedSource(seed))
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ extension AgentHandlers {
let services = context.services
let daemon = services.activeDaemon
guard let window = context.activeWindow, services.agentTabs.canHost(on: daemon) else { return false }
if invocation.origin == .user { services.newTabKinds.record(.agent, folder: nil) }
let windowID = window.state.id
context.registry.track(Task { @MainActor in
var spawn = WorkspaceSpawn(cwd: seed.cwd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ enum AgentHandlers {
return true
}
let folder = pane.selectedTab?.cwd
services.newTabKinds.record(.agent, folder: folder)
let source = pane.agentSeedFromSelectedTab()
let daemon = pane.daemon
context.registry.track(Task { @MainActor in
Expand All @@ -272,7 +271,6 @@ enum AgentHandlers {
}

private static func openNewAgentChat(in pane: PaneController, invocation: ActionInvocation, context: AppActionContext) {
if invocation.origin == .user { context.services.newTabKinds.record(.agent, folder: pane.selectedTab?.cwd) }
pane.newAgentTab()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ nonisolated struct BrowserOpenPlan: Equatable, Sendable {
/// The engine the tab opens with: the caller's, or Chromium for a
/// Chromium internal page.
var engine: String?
/// The engine remembered as the user's choice for the folder: the
/// caller's only, so one chrome:// page does not make every later Cmd-T
/// a Chromium tab.
var recordedEngine: String?

@MainActor
static func make(url text: String?, engine requested: String?, origin: ActionOrigin) -> Outcome {
Expand Down Expand Up @@ -56,6 +52,6 @@ nonisolated struct BrowserOpenPlan: Equatable, Sendable {
case .chromium?, nil: url = resolved
}
}
return .open(BrowserOpenPlan(url: url, engine: engine, recordedEngine: requested))
return .open(BrowserOpenPlan(url: url, engine: engine))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,4 @@ nonisolated enum NewTabKind: Equatable, Sendable {
if agent, services.agentTabs.isNewTabPage(id) { return .page }
return resolve(selectedKind: tab?.kind, engine: tab?.browserEngine, isLocalBrowser: id.hasPrefix(LocalBrowserTab.prefix), isAgent: agent)
}

/// `tabs.newTabKind` over the same-kind rule. Auto takes the kind last
/// opened in the focused tab's folder (else anywhere), and the same
/// kind before anything was opened.
/// The Terminal template (`tabs.newTabTemplate`) no longer turns the page into a terminal:
/// Cmd-T always opens the New Tab page unless `tabs.newTabKind` names another kind
/// (Lawrence 2026-10-10, cx-n0i9).
static func resolve(_ setting: NewTabDefaultKind, template: NewTabTemplate? = nil, sameKind: NewTabKind,
recent: NewTabKind?) -> NewTabKind {
resolveKind(setting, sameKind: sameKind, recent: recent)
}

private static func resolveKind(_ setting: NewTabDefaultKind, sameKind: NewTabKind, recent: NewTabKind?) -> NewTabKind {
switch setting {
case .sameKind: sameKind
case .terminal: .terminal
case .browser: .browser(engine: nil)
case .agent: .agent
case .page: .page
case .auto: recent ?? sameKind
}
}
}

/// What Auto remembers: the kind last opened on purpose (Cmd-T, a New
/// action, a new tab page choice) in each folder and overall. The new tab
/// page itself is a chooser, not a kind. Bounded to the most recent folders.
nonisolated struct NewTabKindMemory: Sendable {
static let maximumFolders = 64
private(set) var last: NewTabKind?
private var byFolder: [String: NewTabKind] = [:]
private var folders: [String] = []

mutating func record(_ kind: NewTabKind, folder: String?) {
guard kind != .page else { return }
last = kind
guard let folder else { return }
byFolder[folder] = kind
folders.removeAll { $0 == folder }
folders.append(folder)
if folders.count > Self.maximumFolders { byFolder[folders.removeFirst()] = nil }
}

func recent(in folder: String?) -> NewTabKind? {
folder.flatMap { byFolder[$0] } ?? last
}
}
Loading
Loading