     1→import SwiftUI
     2→
     3→// MARK: - Live Analysis View
     4→
     5→/// Real-time dashboard showing analysis progress with detailed metrics
     6→struct LiveAnalysisView: View {
     7→    let progress: AnalysisProgress
     8→    var onCancel: (() -> Void)? = nil
     9→
    10→    @State private var animationPhase = false
    11→
    12→    var body: some View {
    13→        VStack(spacing: 16) {
    14→            // Header with current phase
    15→            phaseHeader
    16→
    17→            // Progress bar with percentage
    18→            progressSection
    19→
    20→            // Metrics grid
    21→            metricsGrid
    22→
    23→            // Phase timeline
    24→            PhaseTimelineView(progress: progress)
    25→
    26→            // Current batch info
    27→            if !progress.currentBatchNotes.isEmpty {
    28→                currentBatchSection
    29→            }
    30→
    31→            // Errors (if any)
    32→            if progress.hasErrors {
    33→                errorsSection
    34→            }
    35→
    36→            // Cancel button
    37→            if let cancel = onCancel, progress.isActive {
    38→                Button(action: cancel) {
    39→                    Text("Cancel Analysis")
    40→                        .font(.subheadline)
    41→                        .fontWeight(.medium)
    42→                        .foregroundColor(.red)
    43→                        .padding(.horizontal, 20)
    44→                        .padding(.vertical, 10)
    45→                        .background(Color.red.opacity(0.1))
    46→                        .cornerRadius(8)
    47→                }
    48→            }
    49→        }
    50→        .padding()
    51→        .background(Color(.systemBackground))
    52→        .cornerRadius(16)
    53→        .shadow(color: .black.opacity(0.1), radius: 8, y: 4)
    54→        .onAppear {
    55→            withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
    56→                animationPhase = true
    57→            }
    58→        }
    59→    }
    60→
    61→    // MARK: - Phase Header
    62→
    63→    private var phaseHeader: some View {
    64→        HStack(spacing: 12) {
    65→            // Animated phase icon
    66→            ZStack {
    67→                Circle()
    68→                    .fill(progress.currentPhase.color.opacity(0.2))
    69→                    .frame(width: 48, height: 48)
    70→
    71→                Image(systemName: progress.currentPhase.icon)
    72→                    .font(.title2)
    73→                    .foregroundColor(progress.currentPhase.color)
    74→                    .scaleEffect(animationPhase ? 1.1 : 0.9)
    75→            }
    76→
    77→            VStack(alignment: .leading, spacing: 4) {
    78→                Text(progress.currentPhase.displayName)
    79→                    .font(.headline)
    80→                    .foregroundColor(.primary)
    81→
    82→                if let message = progress.statusMessage {
    83→                    Text(message)
    84→                        .font(.caption)
    85→                        .foregroundColor(.secondary)
    86→                        .lineLimit(1)
    87→                }
    88→            }
    89→
    90→            Spacer()
    91→
    92→            // Elapsed time
    93→            VStack(alignment: .trailing, spacing: 2) {
    94→                Text(progress.elapsedTimeString)
    95→                    .font(.title3)
    96→                    .fontWeight(.semibold)
    97→                    .foregroundColor(.purple)
    98→
    99→                Text("elapsed")
   100→                    .font(.caption2)
   101→                    .foregroundColor(.secondary)
   102→            }
   103→        }
   104→    }
   105→
   106→    // MARK: - Progress Section
   107→
   108→    private var progressSection: some View {
   109→        VStack(spacing: 8) {
   110→            // Progress bar
   111→            GeometryReader { geo in
   112→                ZStack(alignment: .leading) {
   113→                    // Background
   114→                    RoundedRectangle(cornerRadius: 6)
   115→                        .fill(Color(.systemGray5))
   116→
   117→                    // Progress fill with gradient
   118→                    RoundedRectangle(cornerRadius: 6)
   119→                        .fill(
   120→                            LinearGradient(
   121→                                colors: [.purple, .blue],
   122→                                startPoint: .leading,
   123→                                endPoint: .trailing
   124→                            )
   125→                        )
   126→                        .frame(width: geo.size.width * progress.percentComplete)
   127→                        .animation(.easeInOut(duration: 0.3), value: progress.percentComplete)
   128→                }
   129→            }
   130→            .frame(height: 12)
   131→
   132→            // Progress details
   133→            HStack {
   134→                Text("\(progress.processedNotes) / \(progress.totalNotes) notes")
   135→                    .font(.caption)
   136→                    .foregroundColor(.secondary)
   137→
   138→                Spacer()
   139→
   140→                Text("\(Int(progress.percentComplete * 100))%")
   141→                    .font(.caption)
   142→                    .fontWeight(.semibold)
   143→                    .foregroundColor(.purple)
   144→
   145→                if let eta = progress.etaString {
   146→                    Text("ETA: \(eta)")
   147→                        .font(.caption)
   148→                        .foregroundColor(.secondary)
   149→                        .padding(.leading, 8)
   150→                }
   151→            }
   152→        }
   153→    }
   154→
   155→    // MARK: - Metrics Grid
   156→
   157→    private var metricsGrid: some View {
   158→        LazyVGrid(columns: [
   159→            GridItem(.flexible()),
   160→            GridItem(.flexible()),
   161→            GridItem(.flexible()),
   162→            GridItem(.flexible())
   163→        ], spacing: 12) {
   164→            MetricBadge(
   165→                value: String(format: "%.1f", progress.notesPerSecond),
   166→                label: "notes/sec",
   167→                icon: "speedometer",
   168→                color: .blue
   169→            )
   170→
   171→            MetricBadge(
   172→                value: "\(progress.totalConnectionsFound)",
   173→                label: "connections",
   174→                icon: "link",
   175→                color: .green
   176→            )
   177→
   178→            MetricBadge(
   179→                value: "\(progress.currentBatch)/\(progress.totalBatches)",
   180→                label: "batches",
   181→                icon: "square.stack.3d.up",
   182→                color: .orange
   183→            )
   184→
   185→            MetricBadge(
   186→                value: "\(progress.errors.count)",
   187→                label: "errors",
   188→                icon: progress.errors.isEmpty ? "checkmark.circle" : "exclamationmark.triangle",
   189→                color: progress.errors.isEmpty ? .gray : .red
   190→            )
   191→        }
   192→    }
   193→
   194→    // MARK: - Current Batch Section
   195→
   196→    private var currentBatchSection: some View {
   197→        VStack(alignment: .leading, spacing: 8) {
   198→            HStack {
   199→                Image(systemName: "doc.on.doc")
   200→                    .font(.caption)
   201→                    .foregroundColor(.secondary)
   202→
   203→                Text("Processing Batch \(progress.currentBatch)")
   204→                    .font(.caption)
   205→                    .fontWeight(.medium)
   206→                    .foregroundColor(.secondary)
   207→
   208→                Spacer()
   209→
   210→                Text("\(progress.connectionsFoundThisBatch) found")
   211→                    .font(.caption)
   212→                    .foregroundColor(.green)
   213→            }
   214→
   215→            // Show first few notes being processed
   216→            ScrollView(.horizontal, showsIndicators: false) {
   217→                HStack(spacing: 6) {
   218→                    ForEach(Array(progress.currentBatchNotes.prefix(5).enumerated()), id: \.offset) { _, note in
   219→                        Text(URL(fileURLWithPath: note).deletingPathExtension().lastPathComponent)
   220→                            .font(.caption2)
   221→                            .foregroundColor(.white)
   222→                            .padding(.horizontal, 8)
   223→                            .padding(.vertical, 4)
   224→                            .background(Color.purple.opacity(0.7))
   225→                            .cornerRadius(4)
   226→                            .lineLimit(1)
   227→                    }
   228→
   229→                    if progress.currentBatchNotes.count > 5 {
   230→                        Text("+\(progress.currentBatchNotes.count - 5) more")
   231→                            .font(.caption2)
   232→                            .foregroundColor(.secondary)
   233→                    }
   234→                }
   235→            }
   236→        }
   237→        .padding()
   238→        .background(Color(.systemGray6))
   239→        .cornerRadius(8)
   240→    }
   241→
   242→    // MARK: - Errors Section
   243→
   244→    private var errorsSection: some View {
   245→        VStack(alignment: .leading, spacing: 8) {
   246→            HStack {
   247→                Image(systemName: "exclamationmark.triangle.fill")
   248→                    .foregroundColor(.orange)
   249→
   250→                Text("Errors (\(progress.errors.count))")
   251→                    .font(.subheadline)
   252→                    .fontWeight(.medium)
   253→            }
   254→
   255→            ScrollView(.horizontal, showsIndicators: false) {
   256→                HStack(spacing: 8) {
   257→                    ForEach(progress.errors.prefix(3)) { error in
   258→                        VStack(alignment: .leading, spacing: 2) {
   259→                            Text(URL(fileURLWithPath: error.notePath).lastPathComponent)
   260→                                .font(.caption)
   261→                                .fontWeight(.medium)
   262→                                .lineLimit(1)
   263→
   264→                            Text(error.error)
   265→                                .font(.caption2)
   266→                                .foregroundColor(.secondary)
   267→                                .lineLimit(2)
   268→                        }
   269→                        .padding(8)
   270→                        .background(Color.orange.opacity(0.1))
   271→                        .cornerRadius(6)
   272→                    }
   273→                }
   274→            }
   275→        }
   276→        .padding()
   277→        .background(Color(.systemGray6))
   278→        .cornerRadius(8)
   279→    }
   280→}
   281→
   282→// MARK: - Metric Badge
   283→
   284→struct MetricBadge: View {
   285→    let value: String
   286→    let label: String
   287→    let icon: String
   288→    let color: Color
   289→
   290→    var body: some View {
   291→        VStack(spacing: 4) {
   292→            Image(systemName: icon)
   293→                .font(.caption)
   294→                .foregroundColor(color)
   295→
   296→            Text(value)
   297→                .font(.subheadline)
   298→                .fontWeight(.bold)
   299→                .foregroundColor(.primary)
   300→
   301→            Text(label)
   302→                .font(.caption2)
   303→                .foregroundColor(.secondary)
   304→                .lineLimit(1)
   305→        }
   306→        .frame(maxWidth: .infinity)
   307→        .padding(.vertical, 10)
   308→        .background(color.opacity(0.1))
   309→        .cornerRadius(8)
   310→    }
   311→}
   312→
   313→// MARK: - Preview
   314→
   315→#Preview("Live Analysis") {
   316→    VStack {
   317→        LiveAnalysisView(
   318→            progress: AnalysisProgress(
   319→                totalNotes: 150,
   320→                processedNotes: 67,
   321→                currentPhase: .analyzingContent,
   322→                startedAt: Date().addingTimeInterval(-45),
   323→                currentBatch: 4,
   324→                totalBatches: 8,
   325→                phaseStartTimes: [
   326→                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-45),
   327→                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-35),
   328→                    AnalysisPhase.analyzingContent.rawValue: Date().addingTimeInterval(-20)
   329→                ],
   330→                phaseEndTimes: [
   331→                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-35),
   332→                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-20)
   333→                ],
   334→                completedPhases: [.loadingFiles, .parsingWikiLinks],
   335→                notesPerSecond: 2.3,
   336→                batchesPerMinute: 4.5,
   337→                currentBatchNotes: ["Notes/Projects/App Design.md", "Notes/Daily/2024-01-15.md", "Notes/Ideas/Feature X.md"],
   338→                connectionsFoundThisBatch: 12,
   339→                totalConnectionsFound: 89,
   340→                statusMessage: "Processing batch 4..."
   341→            ),
   342→            onCancel: { print("Cancelled") }
   343→        )
   344→        .padding()
   345→
   346→        Spacer()
   347→    }
   348→    .background(Color(.systemGroupedBackground))
   349→}
   350→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI

// MARK: - Live Analysis View

/// Real-time dashboard showing analysis progress with detailed metrics
struct LiveAnalysisView: View {
    let progress: AnalysisProgress
    var onCancel: (() -> Void)? = nil

    @State private var animationPhase = false

    var body: some View {
        VStack(spacing: 16) {
            // Header with current phase
            phaseHeader

            // Progress bar with percentage
            progressSection

            // Metrics grid
            metricsGrid

            // Phase timeline
            PhaseTimelineView(progress: progress)

            // Current batch info
            if !progress.currentBatchNotes.isEmpty {
                currentBatchSection
            }

            // Errors (if any)
            if progress.hasErrors {
                errorsSection
            }

            // Cancel button
            if let cancel = onCancel, progress.isActive {
                Button(action: cancel) {
                    Text("Cancel Analysis")
                        .font(.subheadline)
                        .fontWeight(.medium)
                        .foregroundColor(.red)
                        .padding(.horizontal, 20)
                        .padding(.vertical, 10)
                        .background(Color.red.opacity(0.1))
                        .cornerRadius(8)
                }
            }
        }
        .padding()
        .background(Color(.systemBackground))
        .cornerRadius(16)
        .shadow(color: .black.opacity(0.1), radius: 8, y: 4)
        .onAppear {
            withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
                animationPhase = true
            }
        }
    }

    // MARK: - Phase Header

    private var phaseHeader: some View {
        HStack(spacing: 12) {
            // Animated phase icon
            ZStack {
                Circle()
                    .fill(progress.currentPhase.color.opacity(0.2))
                    .frame(width: 48, height: 48)

                Image(systemName: progress.currentPhase.icon)
                    .font(.title2)
                    .foregroundColor(progress.currentPhase.color)
                    .scaleEffect(animationPhase ? 1.1 : 0.9)
            }

            VStack(alignment: .leading, spacing: 4) {
                Text(progress.currentPhase.displayName)
                    .font(.headline)
                    .foregroundColor(.primary)

                if let message = progress.statusMessage {
                    Text(message)
                        .font(.caption)
                        .foregroundColor(.secondary)
                        .lineLimit(1)
                }
            }

            Spacer()

            // Elapsed time
            VStack(alignment: .trailing, spacing: 2) {
                Text(progress.elapsedTimeString)
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.purple)

                Text("elapsed")
                    .font(.caption2)
                    .foregroundColor(.secondary)
            }
        }
    }

    // MARK: - Progress Section

    private var progressSection: some View {
        VStack(spacing: 8) {
            // Progress bar
            GeometryReader { geo in
                ZStack(alignment: .leading) {
                    // Background
                    RoundedRectangle(cornerRadius: 6)
                        .fill(Color(.systemGray5))

                    // Progress fill with gradient
                    RoundedRectangle(cornerRadius: 6)
                        .fill(
                            LinearGradient(
                                colors: [.purple, .blue],
                                startPoint: .leading,
                                endPoint: .trailing
                            )
                        )
                        .frame(width: geo.size.width * progress.percentComplete)
                        .animation(.easeInOut(duration: 0.3), value: progress.percentComplete)
                }
            }
            .frame(height: 12)

            // Progress details
            HStack {
                Text("\(progress.processedNotes) / \(progress.totalNotes) notes")
                    .font(.caption)
                    .foregroundColor(.secondary)

                Spacer()

                Text("\(Int(progress.percentComplete * 100))%")
                    .font(.caption)
                    .fontWeight(.semibold)
                    .foregroundColor(.purple)

                if let eta = progress.etaString {
                    Text("ETA: \(eta)")
                        .font(.caption)
                        .foregroundColor(.secondary)
                        .padding(.leading, 8)
                }
            }
        }
    }

    // MARK: - Metrics Grid

    private var metricsGrid: some View {
        LazyVGrid(columns: [
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible())
        ], spacing: 12) {
            MetricBadge(
                value: String(format: "%.1f", progress.notesPerSecond),
                label: "notes/sec",
                icon: "speedometer",
                color: .blue
            )

            MetricBadge(
                value: "\(progress.totalConnectionsFound)",
                label: "connections",
                icon: "link",
                color: .green
            )

            MetricBadge(
                value: "\(progress.currentBatch)/\(progress.totalBatches)",
                label: "batches",
                icon: "square.stack.3d.up",
                color: .orange
            )

            MetricBadge(
                value: "\(progress.errors.count)",
                label: "errors",
                icon: progress.errors.isEmpty ? "checkmark.circle" : "exclamationmark.triangle",
                color: progress.errors.isEmpty ? .gray : .red
            )
        }
    }

    // MARK: - Current Batch Section

    private var currentBatchSection: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Image(systemName: "doc.on.doc")
                    .font(.caption)
                    .foregroundColor(.secondary)

                Text("Processing Batch \(progress.currentBatch)")
                    .font(.caption)
                    .fontWeight(.medium)
                    .foregroundColor(.secondary)

                Spacer()

                Text("\(progress.connectionsFoundThisBatch) found")
                    .font(.caption)
                    .foregroundColor(.green)
            }

            // Show first few notes being processed
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 6) {
                    ForEach(Array(progress.currentBatchNotes.prefix(5).enumerated()), id: \.offset) { _, note in
                        Text(URL(fileURLWithPath: note).deletingPathExtension().lastPathComponent)
                            .font(.caption2)
                            .foregroundColor(.white)
                            .padding(.horizontal, 8)
                            .padding(.vertical, 4)
                            .background(Color.purple.opacity(0.7))
                            .cornerRadius(4)
                            .lineLimit(1)
                    }

                    if progress.currentBatchNotes.count > 5 {
                        Text("+\(progress.currentBatchNotes.count - 5) more")
                            .font(.caption2)
                            .foregroundColor(.secondary)
                    }
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
    }

    // MARK: - Errors Section

    private var errorsSection: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Image(systemName: "exclamationmark.triangle.fill")
                    .foregroundColor(.orange)

                Text("Errors (\(progress.errors.count))")
                    .font(.subheadline)
                    .fontWeight(.medium)
            }

            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 8) {
                    ForEach(progress.errors.prefix(3)) { error in
                        VStack(alignment: .leading, spacing: 2) {
                            Text(URL(fileURLWithPath: error.notePath).lastPathComponent)
                                .font(.caption)
                                .fontWeight(.medium)
                                .lineLimit(1)

                            Text(error.error)
                                .font(.caption2)
                                .foregroundColor(.secondary)
                                .lineLimit(2)
                        }
                        .padding(8)
                        .background(Color.orange.opacity(0.1))
                        .cornerRadius(6)
                    }
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
    }
}

// MARK: - Metric Badge

struct MetricBadge: View {
    let value: String
    let label: String
    let icon: String
    let color: Color

    var body: some View {
        VStack(spacing: 4) {
            Image(systemName: icon)
                .font(.caption)
                .foregroundColor(color)

            Text(value)
                .font(.subheadline)
                .fontWeight(.bold)
                .foregroundColor(.primary)

            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
                .lineLimit(1)
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 10)
        .background(color.opacity(0.1))
        .cornerRadius(8)
    }
}

// MARK: - Preview

#Preview("Live Analysis") {
    VStack {
        LiveAnalysisView(
            progress: AnalysisProgress(
                totalNotes: 150,
                processedNotes: 67,
                currentPhase: .analyzingContent,
                startedAt: Date().addingTimeInterval(-45),
                currentBatch: 4,
                totalBatches: 8,
                phaseStartTimes: [
                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-45),
                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-35),
                    AnalysisPhase.analyzingContent.rawValue: Date().addingTimeInterval(-20)
                ],
                phaseEndTimes: [
                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-35),
                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-20)
                ],
                completedPhases: [.loadingFiles, .parsingWikiLinks],
                notesPerSecond: 2.3,
                batchesPerMinute: 4.5,
                currentBatchNotes: ["Notes/Projects/App Design.md", "Notes/Daily/2024-01-15.md", "Notes/Ideas/Feature X.md"],
                connectionsFoundThisBatch: 12,
                totalConnectionsFound: 89,
                statusMessage: "Processing batch 4..."
            ),
            onCancel: { print("Cancelled") }
        )
        .padding()

        Spacer()
    }
    .background(Color(.systemGroupedBackground))
}


