feat(mac): show session labels under context bars

This commit is contained in:
Peter Steinberger
2025-12-13 01:10:12 +00:00
parent f98ab2d037
commit 387615e99f

View File

@@ -22,7 +22,7 @@ struct MenuContent: View {
private let activeSessionWindowSeconds: TimeInterval = 24 * 60 * 60 private let activeSessionWindowSeconds: TimeInterval = 24 * 60 * 60
private let contextCardPadding: CGFloat = 10 private let contextCardPadding: CGFloat = 10
private let contextPillHeight: CGFloat = 16 private let contextBarHeight: CGFloat = 6
private let contextFallbackWidth: CGFloat = 280 private let contextFallbackWidth: CGFloat = 280
var body: some View { var body: some View {
@@ -260,25 +260,19 @@ struct MenuContent: View {
@ViewBuilder @ViewBuilder
private var contextCardRow: some View { private var contextCardRow: some View {
Button(action: {}, label: { Button(action: {}, label: {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .firstTextBaseline) { Text("Context")
Text("Context") .font(.caption.weight(.semibold))
.font(.caption.weight(.semibold)) .foregroundStyle(.secondary)
.foregroundStyle(.secondary)
Spacer(minLength: 10) if self.contextSessions.isEmpty {
Text(self.contextSubtitle) Text("No active sessions")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} } else {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 6) {
if self.contextSessions.isEmpty {
Text("No sessions yet")
.font(.caption)
.foregroundStyle(.secondary)
} else {
ForEach(self.contextSessions) { row in ForEach(self.contextSessions) { row in
self.contextSessionPill(row) self.contextSessionRow(row)
} }
} }
} }
@@ -304,46 +298,33 @@ struct MenuContent: View {
.disabled(true) .disabled(true)
} }
private var contextSubtitle: String {
let count = self.contextActiveCount
if count == 0 { return "Main session" }
if count == 1 { return "1 active session" }
return "\(count) active sessions"
}
private var contextPillWidth: CGFloat { private var contextPillWidth: CGFloat {
let base = self.contextCardWidth > 0 ? self.contextCardWidth : self.contextFallbackWidth let base = self.contextCardWidth > 0 ? self.contextCardWidth : self.contextFallbackWidth
return max(1, base - (self.contextCardPadding * 2)) return max(1, base - (self.contextCardPadding * 2))
} }
@ViewBuilder @ViewBuilder
private func contextSessionPill(_ row: SessionRow) -> some View { private func contextSessionRow(_ row: SessionRow) -> some View {
let label = row.key
let summary = row.tokens.contextSummaryShort
let width = self.contextPillWidth let width = self.contextPillWidth
ZStack(alignment: .center) { VStack(alignment: .leading, spacing: 4) {
ContextUsageBar( ContextUsageBar(
usedTokens: row.tokens.total, usedTokens: row.tokens.total,
contextTokens: row.tokens.contextTokens, contextTokens: row.tokens.contextTokens,
width: width, width: width,
height: self.contextPillHeight) height: self.contextBarHeight)
HStack(spacing: 8) { HStack(spacing: 8) {
Text(label) Text(row.key)
.font(.caption.weight(row.key == "main" ? .semibold : .regular)) .font(.caption2.weight(row.key == "main" ? .semibold : .regular))
.lineLimit(1) .lineLimit(1)
.truncationMode(.middle) .truncationMode(.middle)
.layoutPriority(1) .layoutPriority(1)
Spacer(minLength: 8) Spacer(minLength: 8)
Text(summary) Text(row.tokens.contextSummaryShort)
.font(.caption.monospacedDigit()) .font(.caption2.monospacedDigit())
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.padding(.horizontal, 8)
.padding(.vertical, 1)
} }
.frame(width: width, height: self.contextPillHeight) .frame(width: width)
} }
private var heartbeatStatusRow: some View { private var heartbeatStatusRow: some View {
@@ -516,14 +497,8 @@ struct MenuContent: View {
} }
let activeCount = active.count let activeCount = active.count
let main = rows.first(where: { $0.key == "main" })
var merged = active
if let main, !merged.contains(where: { $0.key == "main" }) {
merged.insert(main, at: 0)
}
// Keep stable ordering: main first, then most recent. // Keep stable ordering: main first, then most recent.
let sorted = merged.sorted { lhs, rhs in let sorted = active.sorted { lhs, rhs in
if lhs.key == "main" { return true } if lhs.key == "main" { return true }
if rhs.key == "main" { return false } if rhs.key == "main" { return false }
return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast) return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast)