fix(macos): improve session preview loading

This commit is contained in:
Peter Steinberger
2026-01-02 19:55:19 +01:00
parent 49e89cf3f1
commit 8b47315845
4 changed files with 55 additions and 13 deletions

View File

@@ -450,10 +450,18 @@ extension GatewayConnection {
// MARK: - Chat
func chatHistory(sessionKey: String) async throws -> ClawdisChatHistoryPayload {
try await self.requestDecoded(
func chatHistory(
sessionKey: String,
limit: Int? = nil,
timeoutMs: Int? = nil) async throws -> ClawdisChatHistoryPayload
{
var params: [String: AnyCodable] = ["sessionKey": AnyCodable(sessionKey)]
if let limit { params["limit"] = AnyCodable(limit) }
let timeout = timeoutMs.map { Double($0) }
return try await self.requestDecoded(
method: .chatHistory,
params: ["sessionKey": AnyCodable(sessionKey)])
params: params,
timeoutMs: timeout)
}
func chatSend(

View File

@@ -15,8 +15,8 @@ struct SessionMenuLabelView: View {
let row: SessionRow
let width: CGFloat
@Environment(\.menuItemHighlighted) private var isHighlighted
private let paddingLeading: CGFloat = 26
private let paddingTrailing: CGFloat = 18
private let paddingLeading: CGFloat = 22
private let paddingTrailing: CGFloat = 14
private let barHeight: CGFloat = 6
private var primaryTextColor: Color {
@@ -35,7 +35,7 @@ struct SessionMenuLabelView: View {
width: max(1, self.width - (self.paddingLeading + self.paddingTrailing)),
height: self.barHeight)
HStack(alignment: .firstTextBaseline, spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: 2) {
Text(self.row.label)
.font(.caption.weight(self.row.key == "main" ? .semibold : .regular))
.foregroundStyle(self.primaryTextColor)
@@ -43,7 +43,7 @@ struct SessionMenuLabelView: View {
.truncationMode(.middle)
.layoutPriority(1)
Spacer(minLength: 8)
Spacer(minLength: 4)
Text("\(self.row.tokens.contextSummaryShort) · \(self.row.ageText)")
.font(.caption.monospacedDigit())

View File

@@ -1,5 +1,6 @@
import ClawdisChatUI
import ClawdisKit
import OSLog
import SwiftUI
private struct SessionPreviewItem: Identifiable, Sendable {
@@ -45,9 +46,16 @@ private actor SessionPreviewCache {
func store(items: [SessionPreviewItem], for sessionKey: String) {
self.entries[sessionKey] = CacheEntry(items: items, updatedAt: Date())
}
func lastItems(for sessionKey: String) -> [SessionPreviewItem]? {
self.entries[sessionKey]?.items
}
}
struct SessionMenuPreviewView: View {
private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "SessionPreview")
private static let previewTimeoutSeconds: Double = 4
let sessionKey: String
let width: CGFloat
let maxItems: Int
@@ -58,6 +66,10 @@ struct SessionMenuPreviewView: View {
@State private var items: [SessionPreviewItem] = []
@State private var status: LoadStatus = .loading
private struct PreviewTimeoutError: LocalizedError {
var errorDescription: String? { "preview timeout" }
}
private enum LoadStatus: Equatable {
case loading
case ready
@@ -73,9 +85,13 @@ struct SessionMenuPreviewView: View {
self.isHighlighted ? Color(nsColor: .selectedMenuItemTextColor).opacity(0.85) : .secondary
}
private var previewLimit: Int {
min(max(self.maxItems * 3, 20), 120)
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(self.title)
.font(.caption.weight(.semibold))
.foregroundStyle(self.secondaryColor)
@@ -104,8 +120,8 @@ struct SessionMenuPreviewView: View {
}
}
.padding(.vertical, 6)
.padding(.leading, 18)
.padding(.trailing, 12)
.padding(.leading, 16)
.padding(.trailing, 11)
.frame(width: max(1, self.width), alignment: .leading)
.task(id: self.sessionKey) {
await self.loadPreview()
@@ -114,7 +130,7 @@ struct SessionMenuPreviewView: View {
@ViewBuilder
private func previewRow(_ item: SessionPreviewItem) -> some View {
HStack(alignment: .top, spacing: 8) {
HStack(alignment: .top, spacing: 4) {
Text(item.role.label)
.font(.caption2.monospacedDigit())
.foregroundStyle(self.roleColor(item.role))
@@ -155,17 +171,34 @@ struct SessionMenuPreviewView: View {
}
do {
let payload = try await GatewayConnection.shared.chatHistory(sessionKey: self.sessionKey)
let timeoutMs = Int(Self.previewTimeoutSeconds * 1000)
let payload = try await AsyncTimeout.withTimeout(
seconds: Self.previewTimeoutSeconds,
onTimeout: { PreviewTimeoutError() }) {
try await GatewayConnection.shared.chatHistory(
sessionKey: self.sessionKey,
limit: self.previewLimit,
timeoutMs: timeoutMs)
}
let built = Self.previewItems(from: payload, maxItems: self.maxItems)
await SessionPreviewCache.shared.store(items: built, for: self.sessionKey)
await MainActor.run {
self.items = built
self.status = built.isEmpty ? .empty : .ready
}
} catch is CancellationError {
return
} catch {
let fallback = await SessionPreviewCache.shared.lastItems(for: self.sessionKey)
await MainActor.run {
self.status = .error("Preview unavailable")
if let fallback {
self.items = fallback
self.status = fallback.isEmpty ? .empty : .ready
} else {
self.status = .error("Preview unavailable")
}
}
Self.logger.warning("Session preview failed session=\(self.sessionKey, privacy: .public) error=\(String(describing: error), privacy: .public)")
}
}