From d6ea400baefb6b23517aa1aaeb50463d0c3b5658 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 6 Oct 2026 12:14:09 -0700 Subject: [PATCH 1/2] test: a New Tab page tab draws the new-tab icon, not the agent chat bubble Co-Authored-By: Claude Opus 5.5 --- .../ConversationTabItemTests.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Packages/macOS/CmuxNext/Tests/CmuxNextBridgeTests/ConversationTabItemTests.swift b/Packages/macOS/CmuxNext/Tests/CmuxNextBridgeTests/ConversationTabItemTests.swift index b8f98f6e8be1..be61cefe0241 100644 --- a/Packages/macOS/CmuxNext/Tests/CmuxNextBridgeTests/ConversationTabItemTests.swift +++ b/Packages/macOS/CmuxNext/Tests/CmuxNextBridgeTests/ConversationTabItemTests.swift @@ -23,4 +23,21 @@ struct ConversationTabItemTests { #expect(item.icon == .icon(.agentChat)) #expect(item.subtitle == nil) } + + /// A tab still on the New Tab page draws the registry's new-tab icon, so + /// it is told apart from an Agent chat tab by more than its title. + @Test func aNewTabPageTabDrawsTheNewTabIcon() throws { + let tab = TabSnapshot(surface: 5, kind: .conversation, title: "about:blank", url: "about:blank") + let pane = PaneSnapshot(id: 3, tabs: [tab]) + let workspace = WorkspaceSnapshot(id: 1, key: WorkspaceKey(rawValue: "0b6c4a52-6d3f-4c55-9d53-8f1f4e0f1a02"), name: "Home", + screens: [ScreenSnapshot(id: 4, layout: .leaf(3), panes: [pane])]) + let store = DaemonStore() + store.apply(snapshot: DaemonTree(workspaceRevision: 1, workspaces: [workspace])) + let model = try #require(store.workspaces.first?.screens.first?.panes.first?.tabs.first) + let page = TabItemMapping.shared.item(model, fallbackTitle: "New Tab", isNewTabPage: true) + let chat = TabItemMapping.shared.item(model, fallbackTitle: "Agent") + #expect(page.icon == .icon(.tabNew)) + #expect(chat.icon == .icon(.agentChat)) + #expect(page.icon != chat.icon) + } } From 28fb0e0c3ece32831199bf9376375fb10b4b5f25 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 6 Oct 2026 12:14:56 -0700 Subject: [PATCH 2/2] fix: New Tab page tabs draw the new-tab icon A tab still on the New Tab page and an Agent chat tab both rode a conversation record and drew the agent chat bubble. The pane knows which tabs are New Tab pages; it now tells the mapping, which draws tab.new. Co-Authored-By: Claude Opus 5.5 --- .../CmuxNext/Sources/CmuxNextApp/PaneController.swift | 8 +++++--- .../CmuxNext/Sources/CmuxNextBridge/TabItemMapping.swift | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/PaneController.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/PaneController.swift index b70f5016003b..c594bd6b1998 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/PaneController.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/PaneController.swift @@ -98,11 +98,13 @@ final class PaneController: SurfacePresenter, PresentablePane { let machine = daemon.isLocal ? nil : services.machines.machineBadge(daemon.machineID) let workspaceID = store.workspace(containing: pane.handle)?.id var items = pane.tabs.filter { !pendingClosed.contains($0.id) }.map { tab -> StripTabItem in - // A new tab page is "New Tab" until it becomes a chat (then the chat's title). + // A new tab page is "New Tab", with the new-tab icon, until it + // becomes a chat (then the chat's title and icon). + let isNewTabPage = tab.agentSession != nil && services.agentTabs.pageTabs.ids.contains(tab.id) let untitled = tab.agentSession != nil - ? services.agentTabs.pageTabs.ids.contains(tab.id) ? Strings.untitledBrowser : AgentPaneModel.tabTitle + ? isNewTabPage ? Strings.untitledBrowser : AgentPaneModel.tabTitle : tab.kind == .conversation ? services.home.tabTitle(for: tab) : tab.kind == .browser ? Strings.untitledBrowser : fallback - var item = TabItemMapping.shared.item(tab, fallbackTitle: untitled) + var item = TabItemMapping.shared.item(tab, fallbackTitle: untitled, isNewTabPage: isNewTabPage) if tab.page != nil, let page = services.pages.storeTabItem(tab) { // A page tab names and badges itself like the page it shows. item.title = page.title diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextBridge/TabItemMapping.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextBridge/TabItemMapping.swift index 0e522d6a617e..02cda8540e89 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextBridge/TabItemMapping.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextBridge/TabItemMapping.swift @@ -12,9 +12,10 @@ public struct TabItemMapping { /// page, whose recorded title is that page's address. /// A conversation tab (conversation-tabs-v1) rides a frontend browser /// record titled with the blank page's address: it shows `fallbackTitle` - /// (the conversation's) and the agent chat icon. + /// (the conversation's) and the agent chat icon. `isNewTabPage` marks a + /// tab still on the New Tab page, which draws the new-tab icon instead. - public func item(_ tab: TabModel, fallbackTitle: String) -> StripTabItem { + public func item(_ tab: TabModel, fallbackTitle: String, isNewTabPage: Bool = false) -> StripTabItem { let isBrowser = tab.kind == .browser let isConversation = tab.kind == .conversation let untitled = tab.displayTitle.isEmpty || ((isBrowser || isConversation) && Self.isBlankPageAddress(tab.displayTitle)) @@ -28,7 +29,7 @@ public struct TabItemMapping { id: StripTabID(tab.id), title: title, subtitle: isConversation ? nil : isBrowser ? tab.url : tab.cwd.map(SidebarMapping.shared.abbreviate), - icon: icon(tab, isBrowser: isBrowser, isConversation: isConversation), + icon: isNewTabPage ? .icon(.tabNew) : icon(tab, isBrowser: isBrowser, isConversation: isConversation), isPinned: tab.pinned, isUnread: tab.hasUnread, isBusy: busy.state.isLoading || isReportingProgress(tab),