Skip to main content
Ora Browser is built with a modular architecture using Swift 5.9, SwiftUI, and AppKit for macOS 15.0+. The codebase is organized for clarity, maintainability, and scalability.

Repository Overview

browser/
├── ora/                    # Main application source code
├── scripts/                # Build and setup scripts
├── project.yml            # XcodeGen project configuration
├── .swiftlint.yml         # Linting rules
├── .swiftformat           # Formatting configuration
└── CONTRIBUTING.md        # Development guidelines
The project uses XcodeGen to generate the Xcode project. Edit project.yml and run xcodegen to regenerate.

Core Directory Structure

The ora/ directory contains all application code organized by purpose:
ora/
├── OraRoot.swift          # Root view and app initialization
├── oraApp.swift           # App entry point and configuration
├── OraCommands.swift      # Menu bar commands
├── Common/                # Shared utilities and extensions
├── Models/                # Data models (SwiftData)
├── Services/              # Business logic and managers
├── Modules/               # Feature-specific UI modules
├── UI/                    # Reusable UI components
├── Resources/             # Assets and data files
└── Assets.xcassets       # Images and icons

Application Entry Points

Main Application Files

1

oraApp.swift

The @main entry point that defines the app structure:
oraApp.swift
@main
struct OraApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup(id: "normal") {
            OraRoot()
                .frame(minWidth: 500, minHeight: 360)
        }
        .defaultSize(width: 1440, height: 900)
        .windowStyle(.hiddenTitleBar)
        
        WindowGroup("Private", id: "private") {
            OraRoot(isPrivate: true)
        }
        
        Settings {
            SettingsContentView()
        }
    }
}
Defines two window types:
  • Normal browsing: Standard persistent tabs
  • Private browsing: In-memory storage only
2

AppState (oraApp.swift:59)

Central observable state for the application:
oraApp.swift
class AppState: ObservableObject {
    @Published var showLauncher: Bool = false
    @Published var launcherSearchText: String = ""
    @Published var showFinderIn: UUID?
    @Published var isFloatingTabSwitchVisible: Bool = false
    @Published var isFullscreen: Bool = false
}
3

OraRoot.swift

Root view component that initializes core managers:
OraRoot.swift
struct OraRoot: View {
    @StateObject private var appState = AppState()
    @StateObject private var tabManager: TabManager
    @StateObject private var historyManager: HistoryManager
    @StateObject private var downloadManager: DownloadManager
    @StateObject private var mediaController: MediaController
    @StateObject private var sidebarManager = SidebarManager()
    @StateObject private var toolbarManager = ToolbarManager()
    
    var body: some View {
        BrowserView()
            .environmentObject(appState)
            .environmentObject(tabManager)
            // ... other environment objects
    }
}

Common Directory

Shared utilities, extensions, and constants used throughout the app:
Common/
├── Constants/
│   ├── AppEvents.swift           # NotificationCenter events
│   ├── ContainerConstants.swift  # Container configurations
│   ├── KeyboardShortcuts.swift   # Keyboard shortcut definitions
│   └── Theme.swift               # App theme and colors
├── Extensions/
│   ├── Color+Hex.swift           # Hex color support
│   ├── View+Modifiers.swift      # Custom SwiftUI modifiers
│   ├── View+Shortcuts.swift      # Keyboard shortcut extensions
│   └── NSWindow+Extensions.swift # Window utilities
├── Representables/
│   ├── WindowAccessor.swift      # NSWindow access from SwiftUI
│   ├── WindowReader.swift        # Window state reading
│   └── KeyCaptureView.swift      # Keyboard event handling
├── Shapes/
│   └── ConditionallyConcentricRectangle.swift
└── Utils/
    ├── SettingsStore.swift       # UserDefaults wrapper
    ├── TabUtils.swift            # Tab helper functions
    └── ClipboardUtils.swift      # Clipboard operations

