# Project export: SafeLine

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: SafeLine is a covert safety app for abuse victims disguised as a notes app. It detects abuse, documents evidence, and can silently alert a trusted reviewer when someone can’t safely ask for help.
- Devpost: https://devpost.com/software/safeline-qvlzdg
- GitHub: https://github.com/arnauldmartinez/treehacks-test
- Video: https://www.youtube.com/embed/86sOKIC7uvY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — arnauldmartinez (7 commits), Jacob Schuster (2 commits)

## Devpost submission (written by the team)

### Inspiration

Domestic abuse affects millions of people worldwide, and many victims are unable to safely seek help. Fear, coercion, and constant monitoring often prevent them from accessing resources, learning about their rights, or reaching out to others. In many cases, even searching for support online is a risk that can likely lead to danger. We were inspired by the reality that the people who most need help often have the least safe access to it. We wanted to design a solution built specifically for those constraints. It needed to be something discreet, protective, and aware of the complexities of abusive situations. SafeLine was created with one central goal: to support victims in ways that are practicable, subtle, and centered entirely on their safety.

### What it does

SafeLine is a covert safety platform disguised as a standard notes app. By creating a note with a specific password as the title, users unlock a secure system designed for documenting and responding to domestic abuse. Once accessed, the app becomes a protected space for recording incidents through text, audio, and photos while maintaining discretion and privacy. The platform has 5 main features: Real-time abuse detection is powered by on-device voice activity detection and lightweight threat classification. When harmful or escalating language is detected, the app automatically logs the interaction, generates a structured summary, and securely stores it as evidence. Real-time abuse detection is powered by on-device voice activity detection and lightweight threat classification. When harmful or escalating language is detected, the app automatically logs the interaction, generates a structured summary, and securely stores it as evidence. Emergency escalation allows victims, when they cannot safely place a call themselves, to trigger a notification that provides authorities with critical information such as name, address, age, and a summary of recent incidents. Emergency escalation allows victims, when they cannot safely place a call themselves, to trigger a notification that provides authorities with critical information such as name, address, age, and a summary of recent incidents. A reviewer dashboard allows trusted advocates to monitor multiple users, review incident histories, receive imminent danger alerts within seconds, and determine whether escalation to law enforcement is necessary. A reviewer dashboard allows trusted advocates to monitor multiple users, review incident histories, receive imminent danger alerts within seconds, and determine whether escalation to law enforcement is necessary. An integrated AI assistant provides guidance on legal rights, safety planning, medical concerns, and situational advice using the user’s documented history as context. An integrated AI assistant provides guidance on legal rights, safety planning, medical concerns, and situational advice using the user’s documented history as context. A protected communication system enables safe chats with trusted reviewers, experts, lawyers, friends, and support contacts without exposing activity. A protected communication system enables safe chats with trusted reviewers, experts, lawyers, friends, and support contacts without exposing activity. Additional features Automatic reversion to safe mode ensures the app returns to its normal notes interface if left unattended (using facial recognition), preventing accidental exposure. Automatic reversion to safe mode ensures the app returns to its normal notes interface if left unattended (using facial recognition), preventing accidental exposure. Secure documentation allows users to manually add incidents. All data is encrypted and can optionally be shared with a trusted independent reviewer for oversight and support. Secure documentation allows users to manually add incidents. All data is encrypted and can optionally be shared with a trusted independent reviewer for oversight and support.

### How we built it

📱 Frontend (Native iOS Experience): Built fully in SwiftUI with an MVVM architecture, delivering a fast, seamless app that feels indistinguishable from a normal everyday Notes-style interface. 🔒 Privacy + Secure Access Control: We use CryptoKit passcode hashing and Apple Vision face detection to automatically lock the app when someone else is nearby, protecting users instantly and silently. 🎙️ Real-Time On-Device Threat Monitoring: Powered by AVAudioEngine and Apple Speech Recognition, the app can detect and log suspicious situations as they happen, without requiring the user to manually activate anything. 🤖 AI Reasoning + User Support: Integrated with OpenAI’s multimodal models, our system can classify incidents from transcripts and provide contextual, expert-informed guidance in real time. ☁️ Backend Infrastructure + Persistent Chat Storage: A dedicated web server backed by Supabase (Postgres + Auth) securely stores conversations and enables reliable syncing and reviewer communication. 📞 Autonomous Emergency Escalation: Using Twilio, the platform can automatically trigger phone calls and alerts during high-risk moments, ensuring help is contacted even when the user cannot.

### Challenges we ran into

