Skip to content
Closed
125 changes: 125 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -257164,6 +257164,131 @@
}
}
},
"workspaceGroup.agentActivity.a11y": {
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "الوكلاء قيد التشغيل: %1$lld. بحاجة إلى إدخال: %2$lld."
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Pokrenuti agenti: %1$lld. Potreban unos: %2$lld."
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Kørende agenter: %1$lld. Kræver input: %2$lld."
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Aktive Agenten: %1$lld. Eingabe erforderlich: %2$lld."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Running agents: %1$lld. Need input: %2$lld."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Agentes en ejecución: %1$lld. Necesitan entrada: %2$lld."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Agents en cours : %1$lld. Saisie requise : %2$lld."
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Agenti in esecuzione: %1$lld. Richiedono input: %2$lld."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "実行中のエージェント: %1$lld。入力待ち: %2$lld。"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "ភ្នាក់ងារកំពុងដំណើរការ៖ %1$lld។ ត្រូវការបញ្ចូល៖ %2$lld។"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "실행 중인 에이전트: %1$lld. 입력 필요: %2$lld."
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Kjørende agenter: %1$lld. Trenger inndata: %2$lld."
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Działające agenty: %1$lld. Wymaga danych wejściowych: %2$lld."
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Agentes em execução: %1$lld. Precisam de entrada: %2$lld."
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Работающих агентов: %1$lld. Требуется ввод: %2$lld."
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "เอเจนต์ที่กำลังทำงาน: %1$lld ต้องการอินพุต: %2$lld"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Çalışan aracılar: %1$lld. Girdi gereken: %2$lld."
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Агентів у роботі: %1$lld. Потрібне введення: %2$lld."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "运行中的代理:%1$lld。需要输入:%2$lld。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "執行中的代理:%1$lld。需要輸入:%2$lld。"
}
}
}
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"workspaceGroup.autoName.numbered": {
"extractionState": "manual",
"localizations": {
Expand Down
18 changes: 15 additions & 3 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11918,12 +11918,16 @@ struct VerticalTabsSidebar: View, Equatable {
)
)
})
let agentActivityCountsByWorkspaceId = workspaceRowInputsById.mapValues {
$0.workspace.agentActivityCounts
}
let groupRowSnapshotsById = Dictionary(uniqueKeysWithValues: renderContext.workspaceGroups.map { group in
(
group.id,
sidebarWorkspaceGroupRowSnapshot(
group: group,
memberWorkspaceIds: renderContext.memberWorkspaceIdsByGroupId[group.id] ?? [],
agentActivityCountsByWorkspaceId: agentActivityCountsByWorkspaceId,
renderContext: renderContext,
unreadSnapshot: unreadSnapshot,
notificationIndex: notificationIndex,
Expand All @@ -11947,6 +11951,7 @@ struct VerticalTabsSidebar: View, Equatable {
return sidebarWorkspaceGroupTableConfiguration(
group: group,
memberWorkspaceIds: renderContext.memberWorkspaceIdsByGroupId[groupId] ?? [],
agentActivityCountsByWorkspaceId: agentActivityCountsByWorkspaceId,
renderContext: renderContext
)
case .workspace(let workspaceId):
Expand Down Expand Up @@ -13591,13 +13596,17 @@ struct VerticalTabsSidebar: View, Equatable {
)
)
})
let agentActivityCountsByWorkspaceId = workspaceRowInputsById.mapValues {
$0.workspace.agentActivityCounts
}
let _ = anchorCwdRevision
let groupRowSnapshotsById = Dictionary(uniqueKeysWithValues: renderContext.workspaceGroups.map { group in
(
group.id,
sidebarWorkspaceGroupRowSnapshot(
group: group,
memberWorkspaceIds: renderContext.memberWorkspaceIdsByGroupId[group.id] ?? [],
agentActivityCountsByWorkspaceId: agentActivityCountsByWorkspaceId,
renderContext: renderContext,
unreadSnapshot: unreadSnapshot,
notificationIndex: notificationIndex,
Expand Down Expand Up @@ -15699,9 +15708,12 @@ struct TabItemView: View, Equatable {
let moveUpActionText = String(localized: "sidebar.workspace.moveUpAction", defaultValue: "Move Up")
let moveDownActionText = String(localized: "sidebar.workspace.moveDownAction", defaultValue: "Move Down")
let latestNotificationSubtitle = latestNotificationText
let conversationMessageSubtitle = !settings.hidesAllDetails && settings.iMessageModeEnabled
? workspaceSnapshot.latestConversationMessage?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
: nil
let conversationMessageSubtitle = SidebarAgentActivitySummary.conversationSubtitle(
showsAgentActivity: showsAgentActivity,
hidesAllDetails: settings.hidesAllDetails,
iMessageModeEnabled: settings.iMessageModeEnabled,
message: workspaceSnapshot.latestConversationMessage
)
let effectiveSubtitle = latestNotificationSubtitle ?? conversationMessageSubtitle
let subtitleLineLimit = latestNotificationSubtitle == nil ? 2 : settings.notificationMessageLineLimit
// Bound notification payloads before shaping so pathological text stays cheap in lazy, Equatable rows.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct SidebarGroupHeaderRowModel: Equatable, Hashable {
let isMultiSelected: Bool
let multiSelectionBackgroundStyle: SidebarWorkspaceRowBackgroundStyle
let memberCount: Int
let showsAgentActivity: Bool
let runningAgentCount: Int
let needsInputAgentCount: Int
var anchorUnreadCount: Int
var canMarkRead: Bool
var canMarkUnread: Bool
Expand Down
61 changes: 58 additions & 3 deletions Sources/Sidebar/AppKitList/Cells/SidebarGroupHeaderRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class SidebarGroupHeaderTableCellView: NSTableCellView {
private let chevronButton = SidebarHeaderGlyphButton()
private let iconImageView = NSImageView()
private let nameField = NSTextField(labelWithString: "")
private let agentActivityField = NSTextField(labelWithString: "")
// Direct-draw badge (shared with workspace rows): NSTextField's
// intrinsic insets shift single digits off the circle's optical center.
private let unreadBadgeView = SidebarRowUnreadBadgeView()
Expand Down Expand Up @@ -72,6 +73,8 @@ final class SidebarGroupHeaderTableCellView: NSTableCellView {
nameField.cell?.truncatesLastVisibleLine = true
addSubview(nameField)

addSubview(agentActivityField)

addSubview(unreadBadgeView)

plusButton.onClick = { [weak self] in self?.actions?.onTapPlus() }
Expand Down Expand Up @@ -180,6 +183,43 @@ final class SidebarGroupHeaderTableCellView: NSTableCellView {
)
nameField.textColor = model.isAnchorActive ? .labelColor : NSColor.labelColor.withAlphaComponent(0.9)

agentActivityField.isHidden = !model.showsAgentActivity
if model.showsAgentActivity {
let font = NSFont.systemFont(
ofSize: GlobalFontMagnification.scaledSize(metrics.nameFontSize, percent: percent),
weight: .semibold
)
let running = "▶ \(model.runningAgentCount)"
let needsInput = "! \(model.needsInputAgentCount)"
let text = "\(running) \(needsInput)"
let attributed = NSMutableAttributedString(
string: text,
attributes: [.font: font]
)
attributed.addAttribute(
.foregroundColor,
value: NSColor.systemBlue,
range: NSRange(location: 0, length: running.utf16.count)
)
attributed.addAttribute(
.foregroundColor,
value: NSColor.systemOrange,
range: NSRange(
location: text.utf16.count - needsInput.utf16.count,
length: needsInput.utf16.count
)
)
agentActivityField.attributedStringValue = attributed
let accessibilityText = SidebarAgentActivitySummary.accessibilityText(
counts: .init(
running: model.runningAgentCount,
needsInput: model.needsInputAgentCount
)
)
agentActivityField.toolTip = accessibilityText
agentActivityField.setAccessibilityLabel(accessibilityText)
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
let showsBadge = model.anchorUnreadCount > 0
unreadBadgeView.isHidden = !showsBadge
if showsBadge {
Expand Down Expand Up @@ -393,8 +433,12 @@ final class SidebarGroupHeaderTableCellView: NSTableCellView {
)
}

let nameAvailable = max(0, (plusButton.frame.minX - 4) - x
- (badgeSize.width > 0 ? badgeSize.width + 6 : 0))
let activitySize = agentActivityField.isHidden
? .zero
: agentActivityField.attributedStringValue.size()
let trailingDetailsWidth = (activitySize.width > 0 ? ceil(activitySize.width) + 8 : 0)
+ (badgeSize.width > 0 ? badgeSize.width + 6 : 0)
let nameAvailable = max(0, (plusButton.frame.minX - 4) - x - trailingDetailsWidth)
let nameSize = nameField.attributedStringValue.size()
// The field owns ALL remaining width (truncation only when genuinely
// out of space); the badge tracks the measured text width instead.
Expand All @@ -404,8 +448,19 @@ final class SidebarGroupHeaderTableCellView: NSTableCellView {
width: nameAvailable,
height: ceil(nameSize.height)
)
var detailX = x + min(ceil(nameSize.width), nameAvailable)
if !agentActivityField.isHidden {
detailX += 8
agentActivityField.frame = NSRect(
x: detailX,
y: midY - ceil(activitySize.height) / 2,
width: ceil(activitySize.width),
height: ceil(activitySize.height)
)
detailX = agentActivityField.frame.maxX
}
if !unreadBadgeView.isHidden {
let badgeX = x + min(ceil(nameSize.width), nameAvailable) + 6
let badgeX = detailX + 6
unreadBadgeView.frame = NSRect(
x: badgeX,
y: midY - badgeSize.height / 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,11 +516,12 @@ final class SidebarWorkspaceRowTableCellView: NSTableCellView {
}
}

let conversationSubtitle: String? = {
guard !settings.hidesAllDetails, settings.iMessageModeEnabled else { return nil }
let trimmed = snapshot.latestConversationMessage?.trimmingCharacters(in: .whitespacesAndNewlines)
return (trimmed?.isEmpty == false) ? trimmed : nil
}()
let conversationSubtitle = SidebarAgentActivitySummary.conversationSubtitle(
showsAgentActivity: model.showsAgentActivity,
hidesAllDetails: settings.hidesAllDetails,
iMessageModeEnabled: settings.iMessageModeEnabled,
message: snapshot.latestConversationMessage
)
let effectiveSubtitle = model.latestNotificationText ?? conversationSubtitle
let subtitleLineLimit = model.latestNotificationText == nil ? 2 : settings.notificationMessageLineLimit
subtitleView.isHidden = effectiveSubtitle == nil
Expand Down
5 changes: 4 additions & 1 deletion Sources/Sidebar/SidebarWorkspaceSnapshotRefreshPolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extension SidebarWorkspaceSnapshotBuilder.Snapshot {
let checklistTotalCount: Int
let checklistFirstUncheckedText: String?
let activeCodingAgentCount: Int
let agentActivityCounts: SidebarAgentActivitySummary.Counts
}

var contextMenuImmediateFields: ContextMenuImmediateFields {
Expand All @@ -33,7 +34,8 @@ extension SidebarWorkspaceSnapshotBuilder.Snapshot {
checklistCompletedCount: checklistCompletedCount,
checklistTotalCount: checklistTotalCount,
checklistFirstUncheckedText: checklistFirstUncheckedText,
activeCodingAgentCount: activeCodingAgentCount
activeCodingAgentCount: activeCodingAgentCount,
agentActivityCounts: agentActivityCounts
)
}

Expand All @@ -58,6 +60,7 @@ extension SidebarWorkspaceSnapshotBuilder.Snapshot {
// The loading spinner is a leading row glyph like mediaActivity, so
// it also updates immediately while the context menu is open.
activeCodingAgentCount: snapshot.activeCodingAgentCount,
agentActivityCounts: snapshot.agentActivityCounts,
compactGitBranchSummaryText: compactGitBranchSummaryText,
compactDirectoryCandidates: compactDirectoryCandidates,
compactBranchDirectoryCandidates: compactBranchDirectoryCandidates,
Expand Down
Loading