Perfect! Now let me check the PhaseTimelineView to see how phases are visualized:


     1→import SwiftUI
     2→
     3→// MARK: - Phase Timeline View
     4→
     5→/// Visual timeline showing completed, current, and pending phases
     6→struct PhaseTimelineView: View {
     7→    let progress: AnalysisProgress
     8→
     9→    var body: some View {
    10→        VStack(alignment: .leading, spacing: 12) {
    11→            // Header
    12→            HStack {
    13→                Text("Analysis Phases")
    14→                    .font(.subheadline)
    15→                    .fontWeight(.medium)
    16→                    .foregroundColor(.secondary)
    17→
    18→                Spacer()
    19→
    20→                Text("\(progress.completedPhases.count)/\(progress.totalPhases)")
    21→                    .font(.caption)
    22→                    .foregroundColor(.secondary)
    23→            }
    24→
    25→            // Timeline
    26→            VStack(spacing: 0) {
    27→                ForEach(Array(AnalysisProgress.allPhases.enumerated()), id: \.offset) { index, phase in
    28→                    PhaseRow(
    29→                        phase: phase,
    30→                        status: phaseStatus(for: phase),
    31→                        duration: progress.phaseDuration(for: phase),
    32→                        isLast: index == AnalysisProgress.allPhases.count - 1
    33→                    )
    34→                }
    35→            }
    36→        }
    37→        .padding()
    38→        .background(Color(.systemGray6))
    39→        .cornerRadius(12)
    40→    }
    41→
    42→    private func phaseStatus(for phase: AnalysisPhase) -> PhaseStatus {
    43→        if progress.completedPhases.contains(phase) {
    44→            return .completed
    45→        } else if progress.currentPhase == phase {
    46→            return .current
    47→        } else if phase == .complete || phase == .failed {
    48→            return .pending
    49→        } else {
    50→            // Check if phase comes before current
    51→            let allPhases = AnalysisProgress.allPhases
    52→            if let currentIndex = allPhases.firstIndex(of: progress.currentPhase),
    53→               let phaseIndex = allPhases.firstIndex(of: phase) {
    54→                return phaseIndex < currentIndex ? .completed : .pending
    55→            }
    56→            return .pending
    57→        }
    58→    }
    59→}
    60→
    61→// MARK: - Phase Status
    62→
    63→enum PhaseStatus {
    64→    case pending
    65→    case current
    66→    case completed
    67→
    68→    var icon: String {
    69→        switch self {
    70→        case .pending: return "circle"
    71→        case .current: return "circle.dotted"
    72→        case .completed: return "checkmark.circle.fill"
    73→        }
    74→    }
    75→
    76→    var color: Color {
    77→        switch self {
    78→        case .pending: return .gray
    79→        case .current: return .purple
    80→        case .completed: return .green
    81→        }
    82→    }
    83→}
    84→
    85→// MARK: - Phase Row
    86→
    87→struct PhaseRow: View {
    88→    let phase: AnalysisPhase
    89→    let status: PhaseStatus
    90→    let duration: TimeInterval?
    91→    let isLast: Bool
    92→
    93→    @State private var isAnimating = false
    94→
    95→    var body: some View {
    96→        HStack(spacing: 12) {
    97→            // Timeline indicator
    98→            VStack(spacing: 0) {
    99→                // Icon
   100→                ZStack {
   101→                    Circle()
   102→                        .fill(status.color.opacity(0.2))
   103→                        .frame(width: 28, height: 28)
   104→
   105→                    if status == .current {
   106→                        Circle()
   107→                            .stroke(status.color, lineWidth: 2)
   108→                            .frame(width: 28, height: 28)
   109→                            .scaleEffect(isAnimating ? 1.2 : 1.0)
   110→                            .opacity(isAnimating ? 0.5 : 1.0)
   111→                    }
   112→
   113→                    Image(systemName: status.icon)
   114→                        .font(.system(size: 12, weight: .medium))
   115→                        .foregroundColor(status.color)
   116→                }
   117→
   118→                // Connector line
   119→                if !isLast {
   120→                    Rectangle()
   121→                        .fill(status == .completed ? Color.green.opacity(0.5) : Color(.systemGray4))
   122→                        .frame(width: 2, height: 20)
   123→                }
   124→            }
   125→
   126→            // Phase info
   127→            VStack(alignment: .leading, spacing: 2) {
   128→                HStack {
   129→                    Image(systemName: phase.icon)
   130→                        .font(.caption)
   131→                        .foregroundColor(status.color)
   132→
   133→                    Text(phase.displayName)
   134→                        .font(.subheadline)
   135→                        .fontWeight(status == .current ? .semibold : .regular)
   136→                        .foregroundColor(status == .pending ? .secondary : .primary)
   137→
   138→                    Spacer()
   139→
   140→                    if let duration = duration {
   141→                        Text(formatDuration(duration))
   142→                            .font(.caption)
   143→                            .foregroundColor(.secondary)
   144→                    } else if status == .current {
   145→                        Text("In progress...")
   146→                            .font(.caption)
   147→                            .foregroundColor(.purple)
   148→                            .italic()
   149→                    }
   150→                }
   151→            }
   152→            .padding(.bottom, isLast ? 0 : 8)
   153→        }
   154→        .onAppear {
   155→            if status == .current {
   156→                withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
   157→                    isAnimating = true
   158→                }
   159→            }
   160→        }
   161→    }
   162→
   163→    private func formatDuration(_ duration: TimeInterval) -> String {
   164→        if duration < 1 {
   165→            return "<1s"
   166→        } else if duration < 60 {
   167→            return String(format: "%.1fs", duration)
   168→        } else {
   169→            let minutes = Int(duration / 60)
   170→            let seconds = Int(duration) % 60
   171→            return "\(minutes)m \(seconds)s"
   172→        }
   173→    }
   174→}
   175→
   176→// MARK: - Compact Phase Timeline
   177→
   178→/// Compact horizontal timeline for smaller spaces
   179→struct CompactPhaseTimelineView: View {
   180→    let progress: AnalysisProgress
   181→
   182→    var body: some View {
   183→        HStack(spacing: 4) {
   184→            ForEach(Array(AnalysisProgress.allPhases.enumerated()), id: \.offset) { index, phase in
   185→                CompactPhaseIndicator(
   186→                    phase: phase,
   187→                    isCompleted: progress.completedPhases.contains(phase),
   188→                    isCurrent: progress.currentPhase == phase
   189→                )
   190→
   191→                if index < AnalysisProgress.allPhases.count - 1 {
   192→                    Rectangle()
   193→                        .fill(
   194→                            progress.completedPhases.contains(phase)
   195→                                ? Color.green.opacity(0.5)
   196→                                : Color(.systemGray4)
   197→                        )
   198→                        .frame(height: 2)
   199→                        .frame(maxWidth: 20)
   200→                }
   201→            }
   202→        }
   203→    }
   204→}
   205→
   206→// MARK: - Compact Phase Indicator
   207→
   208→struct CompactPhaseIndicator: View {
   209→    let phase: AnalysisPhase
   210→    let isCompleted: Bool
   211→    let isCurrent: Bool
   212→
   213→    @State private var isAnimating = false
   214→
   215→    var body: some View {
   216→        ZStack {
   217→            Circle()
   218→                .fill(backgroundColor)
   219→                .frame(width: 24, height: 24)
   220→
   221→            if isCurrent {
   222→                Circle()
   223→                    .stroke(Color.purple, lineWidth: 2)
   224→                    .frame(width: 24, height: 24)
   225→                    .scaleEffect(isAnimating ? 1.3 : 1.0)
   226→                    .opacity(isAnimating ? 0 : 1)
   227→            }
   228→
   229→            Image(systemName: iconName)
   230→                .font(.system(size: 10))
   231→                .foregroundColor(iconColor)
   232→        }
   233→        .onAppear {
   234→            if isCurrent {
   235→                withAnimation(.easeOut(duration: 1.0).repeatForever(autoreverses: false)) {
   236→                    isAnimating = true
   237→                }
   238→            }
   239→        }
   240→    }
   241→
   242→    private var backgroundColor: Color {
   243→        if isCompleted {
   244→            return .green.opacity(0.2)
   245→        } else if isCurrent {
   246→            return .purple.opacity(0.2)
   247→        } else {
   248→            return Color(.systemGray5)
   249→        }
   250→    }
   251→
   252→    private var iconName: String {
   253→        if isCompleted {
   254→            return "checkmark"
   255→        } else if isCurrent {
   256→            return phase.icon
   257→        } else {
   258→            return "circle"
   259→        }
   260→    }
   261→
   262→    private var iconColor: Color {
   263→        if isCompleted {
   264→            return .green
   265→        } else if isCurrent {
   266→            return .purple
   267→        } else {
   268→            return .gray
   269→        }
   270→    }
   271→}
   272→
   273→// MARK: - Preview
   274→
   275→#Preview("Phase Timeline") {
   276→    VStack(spacing: 20) {
   277→        // Full timeline
   278→        PhaseTimelineView(
   279→            progress: AnalysisProgress(
   280→                currentPhase: .analyzingContent,
   281→                phaseStartTimes: [
   282→                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-60),
   283→                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-45),
   284→                    AnalysisPhase.analyzingContent.rawValue: Date().addingTimeInterval(-30)
   285→                ],
   286→                phaseEndTimes: [
   287→                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-45),
   288→                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-30)
   289→                ],
   290→                completedPhases: [.loadingFiles, .parsingWikiLinks]
   291→            )
   292→        )
   293→
   294→        Divider()
   295→
   296→        // Compact timeline
   297→        Text("Compact Timeline")
   298→            .font(.caption)
   299→            .foregroundColor(.secondary)
   300→
   301→        CompactPhaseTimelineView(
   302→            progress: AnalysisProgress(
   303→                currentPhase: .buildingGraph,
   304→                completedPhases: [.loadingFiles, .parsingWikiLinks, .analyzingContent]
   305→            )
   306→        )
   307→        .padding()
   308→    }
   309→    .padding()
   310→}
   311→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI

// MARK: - Phase Timeline View

/// Visual timeline showing completed, current, and pending phases
struct PhaseTimelineView: View {
    let progress: AnalysisProgress

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            // Header
            HStack {
                Text("Analysis Phases")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundColor(.secondary)

                Spacer()

                Text("\(progress.completedPhases.count)/\(progress.totalPhases)")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            // Timeline
            VStack(spacing: 0) {
                ForEach(Array(AnalysisProgress.allPhases.enumerated()), id: \.offset) { index, phase in
                    PhaseRow(
                        phase: phase,
                        status: phaseStatus(for: phase),
                        duration: progress.phaseDuration(for: phase),
                        isLast: index == AnalysisProgress.allPhases.count - 1
                    )
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(12)
    }

    private func phaseStatus(for phase: AnalysisPhase) -> PhaseStatus {
        if progress.completedPhases.contains(phase) {
            return .completed
        } else if progress.currentPhase == phase {
            return .current
        } else if phase == .complete || phase == .failed {
            return .pending
        } else {
            // Check if phase comes before current
            let allPhases = AnalysisProgress.allPhases
            if let currentIndex = allPhases.firstIndex(of: progress.currentPhase),
               let phaseIndex = allPhases.firstIndex(of: phase) {
                return phaseIndex < currentIndex ? .completed : .pending
            }
            return .pending
        }
    }
}

// MARK: - Phase Status

enum PhaseStatus {
    case pending
    case current
    case completed

    var icon: String {
        switch self {
        case .pending: return "circle"
        case .current: return "circle.dotted"
        case .completed: return "checkmark.circle.fill"
        }
    }

    var color: Color {
        switch self {
        case .pending: return .gray
        case .current: return .purple
        case .completed: return .green
        }
    }
}

// MARK: - Phase Row

struct PhaseRow: View {
    let phase: AnalysisPhase
    let status: PhaseStatus
    let duration: TimeInterval?
    let isLast: Bool

    @State private var isAnimating = false

    var body: some View {
        HStack(spacing: 12) {
            // Timeline indicator
            VStack(spacing: 0) {
                // Icon
                ZStack {
                    Circle()
                        .fill(status.color.opacity(0.2))
                        .frame(width: 28, height: 28)

                    if status == .current {
                        Circle()
                            .stroke(status.color, lineWidth: 2)
                            .frame(width: 28, height: 28)
                            .scaleEffect(isAnimating ? 1.2 : 1.0)
                            .opacity(isAnimating ? 0.5 : 1.0)
                    }

                    Image(systemName: status.icon)
                        .font(.system(size: 12, weight: .medium))
                        .foregroundColor(status.color)
                }

                // Connector line
                if !isLast {
                    Rectangle()
                        .fill(status == .completed ? Color.green.opacity(0.5) : Color(.systemGray4))
                        .frame(width: 2, height: 20)
                }
            }

            // Phase info
            VStack(alignment: .leading, spacing: 2) {
                HStack {
                    Image(systemName: phase.icon)
                        .font(.caption)
                        .foregroundColor(status.color)

                    Text(phase.displayName)
                        .font(.subheadline)
                        .fontWeight(status == .current ? .semibold : .regular)
                        .foregroundColor(status == .pending ? .secondary : .primary)

                    Spacer()

                    if let duration = duration {
                        Text(formatDuration(duration))
                            .font(.caption)
                            .foregroundColor(.secondary)
                    } else if status == .current {
                        Text("In progress...")
                            .font(.caption)
                            .foregroundColor(.purple)
                            .italic()
                    }
                }
            }
            .padding(.bottom, isLast ? 0 : 8)
        }
        .onAppear {
            if status == .current {
                withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
                    isAnimating = true
                }
            }
        }
    }

    private func formatDuration(_ duration: TimeInterval) -> String {
        if duration < 1 {
            return "<1s"
        } else if duration < 60 {
            return String(format: "%.1fs", duration)
        } else {
            let minutes = Int(duration / 60)
            let seconds = Int(duration) % 60
            return "\(minutes)m \(seconds)s"
        }
    }
}

// MARK: - Compact Phase Timeline

/// Compact horizontal timeline for smaller spaces
struct CompactPhaseTimelineView: View {
    let progress: AnalysisProgress

