Peek Mobile#
A Tauri-based iOS app for saving and organizing URLs, notes, and tag-sets using frecency (frequency + recency) scoring.
Overview#
Peek is a mobile bookmarking and note-taking app that allows you to:
- Save Pages (URLs) directly from the iOS share sheet
- Save Notes (text with inline #hashtags) from the share sheet or main app
- Create Tag-sets (collections of tags) for quick categorization
- Tag items with multiple tags using frecency-scored suggestions
- Automatically merge tags when saving duplicate items
- Browse saved items in tabbed interface (Pages | Notes | Tags)
- Use domain-affinity tag boost for smarter suggestions
- Sync all item types to an external webhook endpoint
- Edit and delete any saved item
Tech Stack#
- Frontend: React + TypeScript + Vite
- Backend: Rust with Tauri v2
- Platform: iOS (simulator and device)
- Storage: SQLite database in iOS App Groups container (shared between app and extension)
- Database Access:
- Main app: Rust with
rusqlitecrate via Objective-C FFI bridge - Share Extension: Swift with GRDB.swift library
- Main app: Rust with
- Native Bridge: Objective-C bridge for accessing App Group container path from Rust
Architecture#
Item Types#
Peek supports three item types, accessible via tab navigation:
| Type | Description | Source |
|---|---|---|
| Pages | URLs/bookmarks | Share Extension (Safari, etc.) |
| Notes | Text with inline #hashtags | Share Extension (text) or main app |
| Tag-sets | Tag collections only | Main app only |
Notes support two ways to add tags:
- Inline hashtags in the text (e.g.,
#idea #todo) are auto-parsed - Tag buttons below the textarea for adding additional tags
Tag-sets are useful for creating reusable tag combinations.
iOS Share Extension#
The app uses an iOS Share Extension with a full UI that allows:
- Immediate tagging without opening the main app
- Tag selection from frecency-sorted list
- Creating new tags on the fly
- Automatic detection and merging of duplicate items
- Status display showing existing tags for already-saved items
- Support for both URLs and plain text content
Native WKWebView (In-App Browser)#
Each URL card has a "webview" button that opens the URL in a native WKWebView overlay, allowing users to view content without leaving the app.
Implementation:
- Swift plugin:
src-tauri/gen/apple/tauri-app_iOS/tauri-app_iOS/WebViewPlugin.swift - FFI bridge:
src-tauri/webview-plugin/- Rust crate exposing C functions - React integration:
App.tsxcallswindow.__TAURI_INVOKE__("plugin:webview|open", { url })
Features:
- Full-screen overlay with navigation bar
- Native "Done" button to close
- Tracks visited URLs (stores visit timestamp)
- Reader mode available (future)
Pull-to-Refresh#
The main list supports pull-to-refresh with a hold gesture to prevent accidental triggers during scroll.
Behavior:
- Pull down to reveal refresh indicator
- Hold for 300ms to trigger refresh
- Release before 300ms → cancels (bounce back)
- Visual feedback shows progress ring filling
Implementation: src/App.tsx - search for pullToRefresh state
Frecency Algorithm#
Tags are scored using frecency (frequency + recency):
frecency_score = frequency × 10 × decay_factor
decay_factor = 1 / (1 + days_since_use / 7)
This ensures frequently used tags appear first, but decay over time if not used.
Domain-Affinity Tag Boost#
When displaying unused tags in the save/edit interfaces, tags that have been used on URLs from the same domain get a 2x frecency score multiplier. This makes relevant tags appear higher in suggestions.
Example: When saving a URL from github.com/foo/bar, any tags previously used on other GitHub URLs (e.g., github.com/bar/baz) will appear higher in the tag suggestions.
Implementation:
- Domain extraction removes
www.prefix for matching - Applied in both Share Extension (Swift) and main app edit mode (Rust/React)
URL Deduplication#
When saving a URL that already exists:
- The share extension detects the duplicate
- Shows status: "Already saved with tags: existing, tags"
- Pre-selects existing tags
- Button changes to "Update Tags"
- On save, merges new tags with existing tags (set union)
- Preserves original ID and timestamp
Webhook Sync#
The app supports syncing all item types to an external webhook endpoint:
- Configure webhook URL and API key in the Settings screen
- Manual sync via "Sync All" button
- Auto-sync on save from both main app and share extension
- Daily auto-sync checks
last_synctimestamp, syncs if >24 hours - Offline detection skips webhook POST if device is offline (uses
NWPathMonitor)
Payload format:
{
"urls": [
{ "id": "uuid", "url": "https://...", "tags": ["tag1"], "saved_at": "..." }
],
"texts": [
{ "id": "uuid", "content": "Note with #hashtag", "tags": ["hashtag"], "saved_at": "..." }
],
"tagsets": [
{ "id": "uuid", "tags": ["tag1", "tag2"], "saved_at": "..." }
]
}
Profiles#
Mobile profiles provide dev/production data isolation. See docs/profiles.md for full details.
Quick summary:
- Auto-detection: Xcode builds use
devprofile, TestFlight/App Store usedefault - Separate databases: Each profile has its own SQLite file (
peek-{profile-uuid}.db) - Sync isolation: All sync requests include
?profile={slug}
Data Storage#
Data is stored in SQLite databases within the iOS App Groups container (group.com.dietrich.peek-mobile). This enables sharing between the main app and share extension with proper concurrent access via WAL mode.
Per-profile database files:
App Group Container/
├── profiles.json # Profile metadata and sync config
├── peek-{uuid1}.db # Default profile database
├── peek-{uuid2}.db # Development profile database
└── peek-{uuid3}.db # Additional profiles...
Database Location:
~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup/<GROUP_UUID>/peek.db
Database Schema:
-- Unified items table (pages, texts, tagsets)
CREATE TABLE items (
id TEXT PRIMARY KEY, -- UUID
type TEXT NOT NULL DEFAULT 'page', -- 'page', 'text', or 'tagset'
url TEXT, -- URL (required for 'page' type)
content TEXT, -- Text content (required for 'text' type)
created_at TEXT NOT NULL, -- ISO8601 timestamp
updated_at TEXT NOT NULL, -- ISO8601 timestamp
deleted_at TEXT -- Soft delete timestamp (NULL = active)
);
-- Tags table
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- Tag name (lowercase)
frequency INTEGER NOT NULL DEFAULT 0,
last_used TEXT NOT NULL, -- ISO8601 timestamp
frecency_score REAL NOT NULL DEFAULT 0.0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- Item-Tag junction table (many-to-many)
CREATE TABLE item_tags (
item_id TEXT NOT NULL,
tag_id INTEGER NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (item_id, tag_id),
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
-- Settings table (key-value store)
CREATE TABLE settings (
key TEXT PRIMARY KEY, -- e.g., 'webhook_url', 'webhook_api_key', 'last_sync'
value TEXT NOT NULL
);
Key Features:
- WAL mode for concurrent access from main app and share extension
- Unified items table supports all three item types
- Automatic migration from legacy
urls/url_tagstables - Normalized schema with junction table for item-tag relationships
- Indexes on frequently queried columns (type, url, content, frecency_score)
Key Files#
Share Extension#
src-tauri/gen/apple/Peek/ShareViewController.swift- Full UI share extensionShareViewController-full-ui.swift.example- Reference implementation
App Group Bridge#
src-tauri/AppGroupBridge.m- Objective-C bridge for App Groups access- Provides C functions for Rust FFI:
get_app_group_container_path()- Returns path to App Group container for SQLite databaseget_system_is_dark_mode()- Returns current system appearance (dark/light mode)
Rust Backend#
src-tauri/src/lib.rs- Tauri commands and business logic- Page commands:
save_url,get_saved_urls,update_url,delete_url - Text commands:
save_text,get_saved_texts,update_text - Tagset commands:
save_tagset,get_saved_tagsets,update_tagset - Tag commands:
get_tags_by_frecency,get_tags_by_frecency_for_url - Webhook commands:
sync_to_webhook,auto_sync_if_needed
Frontend#
src/App.tsx- React UI with tabbed navigation (Pages | Notes | Tags)src/App.css- Mobile-optimized styling with dark mode
Tests#
tests/integration.test.js- Integration tests against backend/server
Configuration#
src-tauri/tauri.conf.json- Bundle ID:com.dietrich.peek-mobilesrc-tauri/gen/apple/tauri-app_iOS/tauri-app_iOS.entitlements- App Groups entitlementsrc-tauri/gen/apple/Peek/Peek.entitlements- Share extension entitlements
Development#
Prerequisites#
- Node.js and npm
- Rust and Cargo
- Xcode (for iOS development)
- Apple Developer certificate
Setup#
npm install
Running#
Desktop (for quick UI testing):
npm run tauri dev
Hot Reload Development (Recommended)#
For frontend development with instant CSS/JS hot reload:
npm run dev:ios:sim
This command:
- Clears any pre-built
libapp.ato force dev-mode build - Starts the Vite dev server on
http://localhost:1420 - Opens Xcode with the Tauri CLI process running
Then in Xcode:
- Build and run (Cmd+R) with Debug scheme on simulator
- Xcode runs the build script which compiles Rust in dev mode
- The app loads from
localhost:1420- changes hot reload instantly
Note: Keep the terminal process running - it maintains the dev server.
Build Workflow#
Frontend assets (CSS, JS, HTML) are embedded in the Rust binary at compile time for release builds. This means:
- Changing CSS/JS requires rebuilding Rust with
cargo tauri build(NOT justcargo build) - Simply rebuilding in Xcode won't pick up frontend changes for bundled builds
- The library file to copy is in the
deps/subdirectory
Debug Build (Simulator - Bundled Assets):
npm run build:ios
# Then build in Xcode with Debug scheme, simulator target
Release Build (Simulator - for testing release config):
npm run build:ios:sim:release
# Then build in Xcode with Release scheme, simulator target
Release Build (Device):
npm run build:ios:release
# Then build in Xcode with Release scheme, device target
npm Scripts Reference#
| Script | Description |
|---|---|
dev:ios:sim |
Hot reload development - starts vite, opens Xcode |
build:ios |
Debug build for simulator (bundled assets) |
build:ios:sim:release |
Release build for simulator |
build:ios:release |
Release build for device |
xcode |
Copy libraries and open Xcode (no build) |
dev:ios |
Full dev setup with server (for sync testing) |
CLI Build Scripts (from repo root)#
Simulator builds:
| Script | Description |
|---|---|
mobile:ios:xcodebuild:full |
Full debug build + install on simulator |
mobile:ios:xcodebuild:release:full |
Full release build + install on simulator |
mobile:ios:sim:run |
Boot simulator + build + install + launch |
mobile:ios:sim:boot |
Boot iPhone 17 Pro simulator |
mobile:ios:sim:launch |
Launch app on booted simulator |
Device builds:
| Script | Description |
|---|---|
mobile:ios:xcodebuild:device:full |
Full release build + install on USB device |
mobile:ios:device:launch |
Launch app on connected device |
mobile:ios:device:list |
List connected iOS devices |
One-liner for device deployment:
yarn mobile:ios:xcodebuild:device:full && yarn mobile:ios:device:launch
Important Notes:
- Debug uses
Externals/arm64/Debug/libapp.aand targetaarch64-apple-ios-sim - Release uses
Externals/arm64/Release/libapp.aand targetaarch64-apple-ios - Always copy from the
deps/subfolder (has embedded assets), not the root folder - Use
cargo tauri build, NOTcargo build(the latter doesn't embed frontend assets)
The Xcode preBuildScript checks if libapp.a exists and skips the Rust build if so. For hot reload development, dev:ios:sim clears this file so Xcode runs the build script in dev mode.
App Icon#
The app uses Peek.icon bundle (Xcode 15+ unified icon format):
- Source:
src-tauri/gen/apple/Peek.icon/Assets/Peek clouds src.png(1232x1232) - Xcode generates all required icon sizes during build
- Do NOT recreate
Assets.xcassets/AppIcon.appiconset/- that's Tauri's default icons
In-App Icons (URL Action Buttons)#
The URL cards have action buttons (Safari, WKWebView, copy, edit, delete) that use inline SVG icons.
Location: backend/tauri-mobile/src/App.tsx - search for SvgIcon components
Styling: The .card-action-btn class in App.css controls button appearance:
.card-action-btn {
-webkit-appearance: none;
appearance: none;
background: transparent;
border: none;
/* ... */
}
Important: iOS Safari/WebKit requires explicit -webkit-appearance: none and background: transparent to prevent default button styling that shows as gray backgrounds.
Building#
Build Rust library for iOS:
cd src-tauri
./build-ios.sh
Build iOS app:
cd src-tauri/gen/apple
xcodebuild -scheme tauri-app_iOS -configuration Debug -sdk iphonesimulator -derivedDataPath build
Install on simulator:
xcrun simctl install <DEVICE_ID> "src-tauri/gen/apple/build/Build/Products/debug-iphonesimulator/Peek.app"
Bundle Identifiers#
- Main app:
com.dietrich.peek-mobile - Share extension:
com.dietrich.peek-mobile.share(must be prefixed with main app ID) - App Group:
group.com.dietrich.peek-mobile
Important: All three must match the -mobile suffix for the App Groups sharing to work.
Build Script#
The build-ios.sh script:
- Builds Rust library for both
aarch64-apple-ios-simandx86_64-apple-ios - Creates universal library with
lipo - Copies to
gen/apple/Externals/arm64/debug/libapp.a - Compiles Objective-C bridge code
Testing#
Integration tests verify the webhook sync with the backend server (backend/server/):
# Run tests (starts server with temp data, runs tests, cleans up)
npm test
# Verbose mode (shows server logs)
npm run test:verbose
Tests cover:
- Webhook sync for pages (URLs)
- Texts API (create, read, update, delete)
- Tagsets API (create, read, update, delete)
- Unified
/itemsAPI with type filtering - Tags frecency tracking
- Update and delete operations
Requirements: Server dependencies installed (cd ../server && npm install)
Cleaning Data#
To clear all saved items and tags from simulator:
# Find the SQLite database
find ~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup -name "peek.db"
# Delete it (also removes WAL files)
rm "<path_to_peek.db>"*
# Or to just clear data without deleting the database:
sqlite3 "<path_to_peek.db>" "DELETE FROM item_tags; DELETE FROM items; DELETE FROM tags;"
Share Extension in Xcode#
The share extension must be configured in Xcode:
- Target: "Peek" (Share Extension)
- Bundle ID:
com.dietrich.peek-mobile.share - Principal Class:
ShareViewController - Copy
ShareViewController-full-ui.swift.exampletosrc-tauri/gen/apple/Peek/ShareViewController.swift - Ensure entitlements include App Groups
iOS Build System Architecture#
This section documents the critical details of building Tauri iOS apps. Read this before doing any iOS builds.
The custom-protocol Feature (CRITICAL)#
⚠️ The custom-protocol Cargo feature is REQUIRED for production builds.
Without this feature, the app tries to connect to localhost:1420 instead of using bundled assets, causing:
- "failed to request tauri://localhost" error
- iOS local network permission prompt
- Blank white screen
# backend/tauri-mobile/src-tauri/Cargo.toml
[dependencies]
tauri = { version = "2", features = ["custom-protocol"] }
[features]
custom-protocol = ["tauri/custom-protocol"]
How it works: Tauri's build system sets cargo:dev=true or cargo:dev=false based on this feature:
- With
custom-protocol→dev=false→ uses bundled assets (production) - Without
custom-protocol→dev=true→ tries to connect to localhost (dev mode)
For hot reload development, the feature is still present but we use cargo tauri dev which starts a dev server and connects to it.
Debug vs Release Build Separation#
Debug and release builds are completely separated to prevent cross-contamination:
| Aspect | Debug (Simulator) | Release (Device) |
|---|---|---|
| Target | aarch64-apple-ios-sim |
aarch64-apple-ios |
| Library path | Externals/arm64/Debug/libapp.a |
Externals/arm64/Release/libapp.a |
| Xcode scheme | Debug | Release |
| Cache directory | tmp/ios-cache/debug/ |
tmp/ios-cache/release/ |
| Cargo flags | (none) | --release --features custom-protocol |
| Asset source | Bundled or localhost:1420 | Bundled (custom-protocol) |
The iOS cache system is at the repo root (~/misc/mpeek/tmp/ios-cache/) so it's shared across workspaces. This prevents duplicate Rust compilations.
Why cargo build --lib Instead of cargo tauri build#
The standard cargo tauri build fails on iOS due to Swift FFI symbols (webview_plugin_open, webview_plugin_close) that are defined in Swift code and only available at Xcode's final link stage.
Workaround:
- Build only the lib target:
cargo build --lib --target aarch64-apple-ios --release --features custom-protocol - Copy the library to Xcode's expected location
- Let Xcode handle the final linking with Swift code
This is why the build scripts use cargo build --lib and why the custom-protocol feature must be explicitly specified.
Build Scripts Reference#
From repo root (package.json):
| Script | Description |
|---|---|
yarn mobile:ios:cargo:debug |
Build Rust lib for simulator |
yarn mobile:ios:cargo:release |
Build Rust lib for device (with custom-protocol) |
yarn mobile:ios:xcodebuild |
Build app with xcodebuild for simulator |
yarn mobile:ios:xcodebuild:device |
Build app with xcodebuild for device |
yarn mobile:ios:xcodebuild:install |
Install built app on simulator |
yarn mobile:ios:xcodebuild:install:device |
Install built app on device |
yarn mobile:ios:xcodebuild:full |
Full pipeline: cargo + xcodebuild + install (simulator) |
yarn mobile:ios:xcodebuild:device:full |
Full pipeline: cargo + xcodebuild + install (device) |
yarn mobile:ios:assets |
Rebuild frontend assets only |
From backend/tauri-mobile (package.json):
| Script | Description |
|---|---|
yarn build:ios |
Build for simulator with bundled assets |
yarn build:ios:release |
Build for device |
yarn dev:ios:sim |
Hot reload development |
yarn xcode |
Open Xcode project |
Build Workflow: Simulator Debug#
# Full rebuild from scratch
yarn mobile:ios:xcodebuild:full
# Or step by step:
yarn mobile:ios:cargo:debug # 1. Build Rust library
yarn mobile:ios:xcodebuild # 2. Build with xcodebuild (clean build)
yarn mobile:ios:xcodebuild:install # 3. Install on simulator
yarn mobile:ios:sim:launch # 4. Launch app
Build Workflow: Device Release#
# Full rebuild from scratch
yarn mobile:ios:xcodebuild:device:full
# Or step by step:
yarn mobile:ios:cargo:release # 1. Build Rust library with custom-protocol
yarn mobile:ios:xcodebuild:device # 2. Build with xcodebuild
yarn mobile:ios:xcodebuild:install:device # 3. Install on device
yarn mobile:ios:device:launch # 4. Launch app
Frontend-Only Changes#
If you only changed CSS/JS/HTML and Rust is unchanged:
yarn mobile:ios:assets # Rebuild frontend, copy to Xcode
yarn mobile:ios:xcodebuild # Rebuild app (clean build required!)
yarn mobile:ios:xcodebuild:install # Install
Warning: Xcode aggressively caches assets. Always use clean build (the scripts do this automatically) or changes won't appear.
iOS App Group Containers & Data Persistence#
Understanding iOS container lifecycle is critical to avoid data loss.
What Is an App Group Container?#
iOS apps store data in sandboxed containers. An App Group container is a shared container that multiple apps can access (main app + share extension). Our app uses:
- App Group ID:
group.com.dietrich.peek-mobile - Container path:
~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup/<UUID>/
The <UUID> is assigned by iOS when the app is first installed.
When Container UUIDs Change (Data Loss Scenarios)#
| Action | Container UUID | Data |
|---|---|---|
xcrun simctl install (app update) |
Preserved | Preserved ✓ |
xcrun simctl uninstall then reinstall |
New UUID | LOST ✗ |
| Manual delete from Home screen | New UUID | LOST ✗ |
| Simulator "Erase All Content and Settings" | New UUID | LOST ✗ |
| Code changes (any) | Preserved | Preserved ✓ |
| Xcode clean build | Preserved | Preserved ✓ |
| Bundle ID change | New UUID | Old data orphaned |
| App Group ID change | New UUID | Old data orphaned |
Key insight: Normal app updates preserve data. Only uninstalling the app deletes the container.
Finding Your Container#
Simulator:
# Find the peek database
find ~/Library/Developer/CoreSimulator/Devices/*/data/Containers/Shared/AppGroup -name "peek-*.db" 2>/dev/null
# Or find profiles.json
find ~/Library/Developer/CoreSimulator/Devices/*/data/Containers/Shared/AppGroup -name "profiles.json" 2>/dev/null
Device: Use the backup scripts (see below).
Backing Up Data#
Device backup (recommended before any device work):
yarn mobile:device:backup
# Creates timestamped backup in ~/sync/peek-backups/
What gets backed up:
appGroupContainer/- All databases and profiles.jsonappDataContainer/Documents/Backups/- In-app backups
Container Locations#
Simulator (per profile):
~/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Shared/AppGroup/<UUID>/
├── profiles.json
├── peek-<profile-uuid>.db
├── peek-<profile-uuid>.db-wal
└── peek-<profile-uuid>.db-shm
Device:
- Accessed via
ios-deployor Xcode's device file browser - Same structure as simulator
Troubleshooting#
localhost Error (Most Common)#
Symptom: App shows "failed to request tauri://localhost", asks for local network permission, shows blank white screen.
Cause: Missing custom-protocol Cargo feature.
Fix:
# 1. Verify feature is in Cargo.toml
grep -A5 "\[features\]" backend/tauri-mobile/src-tauri/Cargo.toml
# Should show: custom-protocol = ["tauri/custom-protocol"]
# 2. Clean and rebuild with feature
rm -rf backend/tauri-mobile/src-tauri/target/aarch64-apple-ios*/release
yarn mobile:ios:cargo:release # For device
# or
yarn mobile:ios:cargo:debug # For simulator (if you want bundled assets)
# 3. Clean xcodebuild and reinstall
yarn mobile:ios:xcodebuild:device:full # For device
Builds Not Updating (Version Stuck)#
Symptom: You changed code but the app still shows old version.
Cause: Xcode caches assets aggressively. A build without clean reuses cached assets.
Fix: The yarn scripts use clean build. If using Xcode GUI:
- Product → Clean Build Folder (Cmd+Shift+K)
- Delete DerivedData:
rm -rf /tmp/peek-xcodebuild-sim /tmp/peek-xcodebuild-device - Rebuild
Wrong Library Architecture#
Symptom: Build fails with architecture mismatch errors.
Cause: Debug library in Release folder or vice versa.
Fix:
# Clear both caches
rm -rf ~/misc/mpeek/tmp/ios-cache/debug
rm -rf ~/misc/mpeek/tmp/ios-cache/release
# Rebuild the correct one
yarn mobile:ios:cargo:debug # For simulator
yarn mobile:ios:cargo:release # For device
Share Extension Not Saving#
Symptom: Share extension appears but items don't save.
Cause: App Group mismatch or database path issue.
Fix:
- Verify all three IDs match:
- Main app:
com.dietrich.peek-mobile - Extension:
com.dietrich.peek-mobile.share - App Group:
group.com.dietrich.peek-mobile
- Main app:
- Check entitlements files in Xcode
- Rebuild both targets
Stale Build Cache#
If changes to AppGroupBridge.m aren't reflected:
cd src-tauri
cargo clean
./build-ios.sh
# Rebuild in Xcode
Share Extension Not Appearing#
Check:
- Bundle ID has correct prefix:
com.dietrich.peek-mobile.share - Share extension Info.plist has correct NSExtension configuration
- App Groups entitlements match between app and extension
No Saved URLs Showing#
Verify:
- App Groups identifier matches in all three places
- Both Rust (rusqlite) and Swift (GRDB) are accessing the same database path
- Database is being created in the App Group container, not the app sandbox
- Check database contents:
sqlite3 <path_to_peek.db> "SELECT * FROM urls;"
"Built for newer iOS version" Linker Warnings#
Symptom: Dozens of linker warnings like Object file (libapp.a[...]) was built for newer 'iOS' version (26.2) than being linked (16.0).
Cause: The Rust build compiled C objects (ring crypto, sqlite, AppGroupBridge) using the system SDK's default deployment target instead of the project's iOS 16.0 target.
Fix: Both build scripts (build-ios.sh and build-release.sh) must set export IPHONEOS_DEPLOYMENT_TARGET=16.0 before cargo build. This is already done — if warnings reappear, check that the env var is still present.
Xcode Points at Wrong Workspace#
Symptom: Build fails with search path '.../ios-fixes-XXXX/...' not found or similar, pointing at an old/different jj workspace.
Cause: Xcode's DerivedData caches $(PROJECT_DIR) from whichever workspace the project was first opened from.
Fix:
- Quit Xcode completely
- Delete stale DerivedData:
rm -rf ~/Library/Developer/Xcode/DerivedData/peek-save-* - Reopen from the correct workspace:
open backend/tauri-mobile/src-tauri/gen/apple/peek-save.xcodeproj
Xcode "Update to Recommended Settings"#
When Xcode prompts to update settings, accept all except "Enable User Script Sandboxing" — that would break the "Build Rust Code" run script phase which needs filesystem access outside the sandbox.
License#
See LICENSE file.