@@ -98,7 +98,8 @@ struct LogTableView: View {
98 sortAscending: $sortAscending,
98 sortAscending: $sortAscending,
99 columnWidths: $columnWidths,
99 columnWidths: $columnWidths,
100 records: records,
100 records: records,
101− onMoveColumn: onMoveColumn
101+ onMoveColumn: onMoveColumn,
102+ onColumnFilter: onCellFilter
@@ -200,6 +201,28 @@ struct LogHeaderView: View {
200 @Binding var columnWidths: [String: CGFloat]
201 @Binding var columnWidths: [String: CGFloat]
201 let records: [LogRecord]
202 let records: [LogRecord]
202 var onMoveColumn: ((String, String) -> Void)?
203 var onMoveColumn: ((String, String) -> Void)?
204+ var onColumnFilter: ((_ column: String, _ value: JSONValue?, _ exclude: Bool) -> Void)?
206+ private static let maxFilterValues = 20
208+ private func uniqueValues(for column: String) -> [JSONValue] {
209+ var seen = Set<JSONValue>()
210+ var result: [JSONValue] = []
211+ for record in records {
212+ let value = record[column] ?? .null
213+ if seen.insert(value).inserted {
214+ result.append(value)
217+ return result.sorted()
220+ private func filterDisplayLabel(for value: JSONValue) -> String {
221+ let str = value.displayString
222+ if str.isEmpty { return "(empty)" }
223+ if str.count > 50 { return String(str.prefix(50)) + "..." }
204 var body: some View {
227 var body: some View {
205 HStack(spacing: 0) {
228 HStack(spacing: 0) {
@@ -262,6 +285,40 @@ struct LogHeaderView: View {
262 Button("Auto-fit All Columns") {
285 Button("Auto-fit All Columns") {
263 columnWidths = computeColumnWidths(columns: columns, records: records)
286 columnWidths = computeColumnWidths(columns: columns, records: records)
289+ if let onFilter = onColumnFilter {
290+ let values = uniqueValues(for: column)
291+ if !values.isEmpty {
294+ let displayValues = Array(values.prefix(Self.maxFilterValues))
295+ let remaining = values.count - displayValues.count
297+ Menu("Filter by Value") {
298+ ForEach(displayValues, id: \.self) { value in
299+ Button(filterDisplayLabel(for: value)) {
300+ onFilter(column, value == .null ? nil : value, false)
305+ Text("\(remaining) more values not shown")
309+ Menu("Exclude Value") {
310+ ForEach(displayValues, id: \.self) { value in
311+ Button(filterDisplayLabel(for: value)) {
312+ onFilter(column, value == .null ? nil : value, true)
317+ Text("\(remaining) more values not shown")