Soapstone #
A location-based messaging app for iOS, inspired by the social message mechanics in the Dark Souls games. Players leave short, structured messages anchored to physical GPS coordinates; others nearby can discover and read them.
Soapstone is built on the AT Protocol (the decentralized network behind Bluesky) using
custom social.soapstone.* lexicons. This repository is the mobile client, written in
Swift / SwiftUI. The backend (Node.js + PostgreSQL/PostGIS App View) lives in a
separate repo.
Requirements #
- Swift 6 toolchain (Xcode 16+ on macOS, or the open-source toolchain on Linux)
- iOS 18+ to run the app on a device or simulator
- xtool to build and deploy the iOS app from a
SwiftPM package (no
.xcodeprojrequired)
The platform-agnostic core (SoapstoneKit) also builds and tests on Linux with just a
Swift toolchain — no Apple SDKs needed.
Project layout #
Package.swift SwiftPM manifest (two targets + test target)
xtool.yml xtool project config (bundle ID, Info.plist)
Info.plist iOS app Info.plist (permissions, URL scheme, orientation)
Sources/
SoapstoneKit/ Platform-agnostic core — builds & tests on Linux
Auth/ OAuth (PKCE + DPoP) session layer
Core/ Domain models, config, geo URIs, phrase catalog
Feed/ Nearby-feed + AR placement logic (pure, testable)
Location/ LocationProviding protocol + mock
Net/ XRPC, App View (read) and PDS (write) clients
Resources/ Bundled phrase catalog (en.json)
soapstone_mobile/ The iOS app — SwiftUI, CoreLocation, ARKit scaffold
App/ Entry point, AppModel, Keychain token store
Features/ Login, Feed, Compose, AR screens + view models
Location/ CoreLocationProvider
Theme/ Fonts + styling
Tests/SoapstoneKitTests/ Unit tests for the core (run with `swift test`)
docs/ Lexicon reference, environment config, architecture
Two-target design #
All UI / Apple-framework code lives in the soapstone_mobile target and is guarded with
#if canImport(SwiftUI), so the package still compiles to an empty module on Linux. Everything
that can be tested without a device — auth crypto, geo math, networking, feed logic — lives in
SoapstoneKit, which depends only on Foundation and
swift-crypto. This keeps the test suite runnable in CI
on Linux while the device-only layer stays thin.
Building & running #
Core (any platform) #
swift build # compile both targets
swift test # run the SoapstoneKit unit suite (runs on Linux & macOS)
iOS app (xtool) #
xtool dev # build, install, and run on a connected device / simulator
xtool dev build # build the .app without launching
See the xtool docs for first-time setup (signing, device pairing). The bundle identifier and Info.plist are configured in xtool.yml.
Configuration #
App-wide constants live in
AppConfig — App View host, OAuth client
metadata URL, redirect scheme, and the default 20 m search radius. See
docs/ENVIRONMENT_CONFIG.md for per-environment overrides.
How it works #
- Auth — ATProto OAuth with PKCE + DPoP, implemented from scratch in
SoapstoneKit/Auth(there is no mature managed Swift OAuth client for ATProto). Tokens never leave the session layer; they are persisted only in the Keychain. App passwords are not supported. - Reads — nearby posts come from the App View backend
(
AppViewClient.getPosts), which runs the PostGIS geospatial query. Read-only. - Writes — new messages and ratings are written directly to the user's PDS via
PDSClient(com.atproto.repo.createRecord) using the custom lexicon$types. - Location — foreground-only GPS via
CoreLocationProvider; the nearby feed updates reactively as the user moves. AMockLocationProviderbacks tests and previews.
For the data shapes exchanged with the backend, see docs/lexicon-reference.md and docs/XRPC_SERVICE.md.
Roadmap #
- v1 (current): list-based UI showing posts within 20 m of the user's live location.
- Future: an augmented-reality view rendering message indicators over the camera feed at
their real-world coordinates (ARKit). The pure
Feed/ARPlacement+Feed/GeoProjectionlogic is the start of that work.
Further reading #
- CLAUDE.md — product, domain, protocol, and architecture notes
- docs/lexicon-reference.md —
social.soapstone.*record shapes - docs/ENVIRONMENT_CONFIG.md — environment configuration
- docs/XRPC_SERVICE.md — networking / client layer