# Key Modules in BitChat's Application Layer: Runtime, Models, and Services

> Explore BitChat's application layer modules: AppRuntime, view models like ConversationStore, and services. Understand its architecture for efficient development.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-21

---

**BitChat's application layer is organized around a composition root (`AppRuntime`) that injects feature-scoped view models—such as `ConversationStore`, `ChatViewModel`, and `PublicChatModel`—to separate domain logic from UI state and networking services.**

BitChat, an open-source peer-to-peer messaging application maintained by permissionlesstech, implements a clean, layered architecture that separates runtime orchestration from domain models and view-specific state. Understanding the key modules in BitChat's application layer is essential for developers contributing to the SwiftUI codebase or integrating with its Nostr and mesh networking protocols. Each module is designed to eliminate the historic anti-pattern of a single global environment object, improving testability and maintainability.

## Runtime Orchestration with AppRuntime

The `AppRuntime` class serves as the **composition root** for the entire application. Defined in [`bitchat/App/AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/AppRuntime.swift), this `ObservableObject` owns the app-wide state and wires together all feature models, the `ConversationStore`, the `ChatViewModel`, and runtime services including Tor, notification handling, and shared-content import.

`AppRuntime` also publishes typed application events via `AppEventStream`, allowing decoupled components to react to lifecycle and networking changes. When the SwiftUI app launches, the runtime is injected into the environment and started automatically via `ContentView.onAppear`.

```swift
import Bitchat

// In the SwiftUI App entry point
@main
struct BitchatApp: App {
    @StateObject private var runtime = AppRuntime()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(runtime)                // inject the whole runtime
        }
    }
}

```

The `AppRuntime` composition root wires together all feature models and starts the networking stack when `runtime.start()` is called.

## Domain State Management with ConversationStore

`ConversationStore` acts as the **single source of truth** for all conversation-level data. Located in [`bitchat/ViewModels/ConversationStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ConversationStore.swift), it centralizes public-channel timelines, private-inbox state, selected conversation tracking, unread counts, and message de-duplication.

All feature models read from and mutate this store via a well-defined intent API. The store exposes reactive publishers—such as `publicMessages`—that SwiftUI views subscribe to for real-time updates.

```swift
// In a view that shows the public chat timeline
struct PublicChatList: View {
    @EnvironmentObject var runtime: AppRuntime
    @State private var messages: [Message] = []

    var body: some View {
        List(messages) { msg in
            Text(msg.body)
        }
        .onReceive(runtime.conversations.publicMessages) { msgs in
            self.messages = msgs
        }
    }
}

```

*`ConversationStore` publishes `publicMessages` (a `Publisher<[Message]>`) that UI components subscribe to.*

## UI Bridge and Feature-Specific Models

### ChatViewModel as the Domain Coordinator

`ChatViewModel` functions as the primary bridge between domain logic and the UI. Implemented in [`bitchat/ViewModels/ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModel.swift), it handles message sending, lifecycle events, and delegates heavy-lifting to coordinators like `ChatPeerListCoordinator` and `ChatOutgoingCoordinator`. It remains intentionally thin, with most state management deferred to the runtime-owned models.

### Feature-Scoped View Models

BitChat employs **feature-scoped models** that are injected by `AppRuntime`, eliminating the monolithic global state pattern:

- **`PublicChatModel`** ([`bitchat/ViewModels/PublicChatModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/PublicChatModel.swift)): Manages the public chat view, providing paginated message lists and reacting to broadcast events.
- **`PrivateInboxModel`** ([`bitchat/ViewModels/PrivateInboxModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/PrivateInboxModel.swift)): Represents the private-inbox state, including DM lists and unread counts, reading directly from `ConversationStore`.
- **`PrivateConversationModel`** ([`bitchat/ViewModels/PrivateConversationModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/PrivateConversationModel.swift)): Handles selected direct-message conversations, including peer identity resolution, encryption state, and favorite toggling.
- **`ConversationUIModel`** ([`bitchat/ViewModels/ConversationUIModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ConversationUIModel.swift)): Centralizes composer state, autocomplete, message formatting, and row-level actions, keeping the UI decoupled from `ChatViewModel` logic.
- **`LocationChannelsModel`** ([`bitchat/ViewModels/LocationChannelsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/LocationChannelsModel.swift)): Adapts the location-presence subsystem, exposing geohash-based channels and mesh-only presence data.
- **`PeerListModel`** ([`bitchat/ViewModels/PeerListModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/PeerListModel.swift)): Supplies the peer-list UI with live counts and routing information, consuming `LocationChannelsModel` rather than accessing the location manager directly.

Sending a message through the public chat model demonstrates this delegation pattern:

```swift
// Inside a SwiftUI view that has `@EnvironmentObject var runtime: AppRuntime`
Button("Send") {
    let text = "Hello, mesh world!"
    // The public chat model forwards the intent to the ChatViewModel.
    runtime.publicChatModel.sendMessage(text)
}

```

*`PublicChatModel` calls `ChatViewModel.sendMessage(_:to:)` with the canonical `ConversationID` for the public channel.*

## Nostr Protocol Integration

The Nostr layer provides decentralized identity and relay functionality through three key modules:

- **`NostrIdentityBridge`** ([`bitchat/Nostr/NostrIdentityBridge.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrIdentityBridge.swift)): Bridges the app's internal identity representation with Nostr protocol specifics (npub/nostr keys), used by `AppRuntime` and various Nostr services.
- **`NostrRelayManager`** ([`bitchat/Nostr/NostrRelayManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrRelayManager.swift)): Manages relay connections, handling reconnection logic, proof-of-work (PoW), and inbound/outbound event routing.
- **`NostrProtocol`** ([`bitchat/Nostr/NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrProtocol.swift)): Implements the low-level Nostr protocol, including JSON-RPC subscription handling and event publishing.

