SymorvaDocs
SDK & APIs

Client SDKs

The preview interface for building community apps — Swift and SwiftUI for Apple platforms, Kotlin and Jetpack Compose for Android, and the shared contract desktop shells bind to.

Developer preview· Interface published, subject to change

This page describes the preview interface of the Symorva client SDKs. Names, signatures and package coordinates may change before general availability. The architecture it is built on — native UI over a shared headless core — is the one the reference deployment's iOS and Android clients use today.

Community apps are generated from the manifest, but operators and their developers can extend them or build their own surfaces with the client SDKs. The SDKs expose the same product interfaces the generated apps use: session, chat, meetings and each optional module.

Architecture

SwiftUI / Compose views           your code and generated screens
        │
Symorva client SDK                session, chat, meetings, studio, wallet, directory
        │
headless core                     product state, rules, stale-result isolation
        │
provider adapters                 transport, messaging SDK, media, OS integration
        │
community server + shared services

Views depend on product interfaces, never on transport or messaging-core types. That keeps the UI native on each platform while behaviour stays identical across platforms.

Design rules

These rules come from the reference deployment's client data layer and apply to every SDK surface:

  • Views observe, repositories own. State is exposed as observable values — @Observable models and AsyncSequence in Swift, StateFlow in Kotlin. Views never call the network or the messaging core directly and never keep their own shared caches.
  • Everything is account-scoped. Each repository is bound to the signed-in account. Signing out, switching accounts or clearing the cache cancels in-flight work, and any result that arrives late is discarded rather than shown under the wrong account.
  • One request per key. Concurrent reads of the same resource join a single in-flight request.
  • Work follows observers. When the last observer of a resource goes away, its page-owned work stops. Inactive conversations are not polled.
  • Off the main thread. Decoding, serialisation, file access and image decoding never run on the UI thread or while holding a state lock.
  • Optimism only where it is safe. Personal favourites, conversation pin and mute, and the member's own reactions update instantly and roll back on failure. Money, permissions and destructive actions wait for server confirmation.
  • Unknown is a state. A command whose outcome is unknown is queried, never automatically resent.

Modules in the SDK

Foundation modules are always present. Optional modules are optional values: if a module is disabled in the manifest, its client is absent, so code that depends on it must handle that explicitly.

OrbitApp.swift
import SwiftUI
import SymorvaKit

@main
struct OrbitApp: App {
    // Configuration is generated from symorva.config.json at build time.
    @State private var community = SymorvaCommunity(configuration: .generated)

    var body: some Scene {
        WindowGroup {
            CommunityRootView()
                .environment(community)
        }
    }
}

// Foundation modules are non-optional.
let chat: ChatClient = community.chat
let meetings: MeetingsClient = community.meetings

// Optional modules are nil when disabled in the manifest.
if let wallet = community.wallet { /* … */ }

Chat

ConversationsView.swift
struct ConversationsView: View {
    @Environment(SymorvaCommunity.self) private var community

    var body: some View {
        List(community.chat.conversations.items) { conversation in
            NavigationLink(value: conversation.id) {
                ConversationRow(conversation: conversation)
            }
        }
        .task { await community.chat.conversations.refresh() }
    }
}

// Sending. With end-to-end encryption on, content is encrypted on this device.
let conversation = try await community.chat.conversation(id: conversationID)
let receipt = try await conversation.send(.text("Governance call starts at 18:00."))
// receipt.state: .sending, .sent or .failed(reason)

Text longer than 4,096 UTF-16 code units is split into several messages by the SDK, following the same rules on every platform.

Meetings

A join returns the role the meeting service admitted the member with. Stage members receive a real-time media session; listeners in large-room mode receive a playback stream.

let room = try await community.meetings.join(roomID: roomID)

switch room.role {
case .stage(let media):
    try await media.setMicrophoneEnabled(true)
case .listener(let playback):
    playback.play()
}

try await room.raiseHand()   // signals the host; never grants publishing by itself
try await room.leave()

AI studio grants

Conversation access for the assistant is requested from the member's device and always shows a consent sheet first. The SDK never sends conversation content without a grant.

guard let studio = community.aiStudio else { return }

let grant = try await studio.requestGrant(
    for: conversationID,
    scope: .selectedMessages(selectedIDs)
)                                   // throws .declined if the member says no
let summary = try await studio.summarize(using: grant)
try await studio.revoke(grant)

Wallet

Transfers are prepared, confirmed and resolved. The outcome is explicit, including the case where it is not yet known.

guard let wallet = community.wallet else { return }

let draft = try await wallet.prepareTransfer(to: memberID, asset: asset, amount: Amount("25.00"))
let outcome = try await draft.confirm(with: .pin(pinEntry))

switch outcome {
case .settled(let transfer):
    show(transfer)
case .failed(let reason):
    show(reason)
case .unknown(let pending):
    let resolved = try await pending.query()   // queries the original command; never resubmits
    show(resolved)
}

The PIN is encrypted on the device with the server's public key before it leaves the app. Biometric confirmation is offered only when the server advertises a verification protocol for it.

Desktop

macOS apps use the same Swift package with SwiftUI on macOS. Windows shells bind to the same headless interfaces; the shell technology is fixed in the delivery plan for each community. The contract every shell implements is language-neutral; in TypeScript notation:

client-contract.d.ts
interface CommunityClient {
  readonly session: Session;
  readonly chat: ChatClient;
  readonly meetings: MeetingsClient;
  readonly aiStudio?: StudioClient;
  readonly wallet?: WalletClient;
  readonly directory?: DirectoryClient;
}

interface Observable<T> {
  readonly value: T;
  subscribe(listener: (value: T) => void): () => void;  // returns unsubscribe
}

type TransferOutcome =
  | { kind: "settled"; transfer: Transfer }
  | { kind: "failed"; reason: FailureReason }
  | { kind: "unknown"; pending: PendingCommand };

Errors

ErrorMeaning
unauthenticatedThe session expired or was revoked; sign in again
forbiddenThe member's role does not allow the action
moduleDisabledThe module or option is disabled in the manifest
notFoundThe resource does not exist or is not visible to the member
rateLimitedRetry after the interval the server provided
networkNo response; for commands, the outcome may be unknown
serverThe server returned an error code

Distribution and requirements

Preview builds are distributed to design partners as a Swift package and as Android library artifacts. Minimum operating-system versions are published with the first preview release.

On this page