     1→import Foundation
     2→import SwiftUI
     3→
     4→// MARK: - Vault Graph
     5→
     6→/// Full vault graph representation
     7→struct VaultGraph: Codable {
     8→    var nodes: [String: NoteNode]           // path -> node
     9→    var edges: [NoteConnection]
    10→    var clusters: [NoteCluster]
    11→    var lastFullAnalysis: Date?
    12→    var totalNotes: Int
    13→    var orphanCount: Int
    14→    var connectionCount: Int
    15→
    16→    // MARK: - Computed Properties
    17→
    18→    var averageConnectionsPerNote: Double {
    19→        guard totalNotes > 0 else { return 0 }
    20→        let totalConnections = nodes.values.reduce(0) { $0 + $1.totalConnections }
    21→        return Double(totalConnections) / Double(totalNotes)
    22→    }
    23→
    24→    var mostConnectedNotes: [NoteNode] {
    25→        Array(nodes.values.sorted { $0.totalConnections > $1.totalConnections }.prefix(10))
    26→    }
    27→
    28→    var leastConnectedNotes: [NoteNode] {
    29→        Array(nodes.values.filter { !$0.isOrphan }.sorted { $0.totalConnections < $1.totalConnections }.prefix(10))
    30→    }
    31→
    32→    var orphanNodes: [NoteNode] {
    33→        nodes.values.filter { $0.isOrphan }.sorted { $0.title < $1.title }
    34→    }
    35→
    36→    var connectionsByType: [ConnectionType: Int] {
    37→        Dictionary(grouping: edges, by: { $0.connectionType })
    38→            .mapValues { $0.count }
    39→    }
    40→
    41→    // MARK: - Init
    42→
    43→    init(
    44→        nodes: [String: NoteNode] = [:],
    45→        edges: [NoteConnection] = [],
    46→        clusters: [NoteCluster] = [],
    47→        lastFullAnalysis: Date? = nil,
    48→        totalNotes: Int = 0,
    49→        orphanCount: Int = 0,
    50→        connectionCount: Int = 0
    51→    ) {
    52→        self.nodes = nodes
    53→        self.edges = edges
    54→        self.clusters = clusters
    55→        self.lastFullAnalysis = lastFullAnalysis
    56→        self.totalNotes = totalNotes
    57→        self.orphanCount = orphanCount
    58→        self.connectionCount = connectionCount
    59→    }
    60→
    61→    // MARK: - Methods
    62→
    63→    /// Get all connections for a specific note
    64→    func connections(for notePath: String) -> [NoteConnection] {
    65→        edges.filter { $0.sourceNotePath == notePath || $0.targetNotePath == notePath }
    66→    }
    67→
    68→    /// Get outgoing connections from a note
    69→    func outgoingConnections(from notePath: String) -> [NoteConnection] {
    70→        edges.filter { $0.sourceNotePath == notePath }
    71→    }
    72→
    73→    /// Get incoming connections to a note
    74→    func incomingConnections(to notePath: String) -> [NoteConnection] {
    75→        edges.filter { $0.targetNotePath == notePath }
    76→    }
    77→
    78→    /// Get cluster for a note
    79→    func cluster(for notePath: String) -> NoteCluster? {
    80→        clusters.first { $0.noteIds.contains(notePath) }
    81→    }
    82→
    83→    /// Check if two notes are connected
    84→    func areConnected(_ path1: String, _ path2: String) -> Bool {
    85→        edges.contains {
    86→            ($0.sourceNotePath == path1 && $0.targetNotePath == path2) ||
    87→            ($0.sourceNotePath == path2 && $0.targetNotePath == path1)
    88→        }
    89→    }
    90→}
    91→
    92→// MARK: - Analysis Progress
    93→
    94→/// Phases of the analysis pipeline
    95→enum AnalysisPhase: String, Codable {
    96→    case idle
    97→    case loadingFiles
    98→    case parsingWikiLinks
    99→    case analyzingContent
   100→    case buildingGraph
   101→    case identifyingClusters
   102→    case generatingSuggestions
   103→    case complete
   104→    case failed
   105→
   106→    var displayName: String {
   107→        switch self {
   108→        case .idle: return "Ready"
   109→        case .loadingFiles: return "Loading files..."
   110→        case .parsingWikiLinks: return "Parsing wiki-links..."
   111→        case .analyzingContent: return "Analyzing content..."
   112→        case .buildingGraph: return "Building graph..."
   113→        case .identifyingClusters: return "Identifying clusters..."
   114→        case .generatingSuggestions: return "Generating suggestions..."
   115→        case .complete: return "Complete"
   116→        case .failed: return "Failed"
   117→        }
   118→    }
   119→
   120→    var icon: String {
   121→        switch self {
   122→        case .idle: return "circle"
   123→        case .loadingFiles: return "doc.on.doc"
   124→        case .parsingWikiLinks: return "link"
   125→        case .analyzingContent: return "brain"
   126→        case .buildingGraph: return "point.3.connected.trianglepath.dotted"
   127→        case .identifyingClusters: return "circle.hexagongrid"
   128→        case .generatingSuggestions: return "lightbulb"
   129→        case .complete: return "checkmark.circle.fill"
   130→        case .failed: return "xmark.circle.fill"
   131→        }
   132→    }
   133→
   134→    var color: Color {
   135→        switch self {
   136→        case .idle: return .gray
   137→        case .loadingFiles, .parsingWikiLinks, .analyzingContent, .buildingGraph, .identifyingClusters, .generatingSuggestions:
   138→            return .blue
   139→        case .complete: return .green
   140→        case .failed: return .red
   141→        }
   142→    }
   143→}
   144→
   145→/// Analysis progress tracking with real-time metrics
   146→struct AnalysisProgress: Codable {
   147→    // MARK: - Core Progress
   148→    var totalNotes: Int
   149→    var processedNotes: Int
   150→    var currentPhase: AnalysisPhase
   151→    var startedAt: Date
   152→    var estimatedCompletion: Date?
   153→    var errors: [AnalysisError]
   154→    var currentBatch: Int
   155→    var totalBatches: Int
   156→
   157→    // MARK: - Phase Timing
   158→    var phaseStartTimes: [String: Date]  // AnalysisPhase.rawValue -> Date
   159→    var phaseEndTimes: [String: Date]    // AnalysisPhase.rawValue -> Date
   160→    var completedPhases: [AnalysisPhase]
   161→
   162→    // MARK: - Throughput Metrics
   163→    var notesPerSecond: Double
   164→    var batchesPerMinute: Double
   165→    var lastThroughputUpdate: Date?
   166→
   167→    // MARK: - Current Batch Details
   168→    var currentBatchNotes: [String]      // Note paths being processed
   169→    var connectionsFoundThisBatch: Int
   170→    var totalConnectionsFound: Int
   171→
   172→    // MARK: - Status Messages
   173→    var statusMessage: String?           // Current status text
   174→    var currentNotePath: String?         // Note currently being processed
   175→
   176→    // MARK: - Computed Properties
   177→
   178→    var percentComplete: Double {
   179→        guard totalNotes > 0 else { return 0 }
   180→        return Double(processedNotes) / Double(totalNotes)
   181→    }
   182→
   183→    var elapsedTime: TimeInterval {
   184→        Date().timeIntervalSince(startedAt)
   185→    }
   186→
   187→    var isComplete: Bool {
   188→        currentPhase == .complete
   189→    }
   190→
   191→    var isFailed: Bool {
   192→        currentPhase == .failed
   193→    }
   194→
   195→    var hasErrors: Bool {
   196→        !errors.isEmpty
   197→    }
   198→
   199→    var isActive: Bool {
   200→        currentPhase != .idle && currentPhase != .complete && currentPhase != .failed
   201→    }
   202→
   203→    /// Duration of current phase
   204→    var currentPhaseDuration: TimeInterval {
   205→        guard let start = phaseStartTimes[currentPhase.rawValue] else { return 0 }
   206→        return Date().timeIntervalSince(start)
   207→    }
   208→
   209→    /// Duration of a completed phase
   210→    func phaseDuration(for phase: AnalysisPhase) -> TimeInterval? {
   211→        guard let start = phaseStartTimes[phase.rawValue],
   212→              let end = phaseEndTimes[phase.rawValue] else { return nil }
   213→        return end.timeIntervalSince(start)
   214→    }
   215→
   216→    /// Estimated seconds remaining based on throughput
   217→    var estimatedSecondsRemaining: Int? {
   218→        guard notesPerSecond > 0, processedNotes < totalNotes else { return nil }
   219→        let remainingNotes = totalNotes - processedNotes
   220→        return Int(Double(remainingNotes) / notesPerSecond)
   221→    }
   222→
   223→    /// Formatted ETA string
   224→    var etaString: String? {
   225→        guard let seconds = estimatedSecondsRemaining else { return nil }
   226→        if seconds < 60 {
   227→            return "\(seconds)s"
   228→        } else if seconds < 3600 {
   229→            return "\(seconds / 60)m \(seconds % 60)s"
   230→        } else {
   231→            return "\(seconds / 3600)h \(seconds % 3600 / 60)m"
   232→        }
   233→    }
   234→
   235→    /// Formatted elapsed time string
   236→    var elapsedTimeString: String {
   237→        let seconds = Int(elapsedTime)
   238→        if seconds < 60 {
   239→            return "\(seconds)s"
   240→        } else if seconds < 3600 {
   241→            return "\(seconds / 60)m \(seconds % 60)s"
   242→        } else {
   243→            return "\(seconds / 3600)h \(seconds % 3600 / 60)m"
   244→        }
   245→    }
   246→
   247→    /// All phases in order
   248→    static let allPhases: [AnalysisPhase] = [
   249→        .loadingFiles,
   250→        .parsingWikiLinks,
   251→        .analyzingContent,
   252→        .buildingGraph,
   253→        .identifyingClusters,
   254→        .generatingSuggestions
   255→    ]
   256→
   257→    /// Index of current phase (0-based)
   258→    var currentPhaseIndex: Int {
   259→        Self.allPhases.firstIndex(of: currentPhase) ?? 0
   260→    }
   261→
   262→    /// Total number of phases
   263→    var totalPhases: Int {
   264→        Self.allPhases.count
   265→    }
   266→
   267→    // MARK: - Init
   268→
   269→    init(
   270→        totalNotes: Int = 0,
   271→        processedNotes: Int = 0,
   272→        currentPhase: AnalysisPhase = .idle,
   273→        startedAt: Date = Date(),
   274→        estimatedCompletion: Date? = nil,
   275→        errors: [AnalysisError] = [],
   276→        currentBatch: Int = 0,
   277→        totalBatches: Int = 0,
   278→        phaseStartTimes: [String: Date] = [:],
   279→        phaseEndTimes: [String: Date] = [:],
   280→        completedPhases: [AnalysisPhase] = [],
   281→        notesPerSecond: Double = 0,
   282→        batchesPerMinute: Double = 0,
   283→        lastThroughputUpdate: Date? = nil,
   284→        currentBatchNotes: [String] = [],
   285→        connectionsFoundThisBatch: Int = 0,
   286→        totalConnectionsFound: Int = 0,
   287→        statusMessage: String? = nil,
   288→        currentNotePath: String? = nil
   289→    ) {
   290→        self.totalNotes = totalNotes
   291→        self.processedNotes = processedNotes
   292→        self.currentPhase = currentPhase
   293→        self.startedAt = startedAt
   294→        self.estimatedCompletion = estimatedCompletion
   295→        self.errors = errors
   296→        self.currentBatch = currentBatch
   297→        self.totalBatches = totalBatches
   298→        self.phaseStartTimes = phaseStartTimes
   299→        self.phaseEndTimes = phaseEndTimes
   300→        self.completedPhases = completedPhases
   301→        self.notesPerSecond = notesPerSecond
   302→        self.batchesPerMinute = batchesPerMinute
   303→        self.lastThroughputUpdate = lastThroughputUpdate
   304→        self.currentBatchNotes = currentBatchNotes
   305→        self.connectionsFoundThisBatch = connectionsFoundThisBatch
   306→        self.totalConnectionsFound = totalConnectionsFound
   307→        self.statusMessage = statusMessage
   308→        self.currentNotePath = currentNotePath
   309→    }
   310→
   311→    // MARK: - Mutation Methods
   312→
   313→    /// Start a new phase
   314→    mutating func startPhase(_ phase: AnalysisPhase) {
   315→        // End current phase if active
   316→        if currentPhase != .idle && phaseStartTimes[currentPhase.rawValue] != nil {
   317→            phaseEndTimes[currentPhase.rawValue] = Date()
   318→            if !completedPhases.contains(currentPhase) {
   319→                completedPhases.append(currentPhase)
   320→            }
   321→        }
   322→
   323→        currentPhase = phase
   324→        phaseStartTimes[phase.rawValue] = Date()
   325→        statusMessage = phase.displayName
   326→    }
   327→
   328→    /// Update throughput metrics
   329→    mutating func updateThroughput(notesProcessed: Int, timeInterval: TimeInterval) {
   330→        guard timeInterval > 0 else { return }
   331→        notesPerSecond = Double(notesProcessed) / timeInterval
   332→        lastThroughputUpdate = Date()
   333→    }
   334→
   335→    /// Update batch throughput
   336→    mutating func updateBatchThroughput(batchesCompleted: Int, timeInterval: TimeInterval) {
   337→        guard timeInterval > 0 else { return }
   338→        batchesPerMinute = Double(batchesCompleted) / (timeInterval / 60.0)
   339→    }
   340→
   341→    /// Set current batch info
   342→    mutating func setCurrentBatch(notes: [String], batchNumber: Int) {
   343→        currentBatchNotes = notes
   344→        currentBatch = batchNumber
   345→        connectionsFoundThisBatch = 0
   346→    }
   347→
   348→    /// Record a connection found
   349→    mutating func recordConnection() {
   350→        connectionsFoundThisBatch += 1
   351→        totalConnectionsFound += 1
   352→    }
   353→
   354→    /// Record multiple connections
   355→    mutating func recordConnections(_ count: Int) {
   356→        connectionsFoundThisBatch += count
   357→        totalConnectionsFound += count
   358→    }
   359→
   360→    /// Mark analysis as complete
   361→    mutating func markComplete() {
   362→        if currentPhase != .idle {
   363→            phaseEndTimes[currentPhase.rawValue] = Date()
   364→            if !completedPhases.contains(currentPhase) {
   365→                completedPhases.append(currentPhase)
   366→            }
   367→        }
   368→        currentPhase = .complete
   369→        statusMessage = "Analysis complete"
   370→        currentBatchNotes = []
   371→    }
   372→
   373→    /// Mark analysis as failed
   374→    mutating func markFailed(error: String) {
   375→        currentPhase = .failed
   376→        statusMessage = error
   377→    }
   378→}
   379→
   380→/// Analysis error tracking
   381→struct AnalysisError: Codable, Identifiable {
   382→    let id: UUID
   383→    let notePath: String
   384→    let error: String
   385→    let timestamp: Date
   386→    let phase: AnalysisPhase
   387→
   388→    init(
   389→        id: UUID = UUID(),
   390→        notePath: String,
   391→        error: String,
   392→        timestamp: Date = Date(),
   393→        phase: AnalysisPhase
   394→    ) {
   395→        self.id = id
   396→        self.notePath = notePath
   397→        self.error = error
   398→        self.timestamp = timestamp
   399→        self.phase = phase
   400→    }
   401→}
   402→
   403→// MARK: - Feedback Metrics
   404→
   405→/// Feedback loop metrics for tracking AI learning
   406→struct FeedbackMetrics: Codable {
   407→    var totalSuggestionsGenerated: Int
   408→    var suggestionsAccepted: Int
   409→    var suggestionsRejected: Int
   410→    var suggestionsDeferred: Int
   411→    var orphansResolved: Int
   412→    var connectionsAddedToVault: Int
   413→    var lastUpdated: Date
   414→
   415→    // Historical tracking
   416→    var weeklyStats: [WeeklyFeedbackStats]
   417→
   418→    // MARK: - Computed Properties
   419→
   420→    var modelAccuracy: Double {
   421→        let total = suggestionsAccepted + suggestionsRejected
   422→        guard total > 0 else { return 0 }
   423→        return Double(suggestionsAccepted) / Double(total)
   424→    }
   425→
   426→    var pendingSuggestions: Int {
   427→        totalSuggestionsGenerated - suggestionsAccepted - suggestionsRejected - suggestionsDeferred
   428→    }
   429→
   430→    var acceptanceRate: Double {
   431→        guard totalSuggestionsGenerated > 0 else { return 0 }
   432→        return Double(suggestionsAccepted) / Double(totalSuggestionsGenerated)
   433→    }
   434→
   435→    var rejectionRate: Double {
   436→        guard totalSuggestionsGenerated > 0 else { return 0 }
   437→        return Double(suggestionsRejected) / Double(totalSuggestionsGenerated)
   438→    }
   439→
   440→    // MARK: - Init
   441→
   442→    init(
   443→        totalSuggestionsGenerated: Int = 0,
   444→        suggestionsAccepted: Int = 0,
   445→        suggestionsRejected: Int = 0,
   446→        suggestionsDeferred: Int = 0,
   447→        orphansResolved: Int = 0,
   448→        connectionsAddedToVault: Int = 0,
   449→        lastUpdated: Date = Date(),
   450→        weeklyStats: [WeeklyFeedbackStats] = []
   451→    ) {
   452→        self.totalSuggestionsGenerated = totalSuggestionsGenerated
   453→        self.suggestionsAccepted = suggestionsAccepted
   454→        self.suggestionsRejected = suggestionsRejected
   455→        self.suggestionsDeferred = suggestionsDeferred
   456→        self.orphansResolved = orphansResolved
   457→        self.connectionsAddedToVault = connectionsAddedToVault
   458→        self.lastUpdated = lastUpdated
   459→        self.weeklyStats = weeklyStats
   460→    }
   461→
   462→    // MARK: - Methods
   463→
   464→    mutating func recordAcceptance() {
   465→        suggestionsAccepted += 1
   466→        lastUpdated = Date()
   467→        updateWeeklyStats(accepted: 1, rejected: 0)
   468→    }
   469→
   470→    mutating func recordRejection() {
   471→        suggestionsRejected += 1
   472→        lastUpdated = Date()
   473→        updateWeeklyStats(accepted: 0, rejected: 1)
   474→    }
   475→
   476→    mutating func recordDeferred() {
   477→        suggestionsDeferred += 1
   478→        lastUpdated = Date()
   479→    }
   480→
   481→    mutating func recordConnectionWritten() {
   482→        connectionsAddedToVault += 1
   483→        lastUpdated = Date()
   484→    }
   485→
   486→    mutating func recordOrphanResolved() {
   487→        orphansResolved += 1
   488→        lastUpdated = Date()
   489→    }
   490→
   491→    private mutating func updateWeeklyStats(accepted: Int, rejected: Int) {
   492→        let calendar = Calendar.current
   493→        let weekStart = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()))!
   494→
   495→        if let index = weeklyStats.firstIndex(where: { calendar.isDate($0.weekStartDate, inSameDayAs: weekStart) }) {
   496→            weeklyStats[index].suggestionsAccepted += accepted
   497→            weeklyStats[index].suggestionsRejected += rejected
   498→        } else {
   499→            weeklyStats.append(WeeklyFeedbackStats(
   500→                weekStartDate: weekStart,
   501→                suggestionsAccepted: accepted,
   502→                suggestionsRejected: rejected,
   503→                connectionsAdded: 0
   504→            ))
   505→        }
   506→
   507→        // Keep only last 12 weeks
   508→        if weeklyStats.count > 12 {
   509→            weeklyStats = Array(weeklyStats.suffix(12))
   510→        }
   511→    }
   512→}
   513→
   514→/// Weekly feedback statistics
   515→struct WeeklyFeedbackStats: Codable, Identifiable {
   516→    let id: UUID
   517→    let weekStartDate: Date
   518→    var suggestionsAccepted: Int
   519→    var suggestionsRejected: Int
   520→    var connectionsAdded: Int
   521→
   522→    var total: Int {
   523→        suggestionsAccepted + suggestionsRejected
   524→    }
   525→
   526→    var weekAccuracy: Double {
   527→        guard total > 0 else { return 0 }
   528→        return Double(suggestionsAccepted) / Double(total)
   529→    }
   530→
   531→    init(
   532→        id: UUID = UUID(),
   533→        weekStartDate: Date,
   534→        suggestionsAccepted: Int = 0,
   535→        suggestionsRejected: Int = 0,
   536→        connectionsAdded: Int = 0
   537→    ) {
   538→        self.id = id
   539→        self.weekStartDate = weekStartDate
   540→        self.suggestionsAccepted = suggestionsAccepted
   541→        self.suggestionsRejected = suggestionsRejected
   542→        self.connectionsAdded = connectionsAdded
   543→    }
   544→}
   545→
   546→// MARK: - Learning Weights
   547→
   548→/// Learned weights for the feedback loop
   549→struct LearningWeights: Codable {
   550→    var typeWeights: [String: Double]        // ConnectionType.rawValue -> weight
   551→    var keywordImportance: [String: Double]  // keyword -> importance
   552→
   553→    init(
   554→        typeWeights: [String: Double] = [:],
   555→        keywordImportance: [String: Double] = [:]
   556→    ) {
   557→        self.typeWeights = typeWeights
   558→        self.keywordImportance = keywordImportance
   559→    }
   560→
   561→    func weight(for type: ConnectionType) -> Double {
   562→        typeWeights[type.rawValue] ?? 1.0
   563→    }
   564→
   565→    func importance(for keyword: String) -> Double {
   566→        keywordImportance[keyword.lowercased()] ?? 1.0
   567→    }
   568→
   569→    mutating func adjustWeight(for type: ConnectionType, by delta: Double) {
   570→        let current = weight(for: type)
   571→        typeWeights[type.rawValue] = max(0.1, min(2.0, current + delta))
   572→    }
   573→
   574→    mutating func adjustImportance(for keyword: String, by delta: Double) {
   575→        let current = importance(for: keyword)
   576→        keywordImportance[keyword.lowercased()] = max(0.1, min(2.0, current + delta))
   577→    }
   578→}
   579→
   580→// MARK: - Graph Node Position (for visualization)
   581→
   582→/// Position for graph visualization
   583→struct NodePosition: Codable {
   584→    var x: Double
   585→    var y: Double
   586→    var velocityX: Double = 0
   587→    var velocityY: Double = 0
   588→
   589→    init(x: Double, y: Double) {
   590→        self.x = x
   591→        self.y = y
   592→    }
   593→
   594→    static func random(in size: CGSize) -> NodePosition {
   595→        NodePosition(
   596→            x: Double.random(in: 50...(Double(size.width) - 50)),
   597→            y: Double.random(in: 50...(Double(size.height) - 50))
   598→        )
   599→    }
   600→
   601→    mutating func apply(force: CGVector, damping: Double = 0.85) {
   602→        velocityX = (velocityX + force.dx) * damping
   603→        velocityY = (velocityY + force.dy) * damping
   604→        x += velocityX
   605→        y += velocityY
   606→    }
   607→
   608→    func distance(to other: NodePosition) -> Double {
   609→        sqrt(pow(x - other.x, 2) + pow(y - other.y, 2))
   610→    }
   611→}
   612→
   613→// MARK: - AI Response Models
   614→
   615→/// Response structure for AI connection analysis
   616→struct AIConnectionResponse: Codable {
   617→    let connections: [AIConnectionSuggestion]
   618→}
   619→
   620→struct AIConnectionSuggestion: Codable {
   621→    let source: String
   622→    let target: String
   623→    let type: String
   624→    let strength: Double
   625→    let reason: String
   626→    let keywords: [String]?
   627→
   628→    var connectionType: ConnectionType {
   629→        ConnectionType(rawValue: type) ?? .semantic
   630→    }
   631→}
   632→

<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 Foundation
import SwiftUI

// MARK: - Vault Graph

/// Full vault graph representation
struct VaultGraph: Codable {
    var nodes: [String: NoteNode]           // path -> node
    var edges: [NoteConnection]
    var clusters: [NoteCluster]
    var lastFullAnalysis: Date?
    var totalNotes: Int
    var orphanCount: Int
    var connectionCount: Int

    // MARK: - Computed Properties

    var averageConnectionsPerNote: Double {
        guard totalNotes > 0 else { return 0 }
        let totalConnections = nodes.values.reduce(0) { $0 + $1.totalConnections }
        return Double(totalConnections) / Double(totalNotes)
    }

    var mostConnectedNotes: [NoteNode] {
        Array(nodes.values.sorted { $0.totalConnections > $1.totalConnections }.prefix(10))
    }

    var leastConnectedNotes: [NoteNode] {
        Array(nodes.values.filter { !$0.isOrphan }.sorted { $0.totalConnections < $1.totalConnections }.prefix(10))
    }

    var orphanNodes: [NoteNode] {
        nodes.values.filter { $0.isOrphan }.sorted { $0.title < $1.title }
    }

    var connectionsByType: [ConnectionType: Int] {
        Dictionary(grouping: edges, by: { $0.connectionType })
            .mapValues { $0.count }
    }