Accessing relay functionality follows this pattern:

```swift
// Example: fetch the latest Nostr events for the user's identity
func fetchLatestNostrEvents() async throws -> [NostrEvent] {
    let bridge = runtime.idBridge
    guard let identity = try bridge.getCurrentNostrIdentity() else { return [] }
    let relayMgr = runtime.chatViewModel.nostrRelayManager   // exposed via ChatViewModel
    return try await relayMgr.fetchEvents(for: identity.pubkey)
}

```

*The `NostrRelayManager` encapsulates the connection and request logic; the app merely calls its high-level API.*

## Mesh Networking and Bluetooth Transport

For local mesh communication, BitChat integrates the **Noise protocol** and **Bluetooth Low Energy (BLE)** services:

- **`NoiseProtocol`** ([`bitchat/Noise/NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift)): Provides encrypted mesh networking capabilities.
- **`BLEService`** ([`bitchat/Services/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLEService.swift)): Owns the hot-path components including `BLEOutboundWriteBuffer`, `BLEIngressLinkRegistry`, and `BLEFanoutSelector`, exposing them to the application through the runtime.

These services operate below the application layer but are orchestrated by `AppRuntime` to provide seamless local connectivity alongside Nostr relay connectivity.

## Application Chrome and Shared Content

Two specialized models handle UI-only state and system integration:

- **`AppChromeModel`** ([`bitchat/ViewModels/AppChromeModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/AppChromeModel.swift)): Manages presentation state for nickname editing, fingerprint routing, privacy UI, and screenshot handling. Keeping these flags in a dedicated model prevents bloat in `ChatViewModel`.
- **`SharedContentImportModel`** ([`bitchat/ViewModels/SharedContentImportModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/SharedContentImportModel.swift)): Handles importing text and media from the Share Extension, persisting data via `SharedContentStore`.
- **`BoardAlertsModel`** ([`bitchat/ViewModels/BoardAlertsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/BoardAlertsModel.swift)): Generates system-line alerts for mesh-only and geohash-specific messages using `BoardStore`.

Updating the app chrome is straightforward:

```swift
runtime.appChromeModel.presentNicknameEditor = true

```

*`AppChromeModel` holds UI-only flags such as `presentNicknameEditor`, `showVerificationSheet`, etc., keeping them out of the main `ChatViewModel`.*

## Summary

- **AppRuntime** ([`AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppRuntime.swift)) serves as the composition root, wiring all services and view models together via dependency injection.
- **ConversationStore** centralizes all conversation state, acting as the single source of truth for public channels and private inboxes.
- **Feature-scoped models** (`PublicChatModel`, `PrivateInboxModel`, etc.) replace the monolithic global state pattern, improving testability.
- **NostrIdentityBridge** and **NostrRelayManager** abstract the Nostr protocol, providing high-level APIs for decentralized identity and relay communication.
- **BLEService** and **NoiseProtocol** handle encrypted local mesh networking, with hot-path components managed by the runtime.
- **AppChromeModel** isolates UI-only presentation state from domain logic.

## Frequently Asked Questions

### What is the role of AppRuntime in BitChat's architecture?

`AppRuntime` acts as the **composition root** and sole `ObservableObject` injected into the SwiftUI environment. It instantiates and connects all feature models, stores, and networking services (Tor, BLE, Nostr), and publishes application-wide events via `AppEventStream`. This centralizes dependency management and ensures consistent lifecycle handling across the app.

### How does ConversationStore prevent message duplication?

`ConversationStore` implements message de-duplication logic within its state management layer, acting as the single source of truth for all conversation data. By requiring all feature models to mutate conversation state through its well-defined intent API—rather than directly manipulating local arrays—it ensures that public channel timelines and private inboxes maintain consistent, duplicate-free message histories.

### Why does BitChat use separate models instead of a single ChatViewModel?

The architecture has migrated away from a monolithic `ChatViewModel` (the historical global environment object) toward **feature-scoped models** such as `PublicChatModel` and `PrivateConversationModel`. This separation of concerns improves **testability** by allowing unit tests to inject mock stores, enhances **performance** by limiting view updates to relevant state changes, and increases **maintainability** by isolating domain logic from UI-specific state managed by `AppChromeModel`.

### How does BitChat integrate with Nostr relays?

The application layer accesses Nostr functionality through the **NostrIdentityBridge** for key management and the **NostrRelayManager** for network operations. `NostrRelayManager` handles connection lifecycles, reconnection logic, and event routing, exposing high-level async methods like `fetchEvents(for:)` that the UI layer calls without managing raw JSON-RPC protocols directly.