Meshtastic/
├── MeshtasticApp.swift # @main App struct
├── MeshtasticAppDelegate.swift # UIApplicationDelegate (SiriKit)
├── AppState.swift # @EnvironmentObject root state
├── Accessory/ # BLE/TCP/serial connectivity
├── API/ # REST API helpers
├── AppIntents/ # Siri / Shortcuts intents
├── CarPlay/ # CarPlay scene
├── Enums/ # Shared enumerations
├── Extensions/ # Swift extensions (Logger, Date, String…)
├── Helpers/ # Utility types (no UI)
├── Intents/ # INIntent handlers
├── Measurement/ # Unit/measurement formatting
├── Model/ # @Model SwiftData types
├── Persistence/ # PersistenceController, MeshPackets actor
├── Resources/ # Assets, docs bundle, Info.plist
├── Router/ # Router + NavigationState
├── Tips/ # TipKit tips
└── Views/ # SwiftUI views
├── Bluetooth/ # BLE connect view
├── Map/ # Map + overlay views
├── Messages/ # Channel + DM views
├── Nodes/ # Node list + detail
└── Settings/ # All settings views
MeshtasticProtobufs/ # Swift Package wrapping generated protobufs
MeshtasticTests/ # Test target (Swift Testing)
scripts/ # Build and utility scripts
specs/ # Feature specs (speckit workflow)
Meshtastic.xcodeproj is generated from project.yml by XcodeGen rather than hand-maintained. project.yml is the source of truth for targets, build settings, and schemes; the generated .xcodeproj is still committed so the repo builds without installing XcodeGen first.
Most source directories — including Meshtastic/Views/, Meshtastic/Model/, Meshtastic/Accessory/, MeshtasticTests/, Shared/, and Widgets/ — are configured as type: syncedFolder (Xcode 16 synchronized groups). A new file added under one of these directories is picked up automatically; there is no membership checkbox or pbxproj entry to add.
If you change project.yml (or .xcodegen-version), regenerate the project and commit the result:
xcodegen generate
The xcodegen-drift.yml CI check regenerates the project on every PR that touches project.yml, .xcodegen-version, Meshtastic.xcodeproj/**, or any .swift file, and fails if the committed project doesn't exactly match — this is what keeps the generated project honest as the source of truth stays in project.yml. XcodeGen's output is version-sensitive, so .xcodegen-version pins an exact release; install that exact version (not whatever Homebrew currently carries) before regenerating locally.
Keep sibling group names unique. XcodeGen 2.46.0 does not produce a stable order when two root groups have the same display name, which makes the drift check intermittent. Shared tvOS sources used by the visionOS target therefore live under the distinct virtual group Meshtastic TV (Vision shared) rather than a second root group named Meshtastic TV.
| File | Purpose |
|---|---|
project.yml |
XcodeGen project specification — the source of truth for Meshtastic.xcodeproj |
Router/Router.swift |
Central navigation controller (@MainActor) |
Router/NavigationState.swift |
Per-tab navigation state enums |
Extensions/Logger.swift |
Typed OSLog loggers for all subsystems |
Persistence/PersistenceController.swift |
SwiftData ModelContainer setup |
Model/MeshtasticSchema.swift |
VersionedSchema + SchemaMigrationPlan |
Accessory/Accessory Manager/AccessoryManager.swift |
BLE/TCP manager root class |
Large manager classes are split into +Extension files grouped by concern:
// AccessoryManager.swift — properties and init only
// AccessoryManager+Connect.swift — connection lifecycle
// AccessoryManager+ToRadio.swift — outbound packet methods
// AccessoryManager+FromRadio.swift — inbound packet handling
Follow the same pattern when adding new subsystems to AccessoryManager or other large classes.
All logging uses typed Logger instances from Meshtastic/Extensions/Logger.swift. Never use print().
Logger.mesh.debug("Packet received: \(packet.id)")
Logger.transport.error("BLE write failed: \(error)")
Available categories: .admin, .data, .docs, .mesh, .mqtt, .radio, .services, .statistics, .transport, .tak
Views are in Meshtastic/Views/. Each major feature has its own subdirectory. The root ContentView hosts a TabView keyed on NavigationState.
Views that need connectivity inject @EnvironmentObject var bleManager: BLEManager (legacy name; newer code uses AccessoryManager). Views that need navigation inject @EnvironmentObject var router: Router.
All user-visible strings must use String(localized:) or LocalizedStringKey. The source strings file is Localizable.xcstrings in the project root.