Privacy-Preserving Detection: Designing an edge-triggered audio system like Siri using RMS thresholds and semantic threat analysis without continuous recording, while carefully tuning models to reduce false positives and still capture the majority of abusive incidents. Privacy-Preserving Detection: Designing an edge-triggered audio system like Siri using RMS thresholds and semantic threat analysis without continuous recording, while carefully tuning models to reduce false positives and still capture the majority of abusive incidents. Secure, Low-Latency Alerting: Building an encrypted server pipelines that delivers emergency notifications in seconds, balancing rapid response times with strict privacy and access controls. Secure, Low-Latency Alerting: Building an encrypted server pipelines that delivers emergency notifications in seconds, balancing rapid response times with strict privacy and access controls. On-Device Performance Constraints: Running continuous audio and vision monitoring on a mobile device forced us to optimize heavily for battery, latency, and resource usage while maintaining real-time responsiveness. On-Device Performance Constraints: Running continuous audio and vision monitoring on a mobile device forced us to optimize heavily for battery, latency, and resource usage while maintaining real-time responsiveness. Stealth-First UX Engineering: Creating a decoy-to-secure interface that looks completely ordinary while still enabling instant secure transitions. This required careful state management so protection is seamless without exposing user intent. Stealth-First UX Engineering: Creating a decoy-to-secure interface that looks completely ordinary while still enabling instant secure transitions. This required careful state management so protection is seamless without exposing user intent.

### Accomplishments we're proud of

Built a stealth-first safety app designed to protect users in real-world emergencies Achieved sub-second semantic threat detection and autonomous escalation Implemented decoy-to-secure mode switching for discreet privacy protection Enabled always-on on-device audio and vision monitoring without user input Integrated OpenAI, Supabase, and Twilio into a complete end-to-end safety platform

### What we learned

Designing stealth-first UX where safety depends on seamless "decoy-to-secure" transitions Deploying real-time on-device computer vision for autonomous privacy protection and auto-locking Building low-latency continuous audio threat detection pipelines for high-stakes environments Making AI systems trustworthy by combining structured LLM reasoning with deterministic escalation logic and human-in-the-loop control Designing a reliable full-stack architecture

### What's next

We can bolster the app by developing the following features: Improving AI reliability using personalized risk calibration and stronger safeguards against false positives Adding end-to-end encryption and a hardened vault for evidence storage and legal documentation Building a trusted network mode where verified contacts or advocates can receive real-time escalation updates Upgrading emergency workflows, including silent check-ins, timed safe-word triggers, and automatic location sharing

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 48 recognized source files, 157 KB.
- Swift (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Supabase (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (60 of 60)

```
.DS_Store
AISupportViewModel.swift
AudioPlayer.swift
AudioRecorder 2.swift
AudioRecorder.swift
FacePresenceMonitor.swift
ForumsPlaceholderView.swift
ImagePicker.swift
KeyboardObserver.swift
MessageComposer.swift
treehacks.xcodeproj/ChatAPIClient.swift
treehacks.xcodeproj/DrKhanChatViewModel.swift
treehacks.xcodeproj/project.pbxproj
treehacks.xcodeproj/project.xcworkspace/contents.xcworkspacedata
treehacks.xcodeproj/project.xcworkspace/xcuserdata/jacobschuster.xcuserdatad/UserInterfaceState.xcuserstate
treehacks.xcodeproj/xcuserdata/arnauldmartinez.xcuserdatad/xcschemes/xcschememanagement.plist
treehacks.xcodeproj/xcuserdata/jacobschuster.xcuserdatad/xcschemes/xcschememanagement.plist
treehacks/.DS_Store
treehacks/App/AppRouter.swift
treehacks/App/Appstate.swift
treehacks/App/Secrets.swift
treehacks/Assets.xcassets/AccentColor.colorset/Contents.json
treehacks/Assets.xcassets/AppIcon.appiconset/Contents.json
treehacks/Assets.xcassets/background_image.imageset/Contents.json
treehacks/Assets.xcassets/Contents.json
treehacks/ContentView.swift
treehacks/DesignSystem/Components/AppCard.swift
treehacks/DesignSystem/Theme.swift
treehacks/Features/Agent/Views/AISupportView.swift
treehacks/Features/Agent/Views/OpenAIChatService.swift
treehacks/Features/DecoyNotes/ViewModels/DecoyNotesViewModel.swift
treehacks/Features/DecoyNotes/Views/DecoyNotesListView.swift
treehacks/Features/DecoyNotes/Views/NewNoteView.swift
treehacks/Features/DecoyNotes/Views/NoteEditorView.swift
treehacks/Features/Documents/Incident.swift
treehacks/Features/Documents/IncidentsStore.swift
treehacks/Features/Documents/Models/SecureEvent.swift
treehacks/Features/Documents/ViewModels/SecureEventsViewModel.swift
treehacks/Features/Documents/Views/DocumentsView.swift
treehacks/Features/Documents/Views/EventEditorView.swift
treehacks/Features/Documents/Views/NewEventView.swift
treehacks/Features/Emergency/Views/EmergencyView.swift
treehacks/Features/Forum/Views/ForumsView.swift
treehacks/Features/Onboarding/Views/SetPasscodeView.swift
treehacks/Features/ReviewerChats/Views/ChatsView.swift
treehacks/Features/ReviewerChats/Views/ChatsViewModel.swift
treehacks/Features/SecureHome/Views/SecureHomeView.swift
treehacks/Features/ThreatMonitoring/AmplitudeGate.swift
treehacks/Features/ThreatMonitoring/AudioRingBuffer.swift
treehacks/Features/ThreatMonitoring/IncidentRecorder.swift
treehacks/Features/ThreatMonitoring/OpenAIThreatAnalyzer.swift
treehacks/Features/ThreatMonitoring/RiskModel.swift
treehacks/Features/ThreatMonitoring/RMSGate.swift
treehacks/Features/ThreatMonitoring/ThreatAnalysisService.swift
treehacks/Features/ThreatMonitoring/ThreatAssessment.swift
treehacks/Features/ThreatMonitoring/ThreatClassifier.swift
treehacks/Features/ThreatMonitoring/ThreatMonitor.swift
treehacks/Features/ThreatMonitoring/ThreatMonitorTestView.swift
treehacks/Info.plist
treehacks/treehacksApp.swift
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update Secrets.swift
- Finished everything
- Added emergency and forum pages
- Finished chats page and added Dr Khan chat with external server
- finished AI support page and started chat page (without server response)
- Merge remote-tracking branch 'refs/remotes/origin/audio_detection'
- audio detection
- updated secure document page to include image and audio recordings
- created secure document page with only text
- updated landing page
- minimal landing page
- basic working notes ui and pin to secure mode
- basic landing page and secure notes
- basic landing page and secure notes page
- basic notes app
- jacob's first commit!
- test change
- Initial Commit

## Key source files (fetched from GitHub, selected and truncated for size)

### KeyboardObserver.swift

```swift
// Intentionally removed. This file existed temporarily during keyboard bug investigation.
// You can now delete it from the project (Remove Reference) and from disk.
// Keeping it with no compiled code ensures builds succeed until the reference is removed.

#if false
import SwiftUI
import Combine

@MainActor
final class KeyboardObserver: ObservableObject {
    @Published var keyboardHeight: CGFloat = 0
}
#endif

```

### AISupportViewModel.swift

```swift
import Foundation
import Combine

enum ChatRole: String, Codable { case user, assistant }

struct ChatMessage: Identifiable, Codable, Hashable {
    let id: UUID
    let role: ChatRole
    let content: String
    let createdAt: Date
    let imageData: Data?

    init(id: UUID = UUID(), role: ChatRole, content: String, createdAt: Date = Date(), imageData: Data? = nil) {
        self.id = id
        self.role = role
        self.content = content
        self.createdAt = createdAt
        self.imageData = imageData
    }
}

@MainActor
final class AISupportViewModel: ObservableObject {
    @Published private(set) var messages: [ChatMessage] = []

    func startNewChat() {
        messages = []
        // Optional: seed system prompt or intro message
        messages.append(ChatMessage(role: .assistant, content: "Hi! I'm your AI legal assistant. How can I help today?"))
    }

    func sendUser(text: String) {
        messages.append(ChatMessage(role: .user, content: text))

        // Stub a delayed assistant response for now
        Task { @MainActor in
            try? await Task.sleep(nanoseconds: 400_000_000)
            let reply = ChatMessage(role: .assistant, content: "Thanks for your question. I'll review your secure documents context and get back with legal guidance.")
            messages.append(reply)
        }
    }
}


```

### MessageComposer.swift

```swift
import SwiftUI
#if canImport(MessageUI)
import MessageUI
#endif

#if canImport(MessageUI)
struct MessageComposer: UIViewControllerRepresentable {
    let recipients: [String]
    let bodyText: String
    @Environment(\.presentationMode) private var presentationMode

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    func makeUIViewController(context: Context) -> MFMessageComposeViewController {
        let vc = MFMessageComposeViewController()
        vc.messageComposeDelegate = context.coordinator
        vc.recipients = recipients
        vc.body = bodyText
        return vc
    }

    func updateUIViewController(_ uiViewController: MFMessageComposeViewController, context: Context) {}

    final class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
        var parent: MessageComposer

        init(_ parent: MessageComposer) {
            self.parent = parent
        }

        func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}
#else
// Fallback for platforms without MessageUI
struct MessageComposer: View {
    let recipients: [String]
    let bodyText: String
    var body: some View { Text("Messaging not available on this platform.") }
}
#endif

```

### ImagePicker.swift

```swift
import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    let sourceType: UIImagePickerController.SourceType
    let onImagePicked: (Data) -> Void

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = sourceType
        picker.allowsEditing = false
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}

    final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker
        init(parent: ImagePicker) { self.parent = parent }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[.originalImage] as? UIImage,
               let data = image.jpegData(compressionQuality: 0.9) {
                parent.onImagePicked(data)
            }
            picker.dismiss(animated: true)
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true)
        }
    }
}

```

### AudioRecorder.swift

```swift
import Foundation
import AVFoundation

class LegacyAudioRecorderHelper: NSObject, AVAudioRecorderDelegate {
    private var audioRecorder: AVAudioRecorder?
    private var recordingURL: URL?
    private(set) var recordedData: Data?

    func startRecording() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
        try session.setActive(true)

        let tempDir = FileManager.default.temporaryDirectory
        let fileName = UUID().uuidString + ".m4a"
        recordingURL = tempDir.appendingPathComponent(fileName)

        let settings: [String: Any] = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 12000,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]

        guard let recordingURL = recordingURL else { return }

        audioRecorder = try AVAudioRecorder(url: recordingURL, settings: settings)
        audioRecorder?.delegate = self
        audioRecorder?.record()
    }

    func stopRecording() {
        audioRecorder?.stop()
        audioRecorder = nil

        guard let recordingURL = recordingURL else { return }
        recordedData = try? Data(contentsOf: recordingURL)

        try? AVAudioSession.sharedInstance().setActive(false)
    }
}

```

### AudioPlayer.swift

```swift
import Foundation
import AVFoundation
import Combine

@MainActor
final class AudioPlayer: NSObject, ObservableObject, AVAudioPlayerDelegate {
    @Published private(set) var isPlaying: Bool = false
    @Published private(set) var currentID: String?

    private var player: AVAudioPlayer?

    func play(url: URL, id: String) {
        if currentID == id && isPlaying {
            stop()
            return
        }
        stop()
        do {
            let p = try AVAudioPlayer(contentsOf: url)
            p.delegate = self
            p.prepareToPlay()
            p.play()
            self.player = p
            self.isPlaying = true
            self.currentID = id
        } catch {
            print("AudioPlayer failed to play url:", error)
        }
    }

    func play(data: Data, id: String) {
        if currentID == id && isPlaying {
            stop()
            return
        }
        stop()
        do {
            let p = try AVAudioPlayer(data: data)
            p.delegate = self
            p.prepareToPlay()
            p.play()
            self.player = p
            self.isPlaying = true
            self.currentID = id
        } catch {
            print("AudioPlayer failed to play data:", error)
        }
    }

    func stop() {
        player?.stop()
        player = nil
        isPlaying = false
        currentID = nil
    }

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        isPlaying = false
        currentID = nil
        self.player = nil
    }
}

```

### ForumsPlaceholderView.swift

```swift
import SwiftUI

struct ForumsPlaceholderView: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForumTile(title: "Lawyer", description: "Contact the best lawyers nearby.")
                ForumTile(title: "Support", description: "Connect with people who have fought similar battles and won.")
                ForumTile(title: "Babysitter", description: "Book a babysitter to keep your children safe.")
            }
            .padding(.horizontal, 18)
            .padding(.top, 90)
            .padding(.bottom, 60)
        }
        .themedBackground()
        .navigationTitle("Forums")
        .navigationBarTitleDisplayMode(.inline)
    }
}

private struct ForumTile: View {
    let title: String
    let description: String

    var body: some View {
        AppCard {
            HStack(alignment: .center, spacing: 12) {
                VStack(alignment: .leading, spacing: 8) {
                    Text(title)
                        .font(.system(size: 17, weight: .semibold))
                        .foregroundStyle(Theme.text)

                    Text(description)
                        .font(.system(size: 14))
                        .foregroundStyle(Theme.sub)
                }
                Spacer(minLength: 8)
                Text(">")
                    .font(.system(size: 24, weight: .bold))
                    .foregroundStyle(Theme.sub)
                    .accessibilityHidden(true)
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.vertical, 8)
        }
    }
}

#Preview {
    NavigationStack { ForumsPlaceholderView() }
}

```

### AudioRecorder 2.swift

```swift
import Foundation
import Combine
import AVFoundation

@MainActor
final class AudioRecorder: NSObject, ObservableObject, AVAudioRecorderDelegate {
    @Published private(set) var isRecording: Bool = false

    private var recorder: AVAudioRecorder?
    private var tempURL: URL?

    func requestPermission() async -> Bool {
        await withCheckedContinuation { continuation in
            AVAudioSession.sharedInstance().requestRecordPermission { granted in
                continuation.resume(returning: granted)
            }
        }
    }

    func start() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
        try session.setActive(true)

        let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())
        let url = tempDir.appendingPathComponent("rec_\(UUID().uuidString).m4a")
        self.tempURL = url

        let settings: [String: Any] = [
            AVFormatIDKey: kAudioFormatMPEG4AAC,
            AVSampleRateKey: 44100,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]

        let recorder = try AVAudioRecorder(url: url, settings: settings)
        recorder.delegate = self
        recorder.prepareToRecord()
        recorder.record()
        self.recorder = recorder
        self.isRecording = true
    }

    func stop() -> Data? {
        guard let recorder = recorder else { return nil }
        recorder.stop()
        self.isRecording = false
        self.recorder = nil
        guard let url = tempURL else { return nil }
        let data = try? Data(contentsOf: url)
        try? FileManager.default.removeItem(at: url)
        self.tempURL = nil
        return data
    }
}


```

### FacePresenceMonitor.swift

```swift
import Foundation
import AVFoundation
import Vision
import Combine

@MainActor
final class FacePresenceMonitor: NSObject, ObservableObject {
    @Published private(set) var facePresent: Bool = true

    private let session = AVCaptureSession()
    private let output = AVCaptureVideoDataOutput()
    private let videoQueue = DispatchQueue(label: "face.presence.video.queue")

    private var lastFaceTime: Date = .distantPast
    private var watchdogTimer: Timer?

    func start() {
        // Request camera access, then configure and start
        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
            guard let self = self else { return }
            DispatchQueue.main.async {
                guard granted else {
                    // If no permission, treat as not present so caller can decide what to do
                    self.facePresent = false
                    return
                }
                self.configureSessionIfNeeded()
                if !self.session.isRunning {
                    self.videoQueue.async { self.session.startRunning() }
                }
                self.startWatchdog()
            }
        }
    }

    func stop() {
        watchdogTimer?.invalidate()
        watchdogTimer = nil
        if session.isRunning {
            videoQueue.async { [weak self] in self?.session.stopRunning() }
        }
    }

    private func configureSessionIfNeeded() {
        guard session.inputs.isEmpty else { return }
        session.beginConfiguration()
        session.sessionPreset = .medium

        // Front camera
        guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front),
              let input = try? AVCaptureDeviceInput(device: device),
              session.canAddInput(input) else {
            session.commitConfiguration()
            return
        }
        session.addInput(input)

        output.alwaysDiscardsLateVideoFrames = true
        output.setSampleBufferDelegate(self, queue: videoQueue)
        if session.canAddOutput(output) { session.addOutput(output) }

        session.commitConfiguration()
    }

    private func startWatchdog() {
        watchdogTimer?.invalidate()
        lastFaceTime = Date() // assume present at start to avoid immediate lock
        watchdogTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
            guard let self = self else { return }
            let elapsed = Date().timeIntervalSince(self.lastFaceTime)
            let present = elapsed < 2.0 // 2s grace period
            if present != self.facePresent {
                self.facePresent = present
            }
        }
    }
}

extension FacePresenceMonitor: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        let request = VNDetectFaceRectanglesRequest { [weak self] req, _ in
            guard let self = self else { return }
            let faces = (req.results as? [VNFaceObservation]) ?? []
            if !faces.isEmpty {
                // Update last seen time on any face
                self.lastFaceTime = Date()
            }
        }
        // Front camera in portrait is typically leftMirrored
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .leftMirrored)
        try? handler.perform([request])
    }
}

```

### treehacks/treehacksApp.swift

```swift
import SwiftUI

@main
struct TreehacksApp: App {
    @StateObject private var appState = AppState()
    @StateObject private var incidentsStore = IncidentsStore()

    var body: some Scene {
        WindowGroup {
            AppRouter()
                .environmentObject(appState)
                .environmentObject(incidentsStore)
        }
    }
}

```

[38 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]