Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading