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 @@ -49,10 +49,6 @@ public nonisolated struct WorkspaceRowContent: Hashable, Sendable {
/// Compact rows under a badge style (`SidebarRowBadgeStyle`): the dim time since the last
/// change on the right, unless the second line shows the last activity already.
public var badgeTime: String?
/// While the agent works: when it began (`SidebarWorkspaceDetail.activitySince`). The row
/// shows how long ("4m") beside the working dot when its name keeps its full width
/// (`WorkspaceRowView+Elapsed`); the hover card shows it to the second.
public var workingSince: Date?
/// The status block under the name (classic's): the reported entries
/// with `agentStatus` on, the log line with `log` on and the progress
/// label with `progress` on. Nil when none of those is reported, so a
Expand Down Expand Up @@ -128,7 +124,6 @@ public nonisolated struct WorkspaceRowContent: Hashable, Sendable {
)
trailingStatus = trailing
badgeTime = set.secondLine.contains(.lastActivity) ? nil : ws.lastActivity.map { Self.ageText($0, now: now) }
if case .working = activity { workingSince = ws.detail.activitySince }
}

/// An expanded row (`sidebar.density`, cx-13w3; T3 Code / Codex
Expand All @@ -153,7 +148,6 @@ public nonisolated struct WorkspaceRowContent: Hashable, Sendable {
expanded = true
time = ws.lastActivity.map { Self.ageText($0, now: now) }
self.meta = meta.isEmpty ? nil : meta.joined(separator: Self.separator)
if case .working = activity { workingSince = ws.detail.activitySince }
}

/// The age of a change, short: a time today, else days (`3d`), weeks
Expand All @@ -179,18 +173,6 @@ public nonisolated struct WorkspaceRowContent: Hashable, Sendable {
}
}

/// How long a row has worked, in whole minutes ("4m", "1h 3m"); nil under a minute, so the
/// row's text changes once a minute at most.
static func workingElapsed(since date: Date, now: Date) -> String? {
let minutes = (now.timeIntervalSince(date) / 60).rounded(.down)
guard minutes >= 1 else { return nil }
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
formatter.maximumUnitCount = 2
formatter.allowedUnits = [.day, .hour, .minute]
return formatter.string(from: minutes * 60)
}

/// The part of `reported` the shown elements draw; nil when empty.
static func status(_ reported: SidebarWorkspaceStatus?, _ set: WorkspaceRowElements) -> SidebarWorkspaceStatus? {
guard let reported else { return nil }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension WorkspaceRowView {
return
}
// The icon, the cleared unread badge and the idle activity are set in `configure`.
if timeText == nil, shown.workingSince == nil, let time = shown.badgeTime {
if timeText == nil, let time = shown.badgeTime {
timeText = time
timeLabel.setString(time)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ extension WorkspaceRowView {
performWithTheme {
// An idle workspace nobody named reads quiet until it is selected, hovered or busy.
title.textColor = isQuiet && !isSelected && !isSecondarySelected && !isHovered ? Palette.textSecondary : Palette.textPrimary
subtitle.setColor(Palette.textSecondary); [timeLabel, metaLabel, elapsedLabel].forEach { $0.setColor(Palette.textTertiary) }
subtitle.setColor(Palette.textSecondary); [timeLabel, metaLabel].forEach { $0.setColor(Palette.textTertiary) }
tabCount.setColor(Palette.textTertiary)
mutedMark.setTint(Palette.textTertiary)
if let machine { machineMark.setTint(machine.color) }
Expand Down Expand Up @@ -210,7 +210,6 @@ extension WorkspaceRowView {
machineMark.frame = NSRect(x: textX + Self.labelInset, y: cy - ind / 2, width: ind, height: ind)
textX = machineMark.frame.maxX + Metrics.space1
}
trailing = layoutElapsed(trailing: trailing, textX: textX, cy: cy, height: sh)
let textW = max(0, trailing - textX)
let titleArrival = titleArrivalPending || titleArrivalActive
title.isHidden = renaming || (isShowingPlaceholder && !titleArrival)
Expand All @@ -232,7 +231,7 @@ extension WorkspaceRowView {
updateCalmTitleTip()
subtitle.wantsLayer = true
subtitle.setTrailingFade(occlusion: overlayStart.map { max(0, subtitle.frame.maxX - ($0 - Metrics.space1)) } ?? 0, fade: fade, animated: fades)
for mark in [activity, rowBadge, agentMark, mutedMark, machineMark, audioButton, presenceDots, timeLabel, elapsedLabel] as [NSView] {
for mark in [activity, rowBadge, agentMark, mutedMark, machineMark, audioButton, presenceDots, timeLabel] as [NSView] {
fadeOpacity(mark, to: overlayStart.map { mark.frame.maxX > $0 - Metrics.space1 } == true ? 0 : 1, fades: fades)
}
layoutStatus(x: textX, width: max(0, b.width - Metrics.space3 - textX), head: b.maxY, bottom: full.maxY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Observation
import CmuxAgentBrands
import CmuxNextDesign
import CmuxNextIcons
import CmuxNextWakeups
import QuartzCore

final class WorkspaceRowView: SidebarRowView {
Expand All @@ -14,10 +13,6 @@ final class WorkspaceRowView: SidebarRowView {
let subtitle = SidebarRowView.label(font: SidebarStyle.subtitleFont)
/// Expanded rows (`sidebar.density`, cx-13w3; WorkspaceRowView+Expanded): the age and the meta line.
let timeLabel = SidebarRowView.label(font: SidebarStyle.subtitleFont), metaLabel = SidebarRowView.label(font: SidebarStyle.subtitleFont)
/// How long the agent has worked ("4m"), beside the working dot when the name has room (WorkspaceRowView+Elapsed).
let elapsedLabel = SidebarRowView.label(font: SidebarStyle.subtitleFont)
var workingSince: Date?
let elapsedTicker = DemandTimer(owner: "sidebar.row.elapsed")
let activity = StatusIndicatorView()
/// The running agent's brand mark (`SidebarAgentMarkVariant`); hidden when off.
internal let agentMark = DecorativeImageView() // the row's label says the agent's state
Expand Down Expand Up @@ -132,7 +127,7 @@ final class WorkspaceRowView: SidebarRowView {
mutedMark.setAccessibilityElement(false)
machineMark.imageScaling = .scaleProportionallyDown
machineMark.isHidden = true
[icon, title, subtitle, timeLabel, metaLabel, elapsedLabel, activity, agentMark, mutedMark, machineMark, audioButton, badge, rowBadge, presenceDots, closeButton, moreButton, pinButton, disclosureButton, tabCount, prBadge, placeholderBar, statusBlock,
[icon, title, subtitle, timeLabel, metaLabel, activity, agentMark, mutedMark, machineMark, audioButton, badge, rowBadge, presenceDots, closeButton, moreButton, pinButton, disclosureButton, tabCount, prBadge, placeholderBar, statusBlock,
treeLine, pullRequests, keycap].forEach(addSubview)
setUpSessionTree()
disclosureButton.isHidden = true
Expand Down Expand Up @@ -192,7 +187,6 @@ final class WorkspaceRowView: SidebarRowView {
onToggleTabs = nil
onToggleAudio = nil
title.stopMarquee()
stopElapsed()
title.layer?.removeAnimation(forKey: "workspace-title-motion")
lastTitle = nil
titleArrivalGeneration &+= 1
Expand Down Expand Up @@ -268,13 +262,10 @@ final class WorkspaceRowView: SidebarRowView {
subtitle.setFont(SidebarStyle.subtitleFont)
subtitle.setString(shown.now.map(Strings.rowNow) ?? row.detail ?? "")
hasSubtitle = row.detail != nil
// While the agent works, its running time takes the age's place (WorkspaceRowView+Elapsed).
// No status words on a row (Leo 2026-10-21): its badge's dot says it; the label reads the chip.
let trailingText = (shown.workingSince != nil ? shown.trailingStatus : shown.trailingText)
// No status words and no running time on a row (Leo 2026-10-21): its badge's dot says it; the label reads the chip.
let trailingText = shown.trailingText
(isExpanded, timeText) = (shown.expanded, trailingText)
timeLabel.setString(trailingText ?? "")
if workingSince != shown.workingSince { elapsedTicker.cancel() }
workingSince = shown.workingSince
metaLabel.setString(shown.meta ?? "")
activity.configure(badgesLead ? .idle : shown.activity, style: badgesLead ? nil : ws.activityStyle)
activityState = shown.activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import CmuxNextAgentActivity
import Foundation
import Testing

/// Leo 2026-10-21, on rows that said "Done" beside a blue dot again: "WHO BROUGHT BACK THE DONE AND
/// THE TIMERS" (#23390 brought the Done back). A workspace row never says Done: a finished, unread
/// chat keeps only its quiet unread dot. What asks for the user (needs you, failed) still reads.
/// Leo 2026-10-21, on rows that said "Done" beside a blue dot and "10m" beside the working mark
/// again: "WHO BROUGHT BACK THE DONE AND THE TIMERS" (#23390 brought the Done back, #22826 the
/// timer). A workspace row never says Done and never shows how long its agent has worked: a
/// finished, unread chat keeps only its quiet unread dot, a working one its spinner. What asks for
/// the user (needs you, failed) still reads.
@MainActor @Suite(.serialized)
struct SidebarRowNoDoneTests {
private func workspace(_ chip: SidebarPriorityEntry.State?) -> SidebarWorkspace {
Expand Down Expand Up @@ -65,6 +67,17 @@ struct SidebarRowNoDoneTests {
}
}

@Test func aWorkingRowShowsNoRunningTime() {
var ws = SidebarWorkspace(id: WorkspaceID("ws"), title: "Fix the build", lastActivity: Date(), agentWorking: true,
activity: .working(progress: nil))
ws.detail.activitySince = Date().addingTimeInterval(-10 * 60)
let (view, window) = row(ws)
// "10m", "1h 3m", "2d": a number and a unit, the running time's shape.
let timers = shownText(view).filter { $0.range(of: #"^\d+\s?(s|m|h|d|min|hr)\b"#, options: .regularExpression) != nil }
#expect(timers.isEmpty, "the row draws \(shownText(view))")
window.close()
}

/// No status words at all (Leo 2026-10-21, on a blue dot beside "Needs you": "why ISN'T THIS
/// JUST A YELLOW DOT!?"): the badge's dot says it; only the accessibility label words it.
@Test func aRowShowsNoStatusWords() {
Expand Down

This file was deleted.