Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e981188
Add native Swift SDK with attested Transport V2 and Research APIs
marksftw Sep 30, 2026
cb9bf26
Test exact message linkage in Swift Responses integration
marksftw Sep 30, 2026
6499617
Strengthen native handoff and live SDK integration checks
marksftw Sep 30, 2026
d821fde
Implement native Maple iOS app and Swift SDK
marksftw Oct 8, 2026
432c8ad
Polish native iOS library and chat interactions
marksftw Oct 9, 2026
5f62c31
Refine native iOS navigation, chat options, and library UX
marksftw Oct 11, 2026
c3ede94
Preserve native iOS rich chat rendering while scrolling
marksftw Oct 11, 2026
1ab0828
Keep native iOS streaming chat timelines on the vertical axis
marksftw Oct 11, 2026
4232814
Use native iOS menu navigation and refine chat entry
marksftw Oct 12, 2026
945b5c0
ios-native parity item 1: repair SDK session and stream ownership
AnthonyRonning Oct 13, 2026
9fbe51b
ios-native parity item 2: align PCR current and rollback roots
AnthonyRonning Oct 13, 2026
557035d
ios-native parity item 4: classify structured chat rejection codes
AnthonyRonning Oct 13, 2026
28fbd28
ios-native parity items 3 and 5: decode billing and align access rules
AnthonyRonning Oct 13, 2026
5938341
ios-native parity items 4 6 7 9 11: report chat and dictation outcomes
AnthonyRonning Oct 13, 2026
45074c0
ios-native parity items 14g and 18: persist sidebar and default speec…
AnthonyRonning Oct 13, 2026
99da4af
ios-native parity items 16 and 17: align email signup and legal footer
AnthonyRonning Oct 13, 2026
225a0ac
ios-native parity items 4 7-9 11-13: align chat presentation and comp…
AnthonyRonning Oct 13, 2026
198791a
ios-native parity item 10: poll generated titles with lifecycle cance…
AnthonyRonning Oct 13, 2026
f159e07
ios-native parity item 14: align library rows refresh and bulk behavior
AnthonyRonning Oct 13, 2026
8dff137
ios-native parity items 13 15 17 18: align model details and account …
AnthonyRonning Oct 13, 2026
307754c
ios-native parity validation: reveal settings rows and load retained …
AnthonyRonning Oct 13, 2026
136bb9b
fix(swift-sdk): preserve legacy plan errors and simplify cleanup
AnthonyRonning Oct 13, 2026
ee8e0d2
fix(ios): align model and media access with Research
AnthonyRonning Oct 13, 2026
6425bbb
fix(ios): preserve stopped output and recover options navigation
AnthonyRonning Oct 13, 2026
b84ba2d
fix(ios): default missing web search preferences on
AnthonyRonning Oct 13, 2026
09fcb33
fix(ios): reconcile native navigation after chat presentations dismiss
AnthonyRonning Oct 13, 2026
6baf139
Merge pull request #1153 from MaplePrivacyLabs/codex-ios-native-parity-1
AnthonyRonning Oct 13, 2026
71a6e50
Native iOS parity: chat, composer, library and usage (#1155)
AnthonyRonning Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 26 additions & 2 deletions .agents/skills/develop-opensecret-sdk/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: develop-opensecret-sdk
description: Develop and review the Maple TypeScript/React and Rust SDKs under Maple's sdk directory. Use for SDK API, authentication, attestation, encrypted transport, tests, in-tree OpenSecret integration, package contents, versions, or an explicitly authorized npm or crates.io publishing handoff; use develop-maple for application-only work.
description: Develop and review the Maple TypeScript/React, Rust and Swift SDKs under Maple's sdk directory. Use for SDK API, authentication, attestation, encrypted transport, tests, in-tree OpenSecret integration, package contents, versions, or an explicitly authorized publishing handoff; use develop-maple for application-only work.
---

# Develop the Maple SDK
Expand All @@ -14,6 +14,9 @@ package boundaries remain independently versioned and publishable:

- `src/` builds `@mapleai/sdk` for browser and React consumers.
- `rust/` builds the `maple-sdk` crate (imported as `maple_sdk`) for native consumers.
- `swift/` builds the independent `MapleSDK` Swift package for native Apple
clients. Read `swift/AGENTS.md` and `swift/README.md`; its Xcode toolchain,
version, tests and publication boundary are independent of the other SDKs.
- `apps/maple-research/frontend/package.json` is authoritative for whether Maple's browser client
consumes a published TypeScript version or the in-tree `file:../../../sdk` package.
- Research native clients, Maple Agent, and `proxy/` select their Rust SDK
Expand Down Expand Up @@ -50,7 +53,28 @@ caller.

## Develop and validate

Use the SDK's pinned Nix shell. For TypeScript/React work:
Use the SDK's pinned Nix shell for TypeScript and Rust. Swift uses the root
Apple shell and host Xcode 26.5, including its bundled `swift-format`:

```sh
nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh unit
nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh build
nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh ios-build
```

Run Swift commands from the repository root. Match `sdk-swift.yml` for
deterministic and disposable local-backend integration coverage. The
`integration` mode requires an explicit loopback endpoint and disposable
test account configuration; preserve externally owned services. Real
development-enclave trust checks use the separately authorized
`live-attestation` mode. Never infer hardware, provider or Nitro trust from
mocks. Keep one shared credential authority per API origin and test atomic
replacement, cancellation, stale generations and persistence failures.
Swift package-root distribution has not been published; local source,
source checkpoint tags and an eventual independent package publication must
not create or advance a Maple app release. See `swift/README.md`.

For TypeScript/React work:

```sh
nix develop --no-update-lock-file -c bun install --frozen-lockfile --ignore-scripts
Expand Down
24 changes: 24 additions & 0 deletions .agents/skills/validate-maple/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ Use for iOS, Android, signing, updater metadata, installers, entitlements, or di

Run commands from the repository root unless the command changes directory explicitly.

### Native SwiftUI iOS app

Read `apps/maple-ios/AGENTS.md`. The app uses the root Apple shell and host
Xcode 26.5; its PR profile ignores local overrides and builds with the
development bundle identity. Run deterministic checks and unsigned packaging:

```bash
nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh unit
nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh cross-build
nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh simulator-build
nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh device-build
```

The component hook runs only the first command. These commands clear inherited
live-test switches and never sign or upload. Swift package tests on macOS are
core-workflow evidence; iOS compilation is not a simulator UI pass. Run the
exact configured app on iPhone and iPad simulators for UI, lifecycle, resource,
accessibility, account isolation and local-service flows. Record the built
bundle's identity and public origins, simulator runtime and test scope.
Keep real-device permissions, hardware, protection and distribution evidence
distinct. Use only explicitly authorized disposable fixtures for live account
tests; credentials belong in a private child environment, never command
arguments, screenshots, tracked files or logs.

### Standalone hosted Auth

For changes confined to `apps/maple-auth`, use its own guide and checks:
Expand Down
6 changes: 6 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ fi
if hook_selected sdk_rust || hook_selected sdk_ts; then
run_component sdk "$REPO_ROOT/sdk" "." "$REPO_ROOT/sdk/.githooks/pre-commit"
fi
if hook_selected sdk_swift; then
run_component sdk-swift "$REPO_ROOT" ".#apple" "$REPO_ROOT/sdk/swift/.githooks/pre-commit"
fi
if hook_selected ios_native; then
run_component maple-ios "$REPO_ROOT" ".#apple" "$REPO_ROOT/apps/maple-ios/.githooks/pre-commit"
fi
if hook_selected proxy; then
run_component proxy "$REPO_ROOT/proxy" "." "$REPO_ROOT/proxy/.githooks/pre-commit"
fi
Expand Down
91 changes: 91 additions & 0 deletions .github/workflows/ios-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Native iOS CI

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ios-native-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
ios_native: ${{ steps.classify.outputs.ios_native }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Select native app inputs
id: classify
env:
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
enable() { echo 'ios_native=true' >> "$GITHUB_OUTPUT"; }
case "$GITHUB_EVENT_NAME" in
pull_request) separator='...' ;;
push)
separator='..'
if [[ "$BASE_SHA" =~ ^0+$ ]]; then enable; exit 0; fi
;;
*) enable; exit 0 ;;
esac
if ! git cat-file -e "${BASE_SHA}^{commit}" || ! git cat-file -e "${HEAD_SHA}^{commit}"; then
enable; exit 0
fi
changed_paths="$(mktemp)"
trap 'rm -f "$changed_paths"' EXIT
if ! git diff --no-renames --name-only -z "${BASE_SHA}${separator}${HEAD_SHA}" > "$changed_paths"; then
enable; exit 0
fi
python3 scripts/ci/ios_native_change_detection.py < "$changed_paths" >> "$GITHUB_OUTPUT"

ios-native:
needs: changes
if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.ios_native != 'false') }}
runs-on: macos-26-xlarge
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- name: Select Xcode 26.5
run: |
set -euo pipefail
for xcode_app in \
/Applications/Xcode_26.5.app \
/Applications/Xcode_26.5.0.app \
/Applications/Xcode_26.5_beta_2.app \
/Applications/Xcode_26.5_beta.app
do
if [ -x "${xcode_app}/Contents/Developer/usr/bin/xcodebuild" ]; then
sudo xcode-select -s "${xcode_app}/Contents/Developer"
xcodebuild -version
exit 0
fi
done
echo "Xcode 26.5 is not installed on this runner." >&2
exit 1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
github-token: ""
- name: Check formatting and deterministic app tests
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh unit
- name: Compile iOS device and simulator packages
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh cross-build
- name: Build unsigned fixed-profile simulator app
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh simulator-build
- name: Build unsigned fixed-profile device app
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/ios-native.sh device-build
103 changes: 103 additions & 0 deletions .github/workflows/sdk-swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: SDK Swift CI

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: sdk-swift-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
sdk_swift: ${{ steps.classify.outputs.sdk_swift }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Select SDK and backend contract changes
id: classify
env:
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
enable() { echo 'sdk_swift=true' >> "$GITHUB_OUTPUT"; }
case "$GITHUB_EVENT_NAME" in
pull_request) separator='...' ;;
push)
separator='..'
if [[ "$BASE_SHA" =~ ^0+$ ]]; then enable; exit 0; fi
;;
*) enable; exit 0 ;;
esac
if ! git cat-file -e "${BASE_SHA}^{commit}" || ! git cat-file -e "${HEAD_SHA}^{commit}"; then
enable; exit 0
fi
changed_paths="$(mktemp)"
trap 'rm -f "$changed_paths"' EXIT
if ! git diff --no-renames --name-only -z "${BASE_SHA}${separator}${HEAD_SHA}" > "$changed_paths"; then
enable; exit 0
fi
python3 scripts/ci/hook_change_detection.py < "$changed_paths" > "$changed_paths.hook"
python3 scripts/ci/opensecret_change_detection.py < "$changed_paths" > "$changed_paths.backend"
if grep -qx 'sdk_swift=true' "$changed_paths.hook" || grep -qx 'integration=true' "$changed_paths.backend"; then
enable
else
echo 'sdk_swift=false' >> "$GITHUB_OUTPUT"
fi

sdk-swift:
needs: changes
if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.sdk_swift != 'false') }}
runs-on: macos-26-xlarge
timeout-minutes: 60
env:
OPENSECRET_DEV_POSTGRES: "0"
OPENSECRET_DEV_ENV: "0"
OPENSECRET_DEV_CONTAINERS: "0"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false
submodules: recursive
- name: Select Xcode 26.5
run: |
set -euo pipefail
for xcode_app in \
/Applications/Xcode_26.5.app \
/Applications/Xcode_26.5.0.app \
/Applications/Xcode_26.5_beta_2.app \
/Applications/Xcode_26.5_beta.app
do
if [ -x "${xcode_app}/Contents/Developer/usr/bin/xcodebuild" ]; then
sudo xcode-select -s "${xcode_app}/Contents/Developer"
xcodebuild -version
exit 0
fi
done
echo "Xcode 26.5 is not installed on this runner." >&2
exit 1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
with:
github-token: ""
- name: Check formatting and deterministic Swift tests
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh unit
- name: Build the independent Swift package
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh build
- name: Compile the iOS device and simulator library
run: nix develop --no-update-lock-file .#apple -c bash scripts/ci/sdk-swift.sh ios-build
- name: Verify Swift against disposable in-tree OpenSecret
run: nix develop --no-update-lock-file './services/opensecret?submodules=1' -c bash scripts/ci/sdk-swift-backend.sh
16 changes: 15 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ current source and tests take precedence over historical design documents.
- `apps/maple-agent/`: GPUI desktop-v2 prototype, ACP and proxy CLI. Read its
[guide](apps/maple-agent/AGENTS.md) and `$develop-maple-agent`. Its runtime and
update discovery are separate from Research and its existing Agent Mode.
- `sdk/`: TypeScript/React and Rust Maple SDKs for the OpenSecret backend. Read
- `apps/maple-ios/`: native SwiftUI app for iPhone and iPad, with an independent
unsigned validation lane and development identity. Read its
[guide](apps/maple-ios/AGENTS.md); its app core consumes `sdk/swift/`.
- `sdk/`: TypeScript/React, Rust and Swift Maple SDKs for the OpenSecret backend. Read
`$develop-opensecret-sdk` and the SDK documentation.
The native Apple package under `sdk/swift/` has its own contributor guide,
Xcode-based checks and independent version; it does not use the SDK Rust shell.
- `proxy/`: the separate OpenAI-compatible relay, also consumed by the desktop
app. Read `$develop-maple-proxy` and the proxy documentation.
- `services/updates/`: the updater Worker. Preserve its deployed identity,
Expand Down Expand Up @@ -121,6 +126,15 @@ manifest selects, so changing that manifest (a pin bump or a local link) is
what selects its lanes. Tests, docs, container-only inputs, and standalone
component lockfiles retain their independent lanes. Update the classifier and
its table-driven tests when the dependency graph or component layout changes.

Native iOS has its own `ios_native` hook component in the root `.#apple` shell
and `ios-native.yml` workflow. Its change selector includes the app's explicit
font inputs and root toolchain but does not select packaging for SDK-only
edits. Native app changes do not select Research packaging. The hook runs
format lint and deterministic package tests; CI also compiles the package for
both iOS targets and builds unsigned PR-profile apps. Runtime fixtures and
physical-device checks remain separate evidence.

The backend has its own root `opensecret-ci.yml` workflow and change selector;
`sdk-integration.yml` tests both SDKs against `services/opensecret/` from the
same checkout. Backend changes do not imply Research or Agent packaging.
Expand Down
7 changes: 7 additions & 0 deletions apps/maple-ios/.githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# The root orchestrator supplies the pinned Apple shell. No live test opt-ins,
# service startup, signing or app packaging belong in this local hook.
set -u
. "${MAPLE_HOOK_REPO_ROOT:?}/.githooks/lib/common.sh"
hook_require xcrun
hook_run bash "$MAPLE_HOOK_REPO_ROOT/scripts/ci/ios-native.sh" unit
5 changes: 5 additions & 0 deletions apps/maple-ios/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.build/
.swiftpm/
Config/Local.generated.xcconfig
*.xcuserstate
xcuserdata/
9 changes: 9 additions & 0 deletions apps/maple-ios/.swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": 1,
"lineLength": 120,
"indentation": { "spaces": 4 },
"rules": {
"AlwaysUseLowerCamelCase": false,
"UseTripleSlashForDocumentationComments": true
}
}
Loading