⚠️ Not viable on iOS 26 — do not integrate. The cross-app ExtensionKit architecture StoicKit is built on is gated behind a private Apple entitlement as of iOS 26 beta 3+.AppExtensionPoint.Monitorreturns zero identities for extension points whose provider lives in a different app, regardless of configuration, App Group, or shared Team ID. See BLOCKER.md for the full writeup and forward options (App Intents, Share Extension, etc.). This repo is kept as a reference; the public API documented below is preserved for historical context.
A Swift package for writing journal entries into Stoic from any app.
StoicKit lets partner apps push journal entries into Stoic in real time through Apple's ExtensionKit framework. Call store.save(entry) from your app — StoicKit handles delivery automatically.
Runtime requires iOS 26+ / macOS 26+. The package links on iOS 15+ / macOS 12+ so apps with a lower deployment target can depend on it and gate usage with
if #available(iOS 26, macOS 26, *). Built with Swift 6.2 (Xcode 26+).
┌────────────────┐ App Group ┌────────────────┐ XPC ┌──────────┐
│ Your App │──────────────►│ Relay Ext │────────►│ Stoic │
│ │ + Darwin │ (StoicRelay) │ │ │
│ store.save() │ notification │ auto-forward │ │ receive │
└────────────────┘ └────────────────┘ └──────────┘
- Your app calls
store.save(entry)— writes to the App Group shared container. - A Darwin notification wakes the relay extension.
- The relay forwards the entry to Stoic over XPC.
If Stoic is not connected, entries queue in the shared container and deliver on next connection.
dependencies: [
.package(url: "https://github.com/stoicapp/StoicKit", from: "0.1.0")
]import StoicKit
let store = StoicStore(appGroup: "group.com.yourcompany.yourapp")
try store.save(JournalEntry(text: "Today I practiced mindfulness."))Add a new target: File > New > Target > App Extension > Generic Extension
Set EXExtensionPointIdentifier in its Info.plist:
<key>EXAppExtensionAttributes</key>
<dict>
<key>EXExtensionPointIdentifier</key>
<string>com.getstoic.relay</string>
</dict>The entire extension is one file:
import StoicKit
@main
struct MyStoicRelay: StoicRelay {
static let appGroup = "group.com.yourcompany.yourapp"
}Make sure both your app and extension share the same App Group in their entitlements.
Entries saved via StoicStore.save() are queued in the App Group shared container and delivered to Stoic as soon as possible. Delivery timing depends on Stoic's connection state:
| Stoic state | Delivery | How |
|---|---|---|
| Running (foreground/suspended) | Instant | Stoic keeps persistent connections to all enabled extensions. The Darwin notification wakes the relay, which forwards immediately over the existing XPC connection. |
| Not running | Next background refresh (~15 min) | Stoic periodically wakes via background app refresh, connects to extensions, and drains pending queues. |
Entries are never lost — they persist in the shared container until successfully delivered. If delivery fails (e.g., Stoic rejects the entry), the entry remains queued and retries on the next sync.
You do not need to handle connection state, retries, or delivery timing. Just call store.save() and StoicKit takes care of the rest.
Intentionally minimal. Will be expanded as the API evolves.
JournalEntry(
id: UUID(), // Unique identifier for deduplication
text: "Journal text", // Entry content
timestamp: .now, // Creation date
modifiedAt: nil // Last modification date (optional)
)Copy this prompt into your AI coding agent to scaffold the integration automatically:
Add StoicKit integration to this project. Add the StoicKit Swift package from
https://github.com/stoicapp/StoicKit. Check if the project already has an App Group configured — if so, ask me which one to use; if not, create one and share it between the main app and a new Generic Extension target. In the main app, create aStoicStore(appGroup:)and callstore.save(JournalEntry(text:))after the user finishes writing a journal entry. For the extension target, setEXExtensionPointIdentifiertocom.getstoic.relayin its Info.plist and create a single-file@mainstruct conforming toStoicRelaywith the matchingappGroup. Both targets must share the same App Group in their entitlements.
See Example/ for complete reference implementations:
ExampleApp.swift— Main app callingstore.save()ExampleRelay.swift— The relay extension (one file)
- Runtime: iOS 26.0+ / macOS 26.0+ — all public APIs (
StoicStore,StoicRelay) are annotated@available(iOS 26, macOS 26, *) - Linking: iOS 15.0+ / macOS 12.0+ — apps with a lower deployment target can depend on the package and gate usage with
if #available(iOS 26, macOS 26, *) - Swift 6.2
- Xcode 26+
Huge thanks to Gui Rambo and Matt Massicotte for documenting ExtensionKit and writing extensively about it when Apple provided almost no official guidance. Their work made this project possible.
- Creating Custom Extension Points with ExtensionKit by Gui Rambo
- A (Re-)Introduction to ExtensionKit by Matt Massicotte
MIT. See LICENSE for details.