    // MARK: - Init

    init(
        nodes: [String: NoteNode] = [:],
        edges: [NoteConnection] = [],
        clusters: [NoteCluster] = [],
        lastFullAnalysis: Date? = nil,
        totalNotes: Int = 0,
        orphanCount: Int = 0,
        connectionCount: Int = 0
    ) {
        self.nodes = nodes
        self.edges = edges
        self.clusters = clusters
        self.lastFullAnalysis = lastFullAnalysis
        self.totalNotes = totalNotes
        self.orphanCount = orphanCount
        self.connectionCount = connectionCount
    }

    // MARK: - Methods

    /// Get all connections for a specific note
    func connections(for notePath: String) -> [NoteConnection] {
        edges.filter { $0.sourceNotePath == notePath || $0.targetNotePath == notePath }
    }

    /// Get outgoing connections from a note
    func outgoingConnections(from notePath: String) -> [NoteConnection] {
        edges.filter { $0.sourceNotePath == notePath }
    }

    /// Get incoming connections to a note
    func incomingConnections(to notePath: String) -> [NoteConnection] {
        edges.filter { $0.targetNotePath == notePath }
    }

    /// Get cluster for a note
    func cluster(for notePath: String) -> NoteCluster? {
        clusters.first { $0.noteIds.contains(notePath) }
    }

    /// Check if two notes are connected
    func areConnected(_ path1: String, _ path2: String) -> Bool {
        edges.contains {
            ($0.sourceNotePath == path1 && $0.targetNotePath == path2) ||
            ($0.sourceNotePath == path2 && $0.targetNotePath == path1)
        }
    }
}

// MARK: - Analysis Progress

/// Phases of the analysis pipeline
enum AnalysisPhase: String, Codable {
    case idle
    case loadingFiles
    case parsingWikiLinks
    case analyzingContent
    case buildingGraph
    case identifyingClusters
    case generatingSuggestions
    case complete
    case failed

    var displayName: String {
        switch self {
        case .idle: return "Ready"
        case .loadingFiles: return "Loading files..."
        case .parsingWikiLinks: return "Parsing wiki-links..."
        case .analyzingContent: return "Analyzing content..."
        case .buildingGraph: return "Building graph..."
        case .identifyingClusters: return "Identifying clusters..."
        case .generatingSuggestions: return "Generating suggestions..."
        case .complete: return "Complete"
        case .failed: return "Failed"
        }
    }

    var icon: String {
        switch self {
        case .idle: return "circle"
        case .loadingFiles: return "doc.on.doc"
        case .parsingWikiLinks: return "link"
        case .analyzingContent: return "brain"
        case .buildingGraph: return "point.3.connected.trianglepath.dotted"
        case .identifyingClusters: return "circle.hexagongrid"
        case .generatingSuggestions: return "lightbulb"
        case .complete: return "checkmark.circle.fill"
        case .failed: return "xmark.circle.fill"
        }
    }

    var color: Color {
        switch self {
        case .idle: return .gray
        case .loadingFiles, .parsingWikiLinks, .analyzingContent, .buildingGraph, .identifyingClusters, .generatingSuggestions:
            return .blue
        case .complete: return .green
        case .failed: return .red
        }
    }
}

/// Analysis progress tracking with real-time metrics
struct AnalysisProgress: Codable {
    // MARK: - Core Progress
    var totalNotes: Int
    var processedNotes: Int
    var currentPhase: AnalysisPhase
    var startedAt: Date
    var estimatedCompletion: Date?
    var errors: [AnalysisError]
    var currentBatch: Int
    var totalBatches: Int

    // MARK: - Phase Timing
    var phaseStartTimes: [String: Date]  // AnalysisPhase.rawValue -> Date
    var phaseEndTimes: [String: Date]    // AnalysisPhase.rawValue -> Date
    var completedPhases: [AnalysisPhase]

    // MARK: - Throughput Metrics
    var notesPerSecond: Double
    var batchesPerMinute: Double
    var lastThroughputUpdate: Date?

    // MARK: - Current Batch Details
    var currentBatchNotes: [String]      // Note paths being processed
    var connectionsFoundThisBatch: Int
    var totalConnectionsFound: Int

    // MARK: - Status Messages
    var statusMessage: String?           // Current status text
    var currentNotePath: String?         // Note currently being processed

    // MARK: - Computed Properties

    var percentComplete: Double {
        guard totalNotes > 0 else { return 0 }
        return Double(processedNotes) / Double(totalNotes)
    }

    var elapsedTime: TimeInterval {
        Date().timeIntervalSince(startedAt)
    }

    var isComplete: Bool {
        currentPhase == .complete
    }

    var isFailed: Bool {
        currentPhase == .failed
    }

    var hasErrors: Bool {
        !errors.isEmpty
    }

    var isActive: Bool {
        currentPhase != .idle && currentPhase != .complete && currentPhase != .failed
    }

    /// Duration of current phase
    var currentPhaseDuration: TimeInterval {
        guard let start = phaseStartTimes[currentPhase.rawValue] else { return 0 }
        return Date().timeIntervalSince(start)
    }

    /// Duration of a completed phase
    func phaseDuration(for phase: AnalysisPhase) -> TimeInterval? {
        guard let start = phaseStartTimes[phase.rawValue],
              let end = phaseEndTimes[phase.rawValue] else { return nil }
        return end.timeIntervalSince(start)
    }

    /// Estimated seconds remaining based on throughput
    var estimatedSecondsRemaining: Int? {
        guard notesPerSecond > 0, processedNotes < totalNotes else { return nil }
        let remainingNotes = totalNotes - processedNotes
        return Int(Double(remainingNotes) / notesPerSecond)
    }

    /// Formatted ETA string
    var etaString: String? {
        guard let seconds = estimatedSecondsRemaining else { return nil }
        if seconds < 60 {
            return "\(seconds)s"
        } else if seconds < 3600 {
            return "\(seconds / 60)m \(seconds % 60)s"
        } else {
            return "\(seconds / 3600)h \(seconds % 3600 / 60)m"
        }
    }

    /// Formatted elapsed time string
    var elapsedTimeString: String {
        let seconds = Int(elapsedTime)
        if seconds < 60 {
            return "\(seconds)s"
        } else if seconds < 3600 {
            return "\(seconds / 60)m \(seconds % 60)s"
        } else {
            return "\(seconds / 3600)h \(seconds % 3600 / 60)m"
        }
    }

    /// All phases in order
    static let allPhases: [AnalysisPhase] = [
        .loadingFiles,
        .parsingWikiLinks,
        .analyzingContent,
        .buildingGraph,
        .identifyingClusters,
        .generatingSuggestions
    ]

    /// Index of current phase (0-based)
    var currentPhaseIndex: Int {
        Self.allPhases.firstIndex(of: currentPhase) ?? 0
    }

    /// Total number of phases
    var totalPhases: Int {
        Self.allPhases.count
    }

    // MARK: - Init

    init(
        totalNotes: Int = 0,
        processedNotes: Int = 0,
        currentPhase: AnalysisPhase = .idle,
        startedAt: Date = Date(),
        estimatedCompletion: Date? = nil,
        errors: [AnalysisError] = [],
        currentBatch: Int = 0,
        totalBatches: Int = 0,
        phaseStartTimes: [String: Date] = [:],
        phaseEndTimes: [String: Date] = [:],
        completedPhases: [AnalysisPhase] = [],
        notesPerSecond: Double = 0,
        batchesPerMinute: Double = 0,
        lastThroughputUpdate: Date? = nil,
        currentBatchNotes: [String] = [],
        connectionsFoundThisBatch: Int = 0,
        totalConnectionsFound: Int = 0,
        statusMessage: String? = nil,
        currentNotePath: String? = nil
    ) {
        self.totalNotes = totalNotes
        self.processedNotes = processedNotes
        self.currentPhase = currentPhase
        self.startedAt = startedAt
        self.estimatedCompletion = estimatedCompletion
        self.errors = errors
        self.currentBatch = currentBatch
        self.totalBatches = totalBatches
        self.phaseStartTimes = phaseStartTimes
        self.phaseEndTimes = phaseEndTimes
        self.completedPhases = completedPhases
        self.notesPerSecond = notesPerSecond
        self.batchesPerMinute = batchesPerMinute
        self.lastThroughputUpdate = lastThroughputUpdate
        self.currentBatchNotes = currentBatchNotes
        self.connectionsFoundThisBatch = connectionsFoundThisBatch
        self.totalConnectionsFound = totalConnectionsFound
        self.statusMessage = statusMessage
        self.currentNotePath = currentNotePath
    }

    // MARK: - Mutation Methods

    /// Start a new phase
    mutating func startPhase(_ phase: AnalysisPhase) {
        // End current phase if active
        if currentPhase != .idle && phaseStartTimes[currentPhase.rawValue] != nil {
            phaseEndTimes[currentPhase.rawValue] = Date()
            if !completedPhases.contains(currentPhase) {
                completedPhases.append(currentPhase)
            }
        }

        currentPhase = phase
        phaseStartTimes[phase.rawValue] = Date()
        statusMessage = phase.displayName
    }

    /// Update throughput metrics
    mutating func updateThroughput(notesProcessed: Int, timeInterval: TimeInterval) {
        guard timeInterval > 0 else { return }
        notesPerSecond = Double(notesProcessed) / timeInterval
        lastThroughputUpdate = Date()
    }

    /// Update batch throughput
    mutating func updateBatchThroughput(batchesCompleted: Int, timeInterval: TimeInterval) {
        guard timeInterval > 0 else { return }
        batchesPerMinute = Double(batchesCompleted) / (timeInterval / 60.0)
    }

    /// Set current batch info
    mutating func setCurrentBatch(notes: [String], batchNumber: Int) {
        currentBatchNotes = notes
        currentBatch = batchNumber
        connectionsFoundThisBatch = 0
    }

    /// Record a connection found
    mutating func recordConnection() {
        connectionsFoundThisBatch += 1
        totalConnectionsFound += 1
    }

    /// Record multiple connections
    mutating func recordConnections(_ count: Int) {
        connectionsFoundThisBatch += count
        totalConnectionsFound += count
    }

    /// Mark analysis as complete
    mutating func markComplete() {
        if currentPhase != .idle {
            phaseEndTimes[currentPhase.rawValue] = Date()
            if !completedPhases.contains(currentPhase) {
                completedPhases.append(currentPhase)
            }
        }
        currentPhase = .complete
        statusMessage = "Analysis complete"
        currentBatchNotes = []
    }

    /// Mark analysis as failed
    mutating func markFailed(error: String) {
        currentPhase = .failed
        statusMessage = error
    }
}

/// Analysis error tracking
struct AnalysisError: Codable, Identifiable {
    let id: UUID
    let notePath: String
    let error: String
    let timestamp: Date
    let phase: AnalysisPhase

    init(
        id: UUID = UUID(),
        notePath: String,
        error: String,
        timestamp: Date = Date(),
        phase: AnalysisPhase
    ) {
        self.id = id
        self.notePath = notePath
        self.error = error
        self.timestamp = timestamp
        self.phase = phase
    }
}

// MARK: - Feedback Metrics

/// Feedback loop metrics for tracking AI learning
struct FeedbackMetrics: Codable {
    var totalSuggestionsGenerated: Int
    var suggestionsAccepted: Int
    var suggestionsRejected: Int
    var suggestionsDeferred: Int
    var orphansResolved: Int
    var connectionsAddedToVault: Int
    var lastUpdated: Date

    // Historical tracking
    var weeklyStats: [WeeklyFeedbackStats]

    // MARK: - Computed Properties

    var modelAccuracy: Double {
        let total = suggestionsAccepted + suggestionsRejected
        guard total > 0 else { return 0 }
        return Double(suggestionsAccepted) / Double(total)
    }

    var pendingSuggestions: Int {
        totalSuggestionsGenerated - suggestionsAccepted - suggestionsRejected - suggestionsDeferred
    }

    var acceptanceRate: Double {
        guard totalSuggestionsGenerated > 0 else { return 0 }
        return Double(suggestionsAccepted) / Double(totalSuggestionsGenerated)
    }

    var rejectionRate: Double {
        guard totalSuggestionsGenerated > 0 else { return 0 }
        return Double(suggestionsRejected) / Double(totalSuggestionsGenerated)
    }

    // MARK: - Init

    init(
        totalSuggestionsGenerated: Int = 0,
        suggestionsAccepted: Int = 0,
        suggestionsRejected: Int = 0,
        suggestionsDeferred: Int = 0,
        orphansResolved: Int = 0,
        connectionsAddedToVault: Int = 0,
        lastUpdated: Date = Date(),
        weeklyStats: [WeeklyFeedbackStats] = []
    ) {
        self.totalSuggestionsGenerated = totalSuggestionsGenerated
        self.suggestionsAccepted = suggestionsAccepted
        self.suggestionsRejected = suggestionsRejected
        self.suggestionsDeferred = suggestionsDeferred
        self.orphansResolved = orphansResolved
        self.connectionsAddedToVault = connectionsAddedToVault
        self.lastUpdated = lastUpdated
        self.weeklyStats = weeklyStats
    }

    // MARK: - Methods

    mutating func recordAcceptance() {
        suggestionsAccepted += 1
        lastUpdated = Date()
        updateWeeklyStats(accepted: 1, rejected: 0)
    }

    mutating func recordRejection() {
        suggestionsRejected += 1
        lastUpdated = Date()
        updateWeeklyStats(accepted: 0, rejected: 1)
    }

    mutating func recordDeferred() {
        suggestionsDeferred += 1
        lastUpdated = Date()
    }

    mutating func recordConnectionWritten() {
        connectionsAddedToVault += 1
        lastUpdated = Date()
    }

    mutating func recordOrphanResolved() {
        orphansResolved += 1
        lastUpdated = Date()
    }

    private mutating func updateWeeklyStats(accepted: Int, rejected: Int) {
        let calendar = Calendar.current
        let weekStart = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()))!

        if let index = weeklyStats.firstIndex(where: { calendar.isDate($0.weekStartDate, inSameDayAs: weekStart) }) {
            weeklyStats[index].suggestionsAccepted += accepted
            weeklyStats[index].suggestionsRejected += rejected
        } else {
            weeklyStats.append(WeeklyFeedbackStats(
                weekStartDate: weekStart,
                suggestionsAccepted: accepted,
                suggestionsRejected: rejected,
                connectionsAdded: 0
            ))
        }

        // Keep only last 12 weeks
        if weeklyStats.count > 12 {
            weeklyStats = Array(weeklyStats.suffix(12))
        }
    }
}

/// Weekly feedback statistics
struct WeeklyFeedbackStats: Codable, Identifiable {
    let id: UUID
    let weekStartDate: Date
    var suggestionsAccepted: Int
    var suggestionsRejected: Int
    var connectionsAdded: Int

    var total: Int {
        suggestionsAccepted + suggestionsRejected
    }

    var weekAccuracy: Double {
        guard total > 0 else { return 0 }
        return Double(suggestionsAccepted) / Double(total)
    }

    init(
        id: UUID = UUID(),
        weekStartDate: Date,
        suggestionsAccepted: Int = 0,
        suggestionsRejected: Int = 0,
        connectionsAdded: Int = 0
    ) {
        self.id = id
        self.weekStartDate = weekStartDate
        self.suggestionsAccepted = suggestionsAccepted
        self.suggestionsRejected = suggestionsRejected
        self.connectionsAdded = connectionsAdded
    }
}

// MARK: - Learning Weights

/// Learned weights for the feedback loop
struct LearningWeights: Codable {
    var typeWeights: [String: Double]        // ConnectionType.rawValue -> weight
    var keywordImportance: [String: Double]  // keyword -> importance

    init(
        typeWeights: [String: Double] = [:],
        keywordImportance: [String: Double] = [:]
    ) {
        self.typeWeights = typeWeights
        self.keywordImportance = keywordImportance
    }

    func weight(for type: ConnectionType) -> Double {
        typeWeights[type.rawValue] ?? 1.0
    }

    func importance(for keyword: String) -> Double {
        keywordImportance[keyword.lowercased()] ?? 1.0
    }

    mutating func adjustWeight(for type: ConnectionType, by delta: Double) {
        let current = weight(for: type)
        typeWeights[type.rawValue] = max(0.1, min(2.0, current + delta))
    }

    mutating func adjustImportance(for keyword: String, by delta: Double) {
        let current = importance(for: keyword)
        keywordImportance[keyword.lowercased()] = max(0.1, min(2.0, current + delta))
    }
}

// MARK: - Graph Node Position (for visualization)

/// Position for graph visualization
struct NodePosition: Codable {
    var x: Double
    var y: Double
    var velocityX: Double = 0
    var velocityY: Double = 0

    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }

    static func random(in size: CGSize) -> NodePosition {
        NodePosition(
            x: Double.random(in: 50...(Double(size.width) - 50)),
            y: Double.random(in: 50...(Double(size.height) - 50))
        )
    }

    mutating func apply(force: CGVector, damping: Double = 0.85) {
        velocityX = (velocityX + force.dx) * damping
        velocityY = (velocityY + force.dy) * damping
        x += velocityX
        y += velocityY
    }

    func distance(to other: NodePosition) -> Double {
        sqrt(pow(x - other.x, 2) + pow(y - other.y, 2))
    }
}