    var body: some View {
        HStack(spacing: 4) {
            ForEach(Array(AnalysisProgress.allPhases.enumerated()), id: \.offset) { index, phase in
                CompactPhaseIndicator(
                    phase: phase,
                    isCompleted: progress.completedPhases.contains(phase),
                    isCurrent: progress.currentPhase == phase
                )

                if index < AnalysisProgress.allPhases.count - 1 {
                    Rectangle()
                        .fill(
                            progress.completedPhases.contains(phase)
                                ? Color.green.opacity(0.5)
                                : Color(.systemGray4)
                        )
                        .frame(height: 2)
                        .frame(maxWidth: 20)
                }
            }
        }
    }
}

// MARK: - Compact Phase Indicator

struct CompactPhaseIndicator: View {
    let phase: AnalysisPhase
    let isCompleted: Bool
    let isCurrent: Bool

    @State private var isAnimating = false

    var body: some View {
        ZStack {
            Circle()
                .fill(backgroundColor)
                .frame(width: 24, height: 24)

            if isCurrent {
                Circle()
                    .stroke(Color.purple, lineWidth: 2)
                    .frame(width: 24, height: 24)
                    .scaleEffect(isAnimating ? 1.3 : 1.0)
                    .opacity(isAnimating ? 0 : 1)
            }

            Image(systemName: iconName)
                .font(.system(size: 10))
                .foregroundColor(iconColor)
        }
        .onAppear {
            if isCurrent {
                withAnimation(.easeOut(duration: 1.0).repeatForever(autoreverses: false)) {
                    isAnimating = true
                }
            }
        }
    }

    private var backgroundColor: Color {
        if isCompleted {
            return .green.opacity(0.2)
        } else if isCurrent {
            return .purple.opacity(0.2)
        } else {
            return Color(.systemGray5)
        }
    }

    private var iconName: String {
        if isCompleted {
            return "checkmark"
        } else if isCurrent {
            return phase.icon
        } else {
            return "circle"
        }
    }

    private var iconColor: Color {
        if isCompleted {
            return .green
        } else if isCurrent {
            return .purple
        } else {
            return .gray
        }
    }
}

// MARK: - Preview

#Preview("Phase Timeline") {
    VStack(spacing: 20) {
        // Full timeline
        PhaseTimelineView(
            progress: AnalysisProgress(
                currentPhase: .analyzingContent,
                phaseStartTimes: [
                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-60),
                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-45),
                    AnalysisPhase.analyzingContent.rawValue: Date().addingTimeInterval(-30)
                ],
                phaseEndTimes: [
                    AnalysisPhase.loadingFiles.rawValue: Date().addingTimeInterval(-45),
                    AnalysisPhase.parsingWikiLinks.rawValue: Date().addingTimeInterval(-30)
                ],
                completedPhases: [.loadingFiles, .parsingWikiLinks]
            )
        )

        Divider()

        // Compact timeline
        Text("Compact Timeline")
            .font(.caption)
            .foregroundColor(.secondary)

        CompactPhaseTimelineView(
            progress: AnalysisProgress(
                currentPhase: .buildingGraph,
                completedPhases: [.loadingFiles, .parsingWikiLinks, .analyzingContent]
            )
        )
        .padding()
    }
    .padding()
}


Excellent! I now have a complete understanding of the current progress tracking mechanism. Let me create a comprehensive summary document.

## COMPREHENSIVE ANALYSIS: SmartConnectionService Progress Tracking

Based on my exploration of the codebase, here is the complete breakdown of the current progress tracking mechanism:

### 1. **AnalysisProgress Struct Definition**

**File:** `/Users/neog/Apps/iOS/ObsidianViewer-iOS/ObsidianViewer-iOS/Models/GraphModels.swift` (Lines 145-378)

**Core Fields:**

**Progress Tracking:**
- `totalNotes: Int` - Total notes to process
- `processedNotes: Int` - Notes processed so far
- `percentComplete: Double` - Computed property (processedNotes / totalNotes)

**Phase Management:**
- `currentPhase: AnalysisPhase` - Current phase (enum with 8 states: idle, loadingFiles, parsingWikiLinks, analyzingContent, buildingGraph, identifyingClusters, generatingSuggestions, complete, failed)
- `completedPhases: [AnalysisPhase]` - List of phases already completed
- `phaseStartTimes: [String: Date]` - Maps phase.rawValue to start Date
- `phaseEndTimes: [String: Date]` - Maps phase.rawValue to end Date

**Batch Processing:**
- `currentBatch: Int` - Current batch number
- `totalBatches: Int` - Total number of batches
- `currentBatchNotes: [String]` - Paths of notes in current batch
- `connectionsFoundThisBatch: Int` - Connections found in this batch

**Performance Metrics:**
- `notesPerSecond: Double` - Throughput (notes/sec)
- `batchesPerMinute: Double` - Batch processing speed
- `lastThroughputUpdate: Date?` - When metrics were last updated

**Connection Tracking:**
- `totalConnectionsFound: Int` - Cumulative connections found

**Status & Timing:**
- `statusMessage: String?` - Current status text
- `currentNotePath: String?` - Note path being processed
- `startedAt: Date` - Analysis start time
- `estimatedCompletion: Date?` - Computed ETA
- `elapsedTime: TimeInterval` - Computed elapsed duration
- `errors: [AnalysisError]` - Array of errors encountered

---

### 2. **Key Progress Mutation Methods**

**startPhase(_ phase: AnalysisPhase)**
```swift
- Ends previous phase (if active) by recording endTime
- Marks phase as completed
- Sets new currentPhase
- Records phaseStartTime for new phase
- Updates statusMessage to phase.displayName
```

**updateThroughput(notesProcessed: Int, timeInterval: TimeInterval)**
```swift
- Calculates notesPerSecond = notesProcessed / timeInterval
- Updates lastThroughputUpdate timestamp
```

**updateBatchThroughput(batchesCompleted: Int, timeInterval: TimeInterval)**
```swift
- Calculates batchesPerMinute = batchesCompleted / (timeInterval / 60)
```

**recordConnections(_ count: Int)**
```swift
- Increments connectionsFoundThisBatch by count
- Increments totalConnectionsFound by count
```

**markComplete()**
```swift
- Records endTime for current phase
- Sets currentPhase to .complete
- Clears currentBatchNotes
- Sets statusMessage to "Analysis complete"
```

**markFailed(error: String)**
```swift
- Sets currentPhase to .failed
- Sets statusMessage to error description
```

---

### 3. **Current Status Messages Being Used**

**File:** `/Users/neog/Apps/iOS/ObsidianViewer-iOS/ObsidianViewer-iOS/Services/SmartConnectionService.swift`

**Explicit Messages Set:**
- Line 96: `"Loading files from vault..."`
- Line 99: `"Found {count} notes"`
- Line 105: `"Parsing wiki-links and metadata..."`
- Line 126: `"Loading batch {index + 1}/{totalBatches}..."`
- Line 138: `currentNotePath` (path string)
- Line 218: `"Building knowledge graph..."`
- Line 225: `"Identifying topic clusters..."`
- Line 232: `"Generating connection suggestions..."`
- Line 260: `"Found {count} new suggestions"`
- Line 288: `"Analyzing {count} notes in {batchCount} batches (parallel mode)..."`
- Line 381: `"Completed batch {completedBatches}/{totalBatches}..."`
- Line 397: `"Content analysis complete. Found {count} semantic connections."`

**Phase Display Names (from AnalysisPhase enum):**
- `"Ready"` → idle
- `"Loading files..."` → loadingFiles
- `"Parsing wiki-links..."` → parsingWikiLinks
- `"Analyzing content..."` → analyzingContent
- `"Building graph..."` → buildingGraph
- `"Identifying clusters..."` → identifyingClusters
- `"Generating suggestions..."` → generatingSuggestions
- `"Complete"` → complete
- `"Failed"` → failed

---

### 4. **Current Phase Structure**

**6 Main Analysis Phases (in order):**

1. **Phase 1: Loading Files** (`.loadingFiles`)
   - Lists all markdown files recursively
   - Batches files (20 at a time) to reduce memory
   - Updates: totalNotes, statusMessage with batch progress

2. **Phase 2: Parsing Wiki-Links** (`.parsingWikiLinks`)
   - Loads batch contents (max 20 files per batch)
   - Parses: wiki-links, tags, metadata, content hash
   - Creates NoteNode objects with metadata only
   - Creates wiki-link connections
   - Updates: processedNotes every 10 files, throughput metrics

3. **Phase 3: AI Semantic Analysis** (`.analyzingContent`)
   - Processes notes in configurable batches (default: 10)
   - Uses parallel TaskGroup (maxConcurrentBatches: 2)
   - Sends prompts to LM Gateway or multi-agent
   - Records AI-detected semantic connections
   - Updates: currentBatch, totalBatches, currentBatchNotes, connectionsFoundThisBatch
   - Includes rate limiting (0.5s delay between parallel groups)

4. **Phase 4: Building Graph** (`.buildingGraph`)
   - Combines wiki-links + AI connections
   - Calculates incoming connection counts
   - Updates orphan status

5. **Phase 5: Identifying Clusters** (`.identifyingClusters`)
   - Groups notes by shared tags
   - Creates clusters for tags with 3+ notes
   - Assigns colors to clusters

6. **Phase 6: Generating Suggestions** (`.generatingSuggestions`)
   - Filters AI connections by strength threshold
   - Creates ConnectionSuggestion objects
   - Updates feedback metrics

---

### 5. **Batch Processing Logic**

**File Batch Processing (Phase 2):**
```
fileBatches = markdownFiles.chunked(into: 20)  // 20 files per batch
For each batch:
  - Update status every 3 batches (not every batch)
  - Load batch contents in parallel via getBatchFileContents()
  - Process each file individually (extracting metadata)
  - Release content when done with file
  - Update UI every 10 files (not every file)
  - Update throughput every batch
```

**AI Batch Processing (Phase 3):**
```
batches = notes.chunked(into: batchSize)  // 10 notes per batch (configurable)
batchGroups = batches.chunked(into: 2)    // Process 2 batches in parallel

For each batchGroup:
  For each batch in parallel (TaskGroup):
    - Build prompt with batch notes + all note paths + existing connections
    - Call lmService.analyzeContent() or multiAgent.analyzeBatch()
    - Parse AI response for JSON array of connections
    - Record connection count

  Update progress:
    - completedBatches
    - processedNotes += batchNoteCount
    - statusMessage with batch progress
    - Recalculate throughput

  Sleep 0.5s between groups (rate limiting)
```

---

### 6. **Information Available During Processing**

**Real-time Tracking:**
- Current phase with icon, color, display name
- Notes processed / total notes
- Percentage complete
- Elapsed time (formatted as "45s", "2m 15s", etc.)
- ETA (formatted as "30s", "5m 20s", "1h 15m")
- Throughput: notes/second
- Batch throughput: batches/minute
- Connections found (cumulative)
- Connections found this batch
- Current batch number / total batches
- Current note being processed
- Notes in current batch (first 5 displayed)
- Errors encountered with details
- Completed phases vs total phases

**Phase Timing:**
- Duration of each completed phase
- Start and end times for all phases
- Current phase duration (ongoing)

---

### 7. **Error Tracking**

**AnalysisError Structure:**
```swift
struct AnalysisError: Codable, Identifiable {
    let id: UUID
    let notePath: String
    let error: String
    let timestamp: Date
    let phase: AnalysisPhase  // Which phase error occurred in
}
```

**Errors are:**
- Collected in `analysisProgress.errors` array
- Displayed in LiveAnalysisView (up to 3 most recent)
- Includes note path and error message
- Tracked which phase the error occurred in

---

### 8. **How Progress Updates Flow to UI**

**In SmartConnectionService:**
1. Create AnalysisProgress with initial values
2. Update @Published var `analysisProgress` throughout execution
3. Call `startPhase()` when transitioning between phases
4. Call `updateThroughput()` periodically
5. Update `statusMessage`, `currentNotePath`, batch fields directly
6. Call `recordConnections()` when adding AI results
7. Call `markComplete()` or `markFailed()` at end

**In Views (LiveAnalysisView):**
- Observes `progress: AnalysisProgress` parameter
- Automatically recomputes on changes:
  - Percent bar updates
  - Throughput badges
  - Phase timeline
  - Batch details
  - Error list
  - ETA/elapsed time

**In PhaseTimelineView:**
- Shows all 6 phases in vertical timeline
- Pending (gray) → Current (purple, animated) → Completed (green)
- Displays duration for completed phases
- Shows "In progress..." for current phase

---

### 9. **UI Update Optimization**

Current strategy to reduce jank:
- **File loading:** Update status every 3 batches (not per batch)
- **File processing:** Update UI every 10 files (not per file)
- **Batch processing:** Update after each batch completes
- **Progress bar:** Uses animation for smooth transitions
- **Status message:** Updated less frequently to reduce re-renders

---

This provides a complete picture of how progress is currently tracked, what information is available, and how batches are processed. The system is well-designed for providing detailed real-time feedback while managing performance through batching and selective UI updates.