Theme System

The theme system provides consistent styling:
Theme.swift
struct Theme: Equatable {
    let colorScheme: ColorScheme
    
    var primary: Color { Color(hex: "#f3e5d6") }
    var primaryDark: Color { Color(hex: "#141414") }
    var accent: Color { Color(hex: "#FF5F57") }
    var background: Color {
        colorScheme == .dark ? Color(hex: "#0F0E0E") : .white
    }
    // ... more theme colors
}

// Usage
@Environment(\.theme) private var theme
Text("Hello").foregroundColor(theme.foreground)

Models Directory

SwiftData models for persistent storage:
Models/
├── Tab.swift              # Tab state and WebKit integration
├── TabContainer.swift     # Tab grouping and spaces
├── History.swift          # Browsing history entries
├── Download.swift         # Download tracking
├── Folder.swift           # Tab organization folders
├── SearchEngine.swift     # Search engine configurations
└── Keyboard.swift         # Custom keyboard shortcuts

Tab Model

The core Tab model (463 lines) integrates SwiftData persistence with WebKit:
Tab.swift
@Model
class Tab: ObservableObject, Identifiable {
    // MARK: - Persisted Properties
    var id: UUID
    var url: URL
    var title: String
    var favicon: URL?
    var type: TabType  // .pinned, .fav, .normal
    var order: Int
    var backgroundColorHex: String = "#000000"
    
    // MARK: - Transient Properties (not persisted)
    @Transient var webView: WKWebView = WKWebView()
    @Transient @Published var isLoading: Bool = false
    @Transient @Published var backgroundColor: Color = .black
    @Transient var navigationDelegate: WebViewNavigationDelegate?
    @Transient var isPrivate: Bool = false
    
    // MARK: - Relationships
    @Relationship(inverse: \TabContainer.tabs) var container: TabContainer
    
    // MARK: - Lifecycle
    func restoreTransientState(
        historyManager: HistoryManager,
        downloadManager: DownloadManager,
        tabManager: TabManager,
        isPrivate: Bool
    ) {
        // Recreate WKWebView and delegates after deserialization
    }
}
@Transient properties are not persisted to disk. This is crucial for WKWebView which cannot be serialized.

Services Directory

Business logic, state management, and system integration:
Services/
├── TabManager.swift                  # Tab lifecycle and coordination
├── HistoryManager.swift              # Browsing history
├── DownloadManager.swift             # Download handling
├── MediaController.swift             # Media playback state
├── SearchEngineService.swift         # Search engine logic
├── FaviconService.swift              # Favicon fetching and caching
├── PrivacyService.swift              # Cache/cookie clearing
├── UpdateService.swift               # Sparkle auto-updates
├── AppearanceManager.swift           # Light/dark mode
├── SidebarManager.swift              # Sidebar state
├── ToolbarManager.swift              # Toolbar visibility
├── CustomKeyboardShortcutManager.swift
├── DefaultBrowserManager.swift       # System default browser
├── WebViewNavigationDelegate.swift   # WebKit navigation
├── TabScriptHandler.swift            # JavaScript injection
├── KeyModifierListener.swift         # Global keyboard events
└── Importer.swift                    # Import from other browsers

Service Architecture

Managers coordinate related functionality:
TabManager.swift
class TabManager: ObservableObject {
    @Published var tabs: [Tab] = []
    @Published var activeTab: Tab?
    
    private let modelContext: ModelContext
    let mediaController: MediaController
    
    func openTab(url: URL, focusAfterOpening: Bool = true) {
        // Create and configure new tab
    }
    
    func closeActiveTab() {
        // Handle tab closure with history
    }
    
    func selectTabAtIndex(_ index: Int) {
        // Switch to specific tab
    }
}

Modules Directory

Feature-specific UI organized by functionality:
Modules/
├── Browser/              # Main browser view and split view
│   ├── BrowserView.swift
│   ├── BrowserSplitView.swift
│   ├── BrowserContentContainer.swift
│   └── FloatingSidebarOverlay.swift
├── Sidebar/              # Tab list and navigation
│   ├── TabList/
│   └── BottomOption/
├── URLBar/               # Address bar and controls
├── Launcher/             # Quick launcher (Cmd+L)
│   ├── Main/
│   └── Suggestions/
├── Settings/             # Preferences UI
│   └── Sections/
│       ├── GeneralSettingsView.swift
│       ├── SearchEngineSettingsView.swift
│       ├── ShortcutsSettingsView.swift
│       ├── PrivacySecuritySettingsView.swift
│       └── SpacesSettingsView.swift
├── TabSwitch/            # Ctrl+Tab switcher
├── Find/                 # In-page search (Cmd+F)
├── Player/               # Media controls
├── EmojiPicker/          # Emoji selector for tabs
├── Importer/             # Browser import wizard
└── SplitView/            # Split screen browsing

Module Organization

Each module is self-contained with its views and logic:
1

Browser Module

Core browsing interface:
BrowserView.swift
struct BrowserView: View {
    @EnvironmentObject var appState: AppState
    @EnvironmentObject var tabManager: TabManager
    @EnvironmentObject var sidebarManager: SidebarManager
    
    var body: some View {
        BrowserSplitView()
            .overlay(alignment: .top) {
                if !toolbarManager.isToolbarHidden {
                    URLBar()
                }
            }
    }
}
2

Settings Module

Multi-section preferences organized by category:
SettingsContentView.swift
struct SettingsContentView: View {
    var body: some View {
        TabView {
            GeneralSettingsView()
                .tabItem { Label("General", systemImage: "gear") }
            
            SearchEngineSettingsView()
                .tabItem { Label("Search", systemImage: "magnifyingglass") }
            
            ShortcutsSettingsView()
                .tabItem { Label("Shortcuts", systemImage: "command") }
        }
    }
}

UI Directory

Reusable UI components used across modules:
UI/
├── Buttons/
│   ├── OraButton.swift           # Standard button styles
│   └── URLBarButton.swift        # Address bar buttons
├── Dialog/
│   ├── DialogManager.swift       # Modal dialog system
│   ├── DialogModel.swift         # Dialog configuration
│   └── DialogView.swift          # Dialog presentation
├── Toast/
│   ├── ToastManager.swift        # Toast notifications
│   └── ToastView.swift
├── Icons/
│   └── (System icon wrappers)
├── Inputs/
│   └── (Custom text fields)
├── Modifiers/
│   └── (Reusable view modifiers)
├── TabItem.swift                 # Tab visual representation
├── FavTabItem.swift              # Favorite tab styling
├── WebView.swift                 # WebKit SwiftUI wrapper
├── URLBar.swift                  # Address bar component
├── WindowControls.swift          # Traffic light buttons
└── StatusPageView.swift          # Error pages

Component Examples

Centralized modal dialogs:
DialogManager.swift
class DialogManager: ObservableObject {
    @Published var dialogs: [DialogModel] = []
    
    func confirm(
        title: String,
        message: String,
        confirmLabel: String = "OK",
        variant: DialogVariant = .default,
        onConfirm: @escaping () -> Void,
        onCancel: (() -> Void)? = nil
    ) {
        let dialog = DialogModel(
            title: title,
            message: message,
            confirmLabel: confirmLabel,
            variant: variant,
            onConfirm: onConfirm,
            onCancel: onCancel
        )
        dialogs.append(dialog)
    }
}

// Usage from OraRoot.swift:148
dialogManager.confirm(
    title: "Quit Ora?",
    message: "Are you sure you want to quit?",
    confirmLabel: "Quit",
    variant: .destructive,
    onConfirm: { NSApp.terminate(nil) }
)

Project Configuration

XcodeGen (project.yml)

The project is generated from project.yml:
project.yml
name: Ora
options:
  bundleIdPrefix: com.orabrowser

packages:
  Sparkle:
    url: https://github.com/sparkle-project/Sparkle
    from: 2.6.0
  Inject:
    url: https://github.com/krzysztofzablocki/Inject
    from: 1.5.2
  FaviconFinder:
    url: https://github.com/will-lumley/FavIconFinder
    exactVersion: 5.1.5

targets:
  ora:
    type: application
    platform: "macOS"
    deploymentTarget: "15.0"
    sources:
      - path: ora
    dependencies:
      - package: Sparkle
      - package: Inject
      - package: FaviconFinder
Never edit Ora.xcodeproj directly. Always modify project.yml and regenerate:
xcodegen

Build Configurations

Development build with hot reloading:
Debug:
  ASSETCATALOG_COMPILER_APPICON_NAME: OraIconDev
  CODE_SIGN_ENTITLEMENTS: ora/ora-debug.entitlements
  EMIT_FRONTEND_COMMAND_LINES: YES  # For InjectionIII
  OTHER_LDFLAGS: "-Xlinker -interposable"  # Hot reload support
Debug builds load InjectionIII for hot reloading:
oraApp.swift
#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
#endif

Architecture Patterns

State Management

1

Environment Objects

Share state down the view hierarchy:
// In OraRoot.swift
BrowserView()
    .environmentObject(tabManager)
    .environmentObject(historyManager)

// In any child view
@EnvironmentObject var tabManager: TabManager
2

Published Properties

Trigger view updates on changes:
class TabManager: ObservableObject {
    @Published var tabs: [Tab] = []
    @Published var activeTab: Tab?
}
3

NotificationCenter

System-wide events (see AppEvents.swift):
// Post event
NotificationCenter.default.post(name: .showLauncher, object: window)

// Observe event
NotificationCenter.default.addObserver(
    forName: .showLauncher,
    object: nil,
    queue: .main
) { note in
    appState.showLauncher.toggle()
}

Persistence Strategy

SwiftData

Primary storage for tabs, history, downloads
@Model class Tab { }
ModelContext.save()

UserDefaults

Settings and preferences
SettingsStore.shared.autoUpdateEnabled

File System

Favicon cache, downloads
FileManager.default.faviconDirectory

In-Memory

Private browsing mode
OraRoot(isPrivate: true)

Hot Reloading Support

Development workflow uses InjectionIII for instant SwiftUI updates:
OraRoot.swift
@ObserveInjection var inject

var body: some View {
    BrowserView()
        .enableInjection()
}
See Hot Reloading Guide for setup instructions.

Dependencies

External packages managed via Swift Package Manager:

Sparkle

Auto-update frameworkVersion: 2.6.0+

Inject

Hot reloading support (debug only)Version: 1.5.2+

FaviconFinder

Favicon discovery and downloadVersion: 5.1.5

Key Files Reference

FileLine CountPurpose
oraApp.swift117App entry, window setup
OraRoot.swift284Root view, manager init
OraCommands.swift~200Menu bar commands

Best Practices

1

Follow the Structure

Place new files in the appropriate directory:
  • Models: Data structures with SwiftData
  • Services: Business logic and state management
  • Modules: Feature-specific views
  • UI: Reusable components
  • Common: Shared utilities
2

Maintain Modularity

Keep modules independent:
// Good - inject dependencies
struct BrowserView: View {
    @EnvironmentObject var tabManager: TabManager
}

// Bad - tight coupling
struct BrowserView: View {
    let tabManager = TabManager.shared  // Avoid singletons
}
3

Use XcodeGen

Always regenerate project after structure changes:
# Edit project.yml first, then:
xcodegen

Code Style Guide

Formatting and conventions

Contributing

How to contribute code

Setup Guide

Development environment setup

Architecture

High-level design patterns

Build docs developers (and LLMs) love