// MARK: - AI Response Models

/// Response structure for AI connection analysis
struct AIConnectionResponse: Codable {
    let connections: [AIConnectionSuggestion]
}

struct AIConnectionSuggestion: Codable {
    let source: String
    let target: String
    let type: String
    let strength: Double
    let reason: String
    let keywords: [String]?

    var connectionType: ConnectionType {
        ConnectionType(rawValue: type) ?? .semantic
    }
}


Perfect! Now let me check the ConnectionModels to understand the connection types and other related models:


     1→import Foundation
     2→import SwiftUI
     3→
     4→// MARK: - Connection Types
     5→
     6→/// Types of connections between notes
     7→enum ConnectionType: String, Codable, CaseIterable, Identifiable {
     8→    case semantic      // Similar topics/concepts (AI-detected)
     9→    case temporal      // Related by date/timeline
    10→    case hierarchical  // Parent-child relationships
    11→    case reference     // One note mentions concepts from another
    12→    case action        // Tasks that relate to other notes
    13→    case wikiLink      // Explicit [[wiki-link]]
    14→
    15→    var id: String { rawValue }
    16→
    17→    var icon: String {
    18→        switch self {
    19→        case .semantic: return "brain.head.profile"
    20→        case .temporal: return "calendar"
    21→        case .hierarchical: return "arrow.up.arrow.down"
    22→        case .reference: return "quote.bubble"
    23→        case .action: return "checklist"
    24→        case .wikiLink: return "link"
    25→        }
    26→    }
    27→
    28→    var color: Color {
    29→        switch self {
    30→        case .semantic: return .purple
    31→        case .temporal: return .blue
    32→        case .hierarchical: return .orange
    33→        case .reference: return .green
    34→        case .action: return .red
    35→        case .wikiLink: return .gray
    36→        }
    37→    }
    38→
    39→    var displayName: String {
    40→        switch self {
    41→        case .semantic: return "Semantic"
    42→        case .temporal: return "Temporal"
    43→        case .hierarchical: return "Hierarchical"
    44→        case .reference: return "Reference"
    45→        case .action: return "Action"
    46→        case .wikiLink: return "Wiki Link"
    47→        }
    48→    }
    49→
    50→    var description: String {
    51→        switch self {
    52→        case .semantic: return "Notes with similar topics or concepts"
    53→        case .temporal: return "Notes related by date or timeline"
    54→        case .hierarchical: return "Parent-child relationship between notes"
    55→        case .reference: return "One note references concepts from another"
    56→        case .action: return "Tasks or actions that relate to other notes"
    57→        case .wikiLink: return "Explicit [[wiki-link]] in the note"
    58→        }
    59→    }
    60→}
    61→
    62→// MARK: - Note Connection
    63→
    64→/// A connection between two notes
    65→struct NoteConnection: Identifiable, Codable, Hashable {
    66→    let id: UUID
    67→    let sourceNotePath: String
    68→    let targetNotePath: String
    69→    let connectionType: ConnectionType
    70→    var strength: Double           // 0.0 - 1.0
    71→    var reason: String             // AI-generated explanation
    72→    var isUserAccepted: Bool       // User confirmed this connection
    73→    var isUserRejected: Bool       // User rejected this connection
    74→    var createdAt: Date
    75→    var updatedAt: Date
    76→    var keywords: [String]         // Shared keywords/concepts
    77→
    78→    var isExistingWikiLink: Bool { connectionType == .wikiLink }
    79→
    80→    var sourceFileName: String {
    81→        URL(fileURLWithPath: sourceNotePath).deletingPathExtension().lastPathComponent
    82→    }
    83→
    84→    var targetFileName: String {
    85→        URL(fileURLWithPath: targetNotePath).deletingPathExtension().lastPathComponent
    86→    }
    87→
    88→    var strengthLabel: String {
    89→        switch strength {
    90→        case 0.8...1.0: return "Strong"
    91→        case 0.6..<0.8: return "Good"
    92→        case 0.4..<0.6: return "Moderate"
    93→        default: return "Weak"
    94→        }
    95→    }
    96→
    97→    var strengthColor: Color {
    98→        switch strength {
    99→        case 0.8...1.0: return .green
   100→        case 0.6..<0.8: return .blue
   101→        case 0.4..<0.6: return .orange
   102→        default: return .gray
   103→        }
   104→    }
   105→
   106→    init(
   107→        id: UUID = UUID(),
   108→        sourceNotePath: String,
   109→        targetNotePath: String,
   110→        connectionType: ConnectionType,
   111→        strength: Double,
   112→        reason: String,
   113→        isUserAccepted: Bool = false,
   114→        isUserRejected: Bool = false,
   115→        createdAt: Date = Date(),
   116→        updatedAt: Date = Date(),
   117→        keywords: [String] = []
   118→    ) {
   119→        self.id = id
   120→        self.sourceNotePath = sourceNotePath
   121→        self.targetNotePath = targetNotePath
   122→        self.connectionType = connectionType
   123→        self.strength = strength
   124→        self.reason = reason
   125→        self.isUserAccepted = isUserAccepted
   126→        self.isUserRejected = isUserRejected
   127→        self.createdAt = createdAt
   128→        self.updatedAt = updatedAt
   129→        self.keywords = keywords
   130→    }
   131→}
   132→
   133→// MARK: - Connection Suggestion
   134→
   135→/// Status of a connection suggestion
   136→enum SuggestionStatus: String, Codable {
   137→    case pending
   138→    case accepted
   139→    case rejected
   140→    case deferred   // "Ask later"
   141→}
   142→
   143→/// A suggested connection waiting for user action
   144→struct ConnectionSuggestion: Identifiable, Codable {
   145→    let id: UUID
   146→    var suggestedConnection: NoteConnection
   147→    var status: SuggestionStatus
   148→    var createdAt: Date
   149→    var processedAt: Date?
   150→    var batchId: UUID?             // Which analysis batch created this
   151→
   152→    var isPending: Bool { status == .pending }
   153→
   154→    init(
   155→        id: UUID = UUID(),
   156→        suggestedConnection: NoteConnection,
   157→        status: SuggestionStatus = .pending,
   158→        createdAt: Date = Date(),
   159→        processedAt: Date? = nil,
   160→        batchId: UUID? = nil
   161→    ) {
   162→        self.id = id
   163→        self.suggestedConnection = suggestedConnection
   164→        self.status = status
   165→        self.createdAt = createdAt
   166→        self.processedAt = processedAt
   167→        self.batchId = batchId
   168→    }
   169→}
   170→
   171→// MARK: - Note Node
   172→
   173→/// Note metadata for graph representation
   174→struct NoteNode: Identifiable, Codable, Hashable {
   175→    let id: String                 // path
   176→    let path: String
   177→    let title: String
   178→    var incomingConnections: Int
   179→    var outgoingConnections: Int
   180→    var clusterIds: [UUID]
   181→    var isOrphan: Bool
   182→    var lastAnalyzed: Date?
   183→    var contentHash: String?       // Detect changes
   184→    var tags: [String]
   185→    var metadata: NoteMetadata?
   186→
   187→    var totalConnections: Int {
   188→        incomingConnections + outgoingConnections
   189→    }
   190→
   191→    var displayName: String {
   192→        title.replacingOccurrences(of: ".md", with: "")
   193→    }
   194→
   195→    init(
   196→        path: String,
   197→        title: String? = nil,
   198→        incomingConnections: Int = 0,
   199→        outgoingConnections: Int = 0,
   200→        clusterIds: [UUID] = [],
   201→        isOrphan: Bool = true,
   202→        lastAnalyzed: Date? = nil,
   203→        contentHash: String? = nil,
   204→        tags: [String] = [],
   205→        metadata: NoteMetadata? = nil
   206→    ) {
   207→        self.id = path
   208→        self.path = path
   209→        self.title = title ?? URL(fileURLWithPath: path).deletingPathExtension().lastPathComponent
   210→        self.incomingConnections = incomingConnections
   211→        self.outgoingConnections = outgoingConnections
   212→        self.clusterIds = clusterIds
   213→        self.isOrphan = isOrphan
   214→        self.lastAnalyzed = lastAnalyzed
   215→        self.contentHash = contentHash
   216→        self.tags = tags
   217→        self.metadata = metadata
   218→    }
   219→}
   220→
   221→// MARK: - Note Metadata
   222→
   223→/// Parsed metadata from note content
   224→struct NoteMetadata: Codable, Hashable {
   225→    var connections: [String]      // Parsed from ## Conexoes section
   226→    var metadataSection: String?   // Raw ## Metadados content
   227→    var tipo: String?              // Note type from metadata
   228→    var categoria: String?         // Category
   229→    var status: String?            // Status emoji/text
   230→    var prioridade: String?        // Priority
   231→
   232→    init(
   233→        connections: [String] = [],
   234→        metadataSection: String? = nil,
   235→        tipo: String? = nil,
   236→        categoria: String? = nil,
   237→        status: String? = nil,
   238→        prioridade: String? = nil
   239→    ) {
   240→        self.connections = connections
   241→        self.metadataSection = metadataSection
   242→        self.tipo = tipo
   243→        self.categoria = categoria
   244→        self.status = status
   245→        self.prioridade = prioridade
   246→    }
   247→}
   248→
   249→// MARK: - Note Cluster
   250→
   251→/// Theme clusters for grouping related notes
   252→struct NoteCluster: Identifiable, Codable {
   253→    let id: UUID
   254→    var name: String               // AI-generated or user-defined
   255→    var theme: String              // Description of the theme
   256→    var noteIds: [String]          // paths of notes in cluster
   257→    var keywords: [String]
   258→    var colorHex: String           // Hex color for visualization
   259→    var createdAt: Date
   260→
   261→    var color: Color {
   262→        Color(hex: colorHex) ?? .purple
   263→    }
   264→
   265→    var noteCount: Int {
   266→        noteIds.count
   267→    }
   268→
   269→    init(
   270→        id: UUID = UUID(),
   271→        name: String,
   272→        theme: String,
   273→        noteIds: [String] = [],
   274→        keywords: [String] = [],
   275→        colorHex: String = "#8B5CF6",
   276→        createdAt: Date = Date()
   277→    ) {
   278→        self.id = id
   279→        self.name = name
   280→        self.theme = theme
   281→        self.noteIds = noteIds
   282→        self.keywords = keywords
   283→        self.colorHex = colorHex
   284→        self.createdAt = createdAt
   285→    }
   286→}
   287→
   288→// MARK: - Color Extension
   289→
   290→extension Color {
   291→    init?(hex: String) {
   292→        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
   293→        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
   294→
   295→        var rgb: UInt64 = 0
   296→        guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
   297→
   298→        let r = Double((rgb & 0xFF0000) >> 16) / 255.0
   299→        let g = Double((rgb & 0x00FF00) >> 8) / 255.0
   300→        let b = Double(rgb & 0x0000FF) / 255.0
   301→
   302→        self.init(red: r, green: g, blue: b)
   303→    }
   304→
   305→    func toHex() -> String {
   306→        guard let components = UIColor(self).cgColor.components else { return "#8B5CF6" }
   307→        let r = Int(components[0] * 255)
   308→        let g = Int(components[1] * 255)
   309→        let b = Int(components[2] * 255)
   310→        return String(format: "#%02X%02X%02X", r, g, b)
   311→    }
   312→}
   313→

