Loci is a native macOS application built with SwiftUI, AppKit, Swift Package Manager, SQLite, and GRDB. It is designed around one constraint: the user's library remains useful without an account, hosted backend, or LLM provider.
flowchart LR
UI[SwiftUI workspaces] --> Store[LibraryStore]
Extension[Browser extension] --> API[Loopback API]
API --> Store
Store --> DB[(SQLite / GRDB)]
Store --> Assets[Local asset folders]
Store --> Vault[Markdown vault]
Store --> Import[ImportCoordinator]
Import --> Extract[OCR and document extraction]
Import --> Web[Web snapshots]
Vault --> LocalSearch[Local vault search]
Vault -. optional .-> LLM[OpenRouter or Ollama]
OAuth[X OAuth with PKCE] --> Store
Telemetry[Opt-in telemetry] -. aggregate events only .-> Endpoint[HTTPS endpoint]
Solid lines represent local data flow. Dashed lines represent optional external services.
LociApp.swift owns the macOS lifecycle, menus, windows, and top-level service startup. ContentView.swift composes the primary workspaces and application navigation.
LibraryStore in Models.swift is the main observable application model. It owns the in-memory reference projection, selection and workspace state, import orchestration, and targeted updates published by database observation.
Views should read the narrowest state they need. Expensive file, database, image, and extraction work must stay outside SwiftUI body evaluation.
PersistentStore.swift owns the SQLite schema and durable records. GRDB observations in TableObserver.swift publish table-scoped changes so the model can reload affected references instead of decoding the entire library after every write.
Large source payloads remain in the database. The live UI keeps XBookmarkPayloadSummary projections rather than full browser-extension HTML and transcript payloads.
The default library lives under:
~/Library/Application Support/Loci
Originals, thumbnails, the SQLite database, generated Markdown, and import staging files are stored below the selected library root. Folder-backed libraries may be placed in a user-selected local or cloud-synced folder, but provider conflict resolution remains outside Loci's control.
ImportCoordinator serializes queued import work. Imports can originate from files, URLs, screenshots, pasteboard contents, the browser extension, X sync, or the local API. Follow-up jobs generate previews, extract text, and update the Markdown vault.
Import results are delivered through an asynchronous stream. There is no idle UI polling timer.
Grid surfaces use lazy containers. Canvas and Infinity surfaces calculate item positions in screen space and cull tiles outside a buffered viewport. LociImageLoader bounds image decoding and thumbnail work.
Shared visual constants live in LociDesign.swift, AppMotion.swift, and AppBrand.swift. Authored type sizes use a shared @ScaledMetric modifier so Dynamic Type can scale text and symbol geometry.
The WebExtension sends captures to 127.0.0.1:17641. The server is loopback-only unless remote access is explicitly enabled. Protected routes require a bearer token, request bodies are bounded, and allowed origins are restricted.
X sync uses OAuth 2.0 with PKCE. Access and refresh tokens are stored in the macOS Keychain. Legacy key names remain in the source only to migrate existing development installations safely.
LLM assistance is optional. Local vault search remains available without it. When a user configures an external provider, selected source context may leave the Mac; prompts and responses are excluded from telemetry.
Telemetry is disabled by default. Its implementation allowlists aggregate property names and rejects non-HTTPS upload endpoints. See Telemetry and Privacy for the data contract.
Changes must preserve these invariants:
- Core capture, browsing, and local search work without a hosted account.
- Raw user content never enters telemetry.
- Credentials and OAuth tokens never enter the library database or source tree.
- External network use is explicit and attributable to a configured integration.
- Database and filesystem migrations preserve existing libraries.
The baseline contributor checks are:
swift build
swift testHigh-risk changes should also exercise the relevant boundary manually:
- Persistence: fresh library, existing library, deletion, and migration.
- Import: malformed input, duplicates, cancellation, and large batches.
- UI: empty, loading, error, long-content, Dynamic Type, and dark appearances.
- Local API: authorization, CORS, body limits, and loopback binding.
- OAuth: state validation, refresh, cancellation, and Keychain persistence.
- Telemetry: disabled default, allowlist enforcement, queue clearing, and HTTPS-only upload.