From 0d38856b3ef0d1bd6a12c67921541bd6daab40f9 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Tue, 31 Mar 2026 03:03:28 -0400 Subject: [PATCH] Add session filter to Activity view Dropdown in the filter bar lets users scope activity to a single session or view all. Sessions are labeled with their first user message preview. Combines with the existing tool-kind filter. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ViewModels/ActivityViewModel.swift | 19 ++++++++-- .../Activity/Views/ActivityView.swift | 36 +++++++++++++------ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/scarf/scarf/Features/Activity/ViewModels/ActivityViewModel.swift b/scarf/scarf/Features/Activity/ViewModels/ActivityViewModel.swift index 369863b..dc1d4f2 100644 --- a/scarf/scarf/Features/Activity/ViewModels/ActivityViewModel.swift +++ b/scarf/scarf/Features/Activity/ViewModels/ActivityViewModel.swift @@ -6,9 +6,20 @@ final class ActivityViewModel { var toolMessages: [HermesMessage] = [] var filterKind: ToolKind? + var filterSessionId: String? var selectedEntry: ActivityEntry? + var sessionPreviews: [String: String] = [:] var isLoading = true + var availableSessions: [(id: String, label: String)] { + var seen = Set() + return toolMessages.compactMap { message in + guard seen.insert(message.sessionId).inserted else { return nil } + let label = sessionPreviews[message.sessionId] ?? message.sessionId + return (id: message.sessionId, label: label) + } + } + var filteredActivity: [ActivityEntry] { let entries = toolMessages.flatMap { message in message.toolCalls.map { call in @@ -24,10 +35,11 @@ final class ActivityViewModel { ) } } - if let filterKind { - return entries.filter { $0.kind == filterKind } + return entries.filter { entry in + let kindOk = filterKind == nil || entry.kind == filterKind + let sessionOk = filterSessionId == nil || entry.sessionId == filterSessionId + return kindOk && sessionOk } - return entries } func load() async { @@ -38,6 +50,7 @@ final class ActivityViewModel { return } toolMessages = await dataService.fetchRecentToolCalls(limit: 200) + sessionPreviews = await dataService.fetchSessionPreviews(limit: 200) isLoading = false } diff --git a/scarf/scarf/Features/Activity/Views/ActivityView.swift b/scarf/scarf/Features/Activity/Views/ActivityView.swift index 5d7c668..a5d4d00 100644 --- a/scarf/scarf/Features/Activity/Views/ActivityView.swift +++ b/scarf/scarf/Features/Activity/Views/ActivityView.swift @@ -21,20 +21,36 @@ struct ActivityView: View { } private var filterBar: some View { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - FilterChip(label: "All", isSelected: viewModel.filterKind == nil) { - viewModel.filterKind = nil - } - ForEach(ToolKind.allCases, id: \.rawValue) { kind in - FilterChip(label: kind.rawValue.capitalized, isSelected: viewModel.filterKind == kind) { - viewModel.filterKind = kind + HStack(spacing: 12) { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + FilterChip(label: "All", isSelected: viewModel.filterKind == nil) { + viewModel.filterKind = nil + } + ForEach(ToolKind.allCases, id: \.rawValue) { kind in + FilterChip(label: kind.rawValue.capitalized, isSelected: viewModel.filterKind == kind) { + viewModel.filterKind = kind + } } } } - .padding(.horizontal) - .padding(.vertical, 8) + Divider() + .frame(height: 16) + Picker(selection: $viewModel.filterSessionId) { + Text("All Sessions").tag(String?.none) + Divider() + ForEach(viewModel.availableSessions, id: \.id) { session in + Text(session.label) + .lineLimit(1) + .tag(String?.some(session.id)) + } + } label: { + EmptyView() + } + .frame(maxWidth: 250) } + .padding(.horizontal) + .padding(.vertical, 8) } private var activityList: some View {