<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 Foundation
import SwiftUI

// MARK: - Connection Types

/// Types of connections between notes
enum ConnectionType: String, Codable, CaseIterable, Identifiable {
    case semantic      // Similar topics/concepts (AI-detected)
    case temporal      // Related by date/timeline
    case hierarchical  // Parent-child relationships
    case reference     // One note mentions concepts from another
    case action        // Tasks that relate to other notes
    case wikiLink      // Explicit [[wiki-link]]

    var id: String { rawValue }

    var icon: String {
        switch self {
        case .semantic: return "brain.head.profile"
        case .temporal: return "calendar"
        case .hierarchical: return "arrow.up.arrow.down"
        case .reference: return "quote.bubble"
        case .action: return "checklist"
        case .wikiLink: return "link"
        }
    }

    var color: Color {
        switch self {
        case .semantic: return .purple
        case .temporal: return .blue
        case .hierarchical: return .orange
        case .reference: return .green
        case .action: return .red
        case .wikiLink: return .gray
        }
    }

    var displayName: String {
        switch self {
        case .semantic: return "Semantic"
        case .temporal: return "Temporal"
        case .hierarchical: return "Hierarchical"
        case .reference: return "Reference"
        case .action: return "Action"
        case .wikiLink: return "Wiki Link"
        }
    }

    var description: String {
        switch self {
        case .semantic: return "Notes with similar topics or concepts"
        case .temporal: return "Notes related by date or timeline"
        case .hierarchical: return "Parent-child relationship between notes"
        case .reference: return "One note references concepts from another"
        case .action: return "Tasks or actions that relate to other notes"
        case .wikiLink: return "Explicit [[wiki-link]] in the note"
        }
    }
}

// MARK: - Note Connection

/// A connection between two notes
struct NoteConnection: Identifiable, Codable, Hashable {
    let id: UUID
    let sourceNotePath: String
    let targetNotePath: String
    let connectionType: ConnectionType
    var strength: Double           // 0.0 - 1.0
    var reason: String             // AI-generated explanation
    var isUserAccepted: Bool       // User confirmed this connection
    var isUserRejected: Bool       // User rejected this connection
    var createdAt: Date
    var updatedAt: Date
    var keywords: [String]         // Shared keywords/concepts

    var isExistingWikiLink: Bool { connectionType == .wikiLink }

    var sourceFileName: String {
        URL(fileURLWithPath: sourceNotePath).deletingPathExtension().lastPathComponent
    }

    var targetFileName: String {
        URL(fileURLWithPath: targetNotePath).deletingPathExtension().lastPathComponent
    }

    var strengthLabel: String {
        switch strength {
        case 0.8...1.0: return "Strong"
        case 0.6..<0.8: return "Good"
        case 0.4..<0.6: return "Moderate"
        default: return "Weak"
        }
    }

    var strengthColor: Color {
        switch strength {
        case 0.8...1.0: return .green
        case 0.6..<0.8: return .blue
        case 0.4..<0.6: return .orange
        default: return .gray
        }
    }

    init(
        id: UUID = UUID(),
        sourceNotePath: String,
        targetNotePath: String,
        connectionType: ConnectionType,
        strength: Double,
        reason: String,
        isUserAccepted: Bool = false,
        isUserRejected: Bool = false,
        createdAt: Date = Date(),
        updatedAt: Date = Date(),
        keywords: [String] = []
    ) {
        self.id = id
        self.sourceNotePath = sourceNotePath
        self.targetNotePath = targetNotePath
        self.connectionType = connectionType
        self.strength = strength
        self.reason = reason
        self.isUserAccepted = isUserAccepted
        self.isUserRejected = isUserRejected
        self.createdAt = createdAt
        self.updatedAt = updatedAt
        self.keywords = keywords
    }
}

// MARK: - Connection Suggestion

/// Status of a connection suggestion
enum SuggestionStatus: String, Codable {
    case pending
    case accepted
    case rejected
    case deferred   // "Ask later"
}

/// A suggested connection waiting for user action
struct ConnectionSuggestion: Identifiable, Codable {
    let id: UUID
    var suggestedConnection: NoteConnection
    var status: SuggestionStatus
    var createdAt: Date
    var processedAt: Date?
    var batchId: UUID?             // Which analysis batch created this

    var isPending: Bool { status == .pending }

    init(
        id: UUID = UUID(),
        suggestedConnection: NoteConnection,
        status: SuggestionStatus = .pending,
        createdAt: Date = Date(),
        processedAt: Date? = nil,
        batchId: UUID? = nil
    ) {
        self.id = id
        self.suggestedConnection = suggestedConnection
        self.status = status
        self.createdAt = createdAt
        self.processedAt = processedAt
        self.batchId = batchId
    }
}

// MARK: - Note Node

/// Note metadata for graph representation
struct NoteNode: Identifiable, Codable, Hashable {
    let id: String                 // path
    let path: String
    let title: String
    var incomingConnections: Int
    var outgoingConnections: Int
    var clusterIds: [UUID]
    var isOrphan: Bool
    var lastAnalyzed: Date?
    var contentHash: String?       // Detect changes
    var tags: [String]
    var metadata: NoteMetadata?

    var totalConnections: Int {
        incomingConnections + outgoingConnections
    }

    var displayName: String {
        title.replacingOccurrences(of: ".md", with: "")
    }

    init(
        path: String,
        title: String? = nil,
        incomingConnections: Int = 0,
        outgoingConnections: Int = 0,
        clusterIds: [UUID] = [],
        isOrphan: Bool = true,
        lastAnalyzed: Date? = nil,
        contentHash: String? = nil,
        tags: [String] = [],
        metadata: NoteMetadata? = nil
    ) {
        self.id = path
        self.path = path
        self.title = title ?? URL(fileURLWithPath: path).deletingPathExtension().lastPathComponent
        self.incomingConnections = incomingConnections
        self.outgoingConnections = outgoingConnections
        self.clusterIds = clusterIds
        self.isOrphan = isOrphan
        self.lastAnalyzed = lastAnalyzed
        self.contentHash = contentHash
        self.tags = tags
        self.metadata = metadata
    }
}

// MARK: - Note Metadata

/// Parsed metadata from note content
struct NoteMetadata: Codable, Hashable {
    var connections: [String]      // Parsed from ## Conexoes section
    var metadataSection: String?   // Raw ## Metadados content
    var tipo: String?              // Note type from metadata
    var categoria: String?         // Category
    var status: String?            // Status emoji/text
    var prioridade: String?        // Priority

    init(
        connections: [String] = [],
        metadataSection: String? = nil,
        tipo: String? = nil,
        categoria: String? = nil,
        status: String? = nil,
        prioridade: String? = nil
    ) {
        self.connections = connections
        self.metadataSection = metadataSection
        self.tipo = tipo
        self.categoria = categoria
        self.status = status
        self.prioridade = prioridade
    }
}

// MARK: - Note Cluster

/// Theme clusters for grouping related notes
struct NoteCluster: Identifiable, Codable {
    let id: UUID
    var name: String               // AI-generated or user-defined
    var theme: String              // Description of the theme
    var noteIds: [String]          // paths of notes in cluster
    var keywords: [String]
    var colorHex: String           // Hex color for visualization
    var createdAt: Date

    var color: Color {
        Color(hex: colorHex) ?? .purple
    }

    var noteCount: Int {
        noteIds.count
    }

    init(
        id: UUID = UUID(),
        name: String,
        theme: String,
        noteIds: [String] = [],
        keywords: [String] = [],
        colorHex: String = "#8B5CF6",
        createdAt: Date = Date()
    ) {
        self.id = id
        self.name = name
        self.theme = theme
        self.noteIds = noteIds
        self.keywords = keywords
        self.colorHex = colorHex
        self.createdAt = createdAt
    }
}

// MARK: - Color Extension

extension Color {
    init?(hex: String) {
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")

        var rgb: UInt64 = 0
        guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }

        let r = Double((rgb & 0xFF0000) >> 16) / 255.0
        let g = Double((rgb & 0x00FF00) >> 8) / 255.0
        let b = Double(rgb & 0x0000FF) / 255.0

        self.init(red: r, green: g, blue: b)
    }

    func toHex() -> String {
        guard let components = UIColor(self).cgColor.components else { return "#8B5CF6" }
        let r = Int(components[0] * 255)
        let g = Int(components[1] * 255)
        let b = Int(components[2] * 255)
        return String(format: "#%02X%02X%02X", r, g, b)
    }
}


Excellent! Now let me look at one of the progress views to see how the current progress tracking is displayed:


     1→import SwiftUI
     2→
     3→/// Dashboard showing feedback loop metrics and vault health
     4→struct ProgressDashboardView: View {
     5→    @EnvironmentObject var connectionService: SmartConnectionService
     6→
     7→    private var metrics: FeedbackMetrics {
     8→        connectionService.feedbackMetrics
     9→    }
    10→
    11→    private var graph: VaultGraph? {
    12→        connectionService.vaultGraph
    13→    }
    14→
    15→    var body: some View {
    16→        ScrollView {
    17→            VStack(spacing: 20) {
    18→                // Hero stats
    19→                heroStatsSection
    20→
    21→                // Model accuracy
    22→                accuracySection
    23→
    24→                // Vault health
    25→                vaultHealthSection
    26→
    27→                // Connection types breakdown
    28→                connectionTypesSection
    29→
    30→                // Weekly trends
    31→                weeklyTrendsSection
    32→
    33→                // Storage info
    34→                storageSection
    35→            }
    36→            .padding()
    37→        }
    38→        .refreshable {
    39→            let generator = UIImpactFeedbackGenerator(style: .light)
    40→            generator.impactOccurred()
    41→            connectionService.loadPersistedData()
    42→        }
    43→    }
    44→
    45→    // MARK: - Hero Stats
    46→
    47→    private var heroStatsSection: some View {
    48→        HStack(spacing: 12) {
    49→            StatCard(
    50→                title: "Accepted",
    51→                value: "\(metrics.suggestionsAccepted)",
    52→                icon: "checkmark.circle.fill",
    53→                color: .green
    54→            )
    55→
    56→            StatCard(
    57→                title: "Rejected",
    58→                value: "\(metrics.suggestionsRejected)",
    59→                icon: "xmark.circle.fill",
    60→                color: .red
    61→            )
    62→
    63→            StatCard(
    64→                title: "Written",
    65→                value: "\(metrics.connectionsAddedToVault)",
    66→                icon: "doc.badge.plus",
    67→                color: .blue
    68→            )
    69→        }
    70→    }
    71→
    72→    // MARK: - Accuracy Section
    73→
    74→    private var accuracySection: some View {
    75→        VStack(alignment: .leading, spacing: 12) {
    76→            Text("AI Model Accuracy")
    77→                .font(.headline)
    78→
    79→            HStack(spacing: 20) {
    80→                // Large accuracy ring
    81→                ZStack {
    82→                    Circle()
    83→                        .stroke(Color.green.opacity(0.2), lineWidth: 12)
    84→
    85→                    Circle()
    86→                        .trim(from: 0, to: metrics.modelAccuracy)
    87→                        .stroke(
    88→                            metrics.modelAccuracy > 0.7 ? Color.green : Color.orange,
    89→                            style: StrokeStyle(lineWidth: 12, lineCap: .round)
    90→                        )
    91→                        .rotationEffect(.degrees(-90))
    92→
    93→                    VStack(spacing: 4) {
    94→                        Text("\(Int(metrics.modelAccuracy * 100))%")
    95→                            .font(.title)
    96→                            .fontWeight(.bold)
    97→
    98→                        Text("Accuracy")
    99→                            .font(.caption)
   100→                            .foregroundColor(.secondary)
   101→                    }
   102→                }
   103→                .frame(width: 120, height: 120)
   104→
   105→                VStack(alignment: .leading, spacing: 12) {
   106→                    Text("The AI learns from your feedback")
   107→                        .font(.subheadline)
   108→                        .fontWeight(.medium)
   109→
   110→                    VStack(alignment: .leading, spacing: 6) {
   111→                        HStack(spacing: 8) {
   112→                            Image(systemName: "checkmark.circle")
   113→                                .foregroundColor(.green)
   114→                            Text("Accept: Strengthens similar suggestions")
   115→                                .font(.caption)
   116→                                .foregroundColor(.secondary)
   117→                        }
   118→
   119→                        HStack(spacing: 8) {
   120→                            Image(systemName: "xmark.circle")
   121→                                .foregroundColor(.red)
   122→                            Text("Reject: Reduces similar suggestions")
   123→                                .font(.caption)
   124→                                .foregroundColor(.secondary)
   125→                        }
   126→                    }
   127→                }
   128→
   129→                Spacer()
   130→            }
   131→            .padding()
   132→            .background(Color(.systemGray6))
   133→            .cornerRadius(16)
   134→        }
   135→    }
   136→
   137→    // MARK: - Vault Health Section
   138→
   139→    private var vaultHealthSection: some View {
   140→        VStack(alignment: .leading, spacing: 12) {
   141→            Text("Vault Health")
   142→                .font(.headline)
   143→
   144→            if let graph = graph {
   145→                LazyVGrid(columns: [
   146→                    GridItem(.flexible()),
   147→                    GridItem(.flexible())
   148→                ], spacing: 12) {
   149→                    HealthMetric(
   150→                        title: "Total Notes",
   151→                        value: "\(graph.totalNotes)",
   152→                        icon: "doc.text.fill",
   153→                        color: .blue
   154→                    )
   155→
   156→                    HealthMetric(
   157→                        title: "Connections",
   158→                        value: "\(graph.connectionCount)",
   159→                        icon: "link",
   160→                        color: .green
   161→                    )
   162→
   163→                    HealthMetric(
   164→                        title: "Orphans",
   165→                        value: "\(graph.orphanCount)",
   166→                        icon: "questionmark.circle.fill",
   167→                        color: graph.orphanCount > 10 ? .orange : .gray
   168→                    )
   169→
   170→                    HealthMetric(
   171→                        title: "Clusters",
   172→                        value: "\(graph.clusters.count)",
   173→                        icon: "circle.hexagongrid.fill",
   174→                        color: .purple
   175→                    )
   176→
   177→                    HealthMetric(
   178→                        title: "Avg Connections",
   179→                        value: String(format: "%.1f", graph.averageConnectionsPerNote),
   180→                        icon: "chart.bar.fill",
   181→                        color: .blue
   182→                    )
   183→
   184→                    HealthMetric(
   185→                        title: "Last Analysis",
   186→                        value: graph.lastFullAnalysis?.formatted(.relative(presentation: .numeric)) ?? "Never",
   187→                        icon: "clock.fill",
   188→                        color: .gray
   189→                    )
   190→                }
   191→            } else {
   192→                Text("Run an analysis to see vault health metrics")
   193→                    .font(.subheadline)
   194→                    .foregroundColor(.secondary)
   195→                    .frame(maxWidth: .infinity, alignment: .center)
   196→                    .padding()
   197→                    .background(Color(.systemGray6))
   198→                    .cornerRadius(12)
   199→            }
   200→        }
   201→    }
   202→
   203→    // MARK: - Connection Types Breakdown
   204→
   205→    private var connectionTypesSection: some View {
   206→        VStack(alignment: .leading, spacing: 12) {
   207→            Text("Connection Types")
   208→                .font(.headline)
   209→
   210→            if let graph = graph, !graph.edges.isEmpty {
   211→                VStack(spacing: 8) {
   212→                    ForEach(ConnectionType.allCases) { type in
   213→                        let count = graph.connectionsByType[type] ?? 0
   214→                        let percentage = Double(count) / Double(graph.connectionCount)
   215→
   216→                        HStack {
   217→                            Image(systemName: type.icon)
   218→                                .foregroundColor(type.color)
   219→                                .frame(width: 24)
   220→
   221→                            Text(type.displayName)
   222→                                .font(.subheadline)
   223→
   224→                            Spacer()
   225→
   226→                            Text("\(count)")
   227→                                .font(.subheadline)
   228→                                .fontWeight(.medium)
   229→                        }
   230→
   231→                        GeometryReader { geo in
   232→                            ZStack(alignment: .leading) {
   233→                                Rectangle()
   234→                                    .fill(Color(.systemGray5))
   235→
   236→                                Rectangle()
   237→                                    .fill(type.color)
   238→                                    .frame(width: geo.size.width * percentage)
   239→                            }
   240→                        }
   241→                        .frame(height: 6)
   242→                        .cornerRadius(3)
   243→                    }
   244→                }
   245→                .padding()
   246→                .background(Color(.systemGray6))
   247→                .cornerRadius(12)
   248→            } else {
   249→                Text("No connections analyzed yet")
   250→                    .font(.subheadline)
   251→                    .foregroundColor(.secondary)
   252→                    .frame(maxWidth: .infinity, alignment: .center)
   253→                    .padding()
   254→                    .background(Color(.systemGray6))
   255→                    .cornerRadius(12)
   256→            }
   257→        }
   258→    }
   259→
   260→    // MARK: - Weekly Trends
   261→
   262→    private var weeklyTrendsSection: some View {
   263→        VStack(alignment: .leading, spacing: 12) {
   264→            Text("Weekly Trends")
   265→                .font(.headline)
   266→
   267→            if metrics.weeklyStats.isEmpty {
   268→                Text("Accept or reject suggestions to see trends")
   269→                    .font(.subheadline)
   270→                    .foregroundColor(.secondary)
   271→                    .frame(maxWidth: .infinity, alignment: .center)
   272→                    .padding()
   273→                    .background(Color(.systemGray6))
   274→                    .cornerRadius(12)
   275→            } else {
   276→                HStack(alignment: .bottom, spacing: 8) {
   277→                    ForEach(metrics.weeklyStats.suffix(8)) { week in
   278→                        VStack(spacing: 4) {
   279→                            // Stacked bar
   280→                            VStack(spacing: 0) {
   281→                                Rectangle()
   282→                                    .fill(Color.red.opacity(0.7))
   283→                                    .frame(height: CGFloat(week.suggestionsRejected * 4))
   284→
   285→                                Rectangle()
   286→                                    .fill(Color.green)
   287→                                    .frame(height: CGFloat(week.suggestionsAccepted * 4))
   288→                            }
   289→                            .frame(width: 30)
   290→                            .cornerRadius(4)
   291→
   292→                            Text(week.weekStartDate, format: .dateTime.day())
   293→                                .font(.caption2)
   294→                                .foregroundColor(.secondary)
   295→                        }
   296→                    }
   297→
   298→                    Spacer()
   299→
   300→                    // Legend
   301→                    VStack(alignment: .leading, spacing: 4) {
   302→                        HStack(spacing: 4) {
   303→                            Rectangle()
   304→                                .fill(Color.green)
   305→                                .frame(width: 12, height: 12)
   306→                                .cornerRadius(2)
   307→                            Text("Accepted")
   308→                                .font(.caption2)
   309→                        }
   310→                        HStack(spacing: 4) {
   311→                            Rectangle()
   312→                                .fill(Color.red.opacity(0.7))
   313→                                .frame(width: 12, height: 12)
   314→                                .cornerRadius(2)
   315→                            Text("Rejected")
   316→                                .font(.caption2)
   317→                        }
   318→                    }
   319→                }
   320→                .padding()
   321→                .background(Color(.systemGray6))
   322→                .cornerRadius(12)
   323→            }
   324→        }
   325→    }
   326→
   327→    // MARK: - Storage Section
   328→
   329→    private var storageSection: some View {
   330→        VStack(alignment: .leading, spacing: 12) {
   331→            Text("Storage")
   332→                .font(.headline)
   333→
   334→            HStack {
   335→                Image(systemName: "internaldrive.fill")
   336→                    .foregroundColor(.gray)
   337→
   338→                Text("Smart Connections Data")
   339→                    .font(.subheadline)
   340→
   341→                Spacer()
   342→
   343→                Text(ConnectionPersistence().getStorageSizeFormatted())
   344→                    .font(.subheadline)
   345→                    .foregroundColor(.secondary)
   346→            }
   347→            .padding()
   348→            .background(Color(.systemGray6))
   349→            .cornerRadius(12)
   350→        }
   351→    }
   352→}
   353→
   354→// MARK: - Stat Card
   355→
   356→struct StatCard: View {
   357→    let title: String
   358→    let value: String
   359→    let icon: String
   360→    let color: Color
   361→
   362→    var body: some View {
   363→        VStack(spacing: 8) {
   364→            Image(systemName: icon)
   365→                .font(.title2)
   366→                .foregroundColor(color)
   367→
   368→            Text(value)
   369→                .font(.title2)
   370→                .fontWeight(.bold)
   371→
   372→            Text(title)
   373→                .font(.caption)
   374→                .foregroundColor(.secondary)
   375→        }
   376→        .frame(maxWidth: .infinity)
   377→        .padding()
   378→        .background(color.opacity(0.1))
   379→        .cornerRadius(12)
   380→    }
   381→}
   382→
   383→// MARK: - Health Metric
   384→
   385→struct HealthMetric: View {
   386→    let title: String
   387→    let value: String
   388→    let icon: String
   389→    let color: Color
   390→
   391→    var body: some View {
   392→        HStack(spacing: 12) {
   393→            Image(systemName: icon)
   394→                .font(.title3)
   395→                .foregroundColor(color)
   396→                .frame(width: 30)
   397→
   398→            VStack(alignment: .leading, spacing: 2) {
   399→                Text(value)
   400→                    .font(.headline)
   401→                Text(title)
   402→                    .font(.caption)
   403→                    .foregroundColor(.secondary)
   404→            }
   405→
   406→            Spacer()
   407→        }
   408→        .padding()
   409→        .background(Color(.systemGray6))
   410→        .cornerRadius(12)
   411→    }
   412→}
   413→
   414→// MARK: - Preview
   415→
   416→#Preview {
   417→    ProgressDashboardView()
   418→        .environmentObject(SmartConnectionService(
   419→            obsidianService: ObsidianService(),
   420→            lmService: LMGatewayService()
   421→        ))
   422→}
   423→

<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

/// Dashboard showing feedback loop metrics and vault health
struct ProgressDashboardView: View {
    @EnvironmentObject var connectionService: SmartConnectionService

    private var metrics: FeedbackMetrics {
        connectionService.feedbackMetrics
    }

    private var graph: VaultGraph? {
        connectionService.vaultGraph
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // Hero stats
                heroStatsSection

                // Model accuracy
                accuracySection

                // Vault health
                vaultHealthSection

                // Connection types breakdown
                connectionTypesSection

                // Weekly trends
                weeklyTrendsSection

                // Storage info
                storageSection
            }
            .padding()
        }
        .refreshable {
            let generator = UIImpactFeedbackGenerator(style: .light)
            generator.impactOccurred()
            connectionService.loadPersistedData()
        }
    }

    // MARK: - Hero Stats

    private var heroStatsSection: some View {
        HStack(spacing: 12) {
            StatCard(
                title: "Accepted",
                value: "\(metrics.suggestionsAccepted)",
                icon: "checkmark.circle.fill",
                color: .green
            )

            StatCard(
                title: "Rejected",
                value: "\(metrics.suggestionsRejected)",
                icon: "xmark.circle.fill",
                color: .red
            )

            StatCard(
                title: "Written",
                value: "\(metrics.connectionsAddedToVault)",
                icon: "doc.badge.plus",
                color: .blue
            )
        }
    }

    // MARK: - Accuracy Section

    private var accuracySection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("AI Model Accuracy")
                .font(.headline)

            HStack(spacing: 20) {
                // Large accuracy ring
                ZStack {
                    Circle()
                        .stroke(Color.green.opacity(0.2), lineWidth: 12)

                    Circle()
                        .trim(from: 0, to: metrics.modelAccuracy)
                        .stroke(
                            metrics.modelAccuracy > 0.7 ? Color.green : Color.orange,
                            style: StrokeStyle(lineWidth: 12, lineCap: .round)
                        )
                        .rotationEffect(.degrees(-90))

                    VStack(spacing: 4) {
                        Text("\(Int(metrics.modelAccuracy * 100))%")
                            .font(.title)
                            .fontWeight(.bold)

                        Text("Accuracy")
                            .font(.caption)
                            .foregroundColor(.secondary)
                    }
                }
                .frame(width: 120, height: 120)

                VStack(alignment: .leading, spacing: 12) {
                    Text("The AI learns from your feedback")
                        .font(.subheadline)
                        .fontWeight(.medium)

                    VStack(alignment: .leading, spacing: 6) {
                        HStack(spacing: 8) {
                            Image(systemName: "checkmark.circle")
                                .foregroundColor(.green)
                            Text("Accept: Strengthens similar suggestions")
                                .font(.caption)
                                .foregroundColor(.secondary)
                        }

                        HStack(spacing: 8) {
                            Image(systemName: "xmark.circle")
                                .foregroundColor(.red)
                            Text("Reject: Reduces similar suggestions")
                                .font(.caption)
                                .foregroundColor(.secondary)
                        }
                    }
                }

                Spacer()
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(16)
        }
    }

    // MARK: - Vault Health Section

    private var vaultHealthSection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Vault Health")
                .font(.headline)

            if let graph = graph {
                LazyVGrid(columns: [
                    GridItem(.flexible()),
                    GridItem(.flexible())
                ], spacing: 12) {
                    HealthMetric(
                        title: "Total Notes",
                        value: "\(graph.totalNotes)",
                        icon: "doc.text.fill",
                        color: .blue
                    )

                    HealthMetric(
                        title: "Connections",
                        value: "\(graph.connectionCount)",
                        icon: "link",
                        color: .green
                    )

                    HealthMetric(
                        title: "Orphans",
                        value: "\(graph.orphanCount)",
                        icon: "questionmark.circle.fill",
                        color: graph.orphanCount > 10 ? .orange : .gray
                    )

                    HealthMetric(
                        title: "Clusters",
                        value: "\(graph.clusters.count)",
                        icon: "circle.hexagongrid.fill",
                        color: .purple
                    )

                    HealthMetric(
                        title: "Avg Connections",
                        value: String(format: "%.1f", graph.averageConnectionsPerNote),
                        icon: "chart.bar.fill",
                        color: .blue
                    )

                    HealthMetric(
                        title: "Last Analysis",
                        value: graph.lastFullAnalysis?.formatted(.relative(presentation: .numeric)) ?? "Never",
                        icon: "clock.fill",
                        color: .gray
                    )
                }
            } else {
                Text("Run an analysis to see vault health metrics")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    .frame(maxWidth: .infinity, alignment: .center)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(12)
            }
        }
    }

    // MARK: - Connection Types Breakdown

    private var connectionTypesSection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Connection Types")
                .font(.headline)

            if let graph = graph, !graph.edges.isEmpty {
                VStack(spacing: 8) {
                    ForEach(ConnectionType.allCases) { type in
                        let count = graph.connectionsByType[type] ?? 0
                        let percentage = Double(count) / Double(graph.connectionCount)

                        HStack {
                            Image(systemName: type.icon)
                                .foregroundColor(type.color)
                                .frame(width: 24)

                            Text(type.displayName)
                                .font(.subheadline)

                            Spacer()

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

                        GeometryReader { geo in
                            ZStack(alignment: .leading) {
                                Rectangle()
                                    .fill(Color(.systemGray5))

                                Rectangle()
                                    .fill(type.color)
                                    .frame(width: geo.size.width * percentage)
                            }
                        }
                        .frame(height: 6)
                        .cornerRadius(3)
                    }
                }
                .padding()
                .background(Color(.systemGray6))
                .cornerRadius(12)
            } else {
                Text("No connections analyzed yet")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    .frame(maxWidth: .infinity, alignment: .center)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(12)
            }
        }
    }

    // MARK: - Weekly Trends

    private var weeklyTrendsSection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Weekly Trends")
                .font(.headline)

            if metrics.weeklyStats.isEmpty {
                Text("Accept or reject suggestions to see trends")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    .frame(maxWidth: .infinity, alignment: .center)
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(12)
            } else {
                HStack(alignment: .bottom, spacing: 8) {
                    ForEach(metrics.weeklyStats.suffix(8)) { week in
                        VStack(spacing: 4) {
                            // Stacked bar
                            VStack(spacing: 0) {
                                Rectangle()
                                    .fill(Color.red.opacity(0.7))
                                    .frame(height: CGFloat(week.suggestionsRejected * 4))

                                Rectangle()
                                    .fill(Color.green)
                                    .frame(height: CGFloat(week.suggestionsAccepted * 4))
                            }
                            .frame(width: 30)
                            .cornerRadius(4)

                            Text(week.weekStartDate, format: .dateTime.day())
                                .font(.caption2)
                                .foregroundColor(.secondary)
                        }
                    }

                    Spacer()

                    // Legend
                    VStack(alignment: .leading, spacing: 4) {
                        HStack(spacing: 4) {
                            Rectangle()
                                .fill(Color.green)
                                .frame(width: 12, height: 12)
                                .cornerRadius(2)
                            Text("Accepted")
                                .font(.caption2)
                        }
                        HStack(spacing: 4) {
                            Rectangle()
                                .fill(Color.red.opacity(0.7))
                                .frame(width: 12, height: 12)
                                .cornerRadius(2)
                            Text("Rejected")
                                .font(.caption2)
                        }
                    }
                }
                .padding()
                .background(Color(.systemGray6))
                .cornerRadius(12)
            }
        }
    }

    // MARK: - Storage Section

    private var storageSection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Storage")
                .font(.headline)

            HStack {
                Image(systemName: "internaldrive.fill")
                    .foregroundColor(.gray)

                Text("Smart Connections Data")
                    .font(.subheadline)

                Spacer()

                Text(ConnectionPersistence().getStorageSizeFormatted())
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(12)
        }
    }
}

// MARK: - Stat Card

struct StatCard: View {
    let title: String
    let value: String
    let icon: String
    let color: Color

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

            Text(value)
                .font(.title2)
                .fontWeight(.bold)

            Text(title)
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding()
        .background(color.opacity(0.1))
        .cornerRadius(12)
    }
}

// MARK: - Health Metric

struct HealthMetric: View {
    let title: String
    let value: String
    let icon: String
    let color: Color

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: icon)
                .font(.title3)
                .foregroundColor(color)
                .frame(width: 30)

            VStack(alignment: .leading, spacing: 2) {
                Text(value)
                    .font(.headline)
                Text(title)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            Spacer()
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(12)
    }
}

// MARK: - Preview

#Preview {
    ProgressDashboardView()
        .environmentObject(SmartConnectionService(
            obsidianService: ObsidianService(),
            lmService: LMGatewayService()
        ))
}


Perfect! Now let me check one more view to see how the live analysis is displayed: