Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Summary

<!-- What does this PR do? One sentence is enough for small changes. -->

## Changes

<!-- Bullet-point list of what changed and why. -->

## Testing

<!-- How was this tested? (unit tests, manual smoke test, simulator, device, …) -->

## Parity checklist

> This project maintains a feature-parity table in [PARITY.md](../PARITY.md) that
> tracks which features are implemented on iOS vs Android. **Please answer the
> questions below before requesting review.**

- [ ] This PR **does not** add, change, or remove any user-facing feature on either
platform — no PARITY.md update needed.

**— OR —**

- [ ] This PR adds/changes/removes a user-facing feature. I have updated PARITY.md:
- Updated the status symbol(s) for the affected row(s).
- Added or updated "Notes" if the implementation is partial or has caveats.
- Removed or updated any rows in the "Known gaps" table that this PR closes.

<!-- If you changed only one platform, call out the gap explicitly so it doesn't
get lost. Example:
> Implemented Withdraw on Android. iOS already has this (✅). Updated PARITY.md. -->

## Related issues

<!-- Closes #... -->
207 changes: 207 additions & 0 deletions .github/workflows/e2e-cross-platform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
name: Cross-Platform E2E

# Runs on a schedule (not every PR) because these tests:
# - require a shared staging API environment
# - take longer and are more flake-prone than unit tests
# - cover cross-platform interoperability (e.g. register on iOS, accept
# beneficiary invitation on Android) that per-PR CI cannot cheaply cover
#
# The workflow can also be triggered manually (workflow_dispatch) to gate a
# release or debug a specific failure without waiting for the schedule.

on:
schedule:
# 04:00 UTC daily — off-peak, avoids clashing with business-hours CI runs.
- cron: "0 4 * * *"
workflow_dispatch:
inputs:
staging_api_url:
description: "Override the staging API base URL (leave blank to use the default)"
required: false
default: ""

# Prevent concurrent runs from racing on the same staging database.
concurrency:
group: e2e-cross-platform
cancel-in-progress: false

env:
# Default staging endpoint. Override via the workflow_dispatch input or by
# setting the E2E_API_BASE_URL repository secret.
E2E_API_BASE_URL: ${{ inputs.staging_api_url || secrets.E2E_API_BASE_URL || 'https://staging.ethos-protocol.app' }}

jobs:
# ---------------------------------------------------------------------------
# 1. Verify the staging API is reachable before spending simulator time.
# ---------------------------------------------------------------------------
preflight:
name: Staging API preflight
runs-on: ubuntu-latest
outputs:
api_url: ${{ steps.resolve.outputs.api_url }}
steps:
- name: Resolve API URL
id: resolve
run: echo "api_url=$E2E_API_BASE_URL" >> "$GITHUB_OUTPUT"

- name: Check staging API health
run: |
echo "Checking $E2E_API_BASE_URL/health …"
HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$E2E_API_BASE_URL/health")
if [ "$HTTP" != "200" ]; then
echo "❌ Staging API returned HTTP $HTTP — aborting E2E run."
exit 1
fi
echo "✅ Staging API is healthy (HTTP 200)."

# ---------------------------------------------------------------------------
# 2. iOS E2E — build the app and run the cross-platform test suite against
# the shared staging API via XCUITest / XCTest.
# ---------------------------------------------------------------------------
ios-e2e:
name: iOS E2E
runs-on: macos-latest
needs: preflight
defaults:
run:
working-directory: ios/EthosProtocol
env:
API_BASE_URL: ${{ needs.preflight.outputs.api_url }}
steps:
- uses: actions/checkout@v4

- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app

- name: Show toolchain
run: |
xcodebuild -version
swift --version

- name: Resolve package dependencies
run: xcodebuild -resolvePackageDependencies

- name: Install XcodeGen
run: brew install xcodegen

- name: Generate Xcode project
run: |
mkdir -p Xcode
xcodegen generate --project Xcode

# Inject the staging API URL as an Info.plist override so the app under
# test talks to staging, not whatever URL was baked into the build.
- name: Build E2E app for iOS Simulator
run: |
xcodebuild build-for-testing \
-project Xcode/EthosProtocol.xcodeproj \
-scheme EthosProtocol \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-skipMacroValidation \
API_BASE_URL="$API_BASE_URL"

- name: Run cross-platform E2E tests (iOS side)
run: |
xcodebuild test-without-building \
-project Xcode/EthosProtocol.xcodeproj \
-scheme EthosProtocol \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-only-testing:EthosProtocolE2ETests \
-skipMacroValidation \
E2E_API_BASE_URL="$API_BASE_URL" \
TEST_RUNNER_E2E_API_BASE_URL="$API_BASE_URL"

- name: Upload iOS E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: ios-e2e-results
path: ios/EthosProtocol/DerivedData/Logs/Test/*.xcresult
if-no-files-found: ignore

# ---------------------------------------------------------------------------
# 3. Android E2E — build the debug APK + test APK and run them on the
# Android Emulator via Instrumented tests (Espresso / UI Automator).
# ---------------------------------------------------------------------------
android-e2e:
name: Android E2E
runs-on: ubuntu-latest
needs: preflight
defaults:
run:
working-directory: android
env:
API_BASE_URL: ${{ needs.preflight.outputs.api_url }}
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Set up Android SDK
uses: android-actions/setup-android@v3

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew

# Build the app APK and the androidTest APK with the staging URL injected
# via a Gradle property so Instrumentation tests hit staging, not prod.
- name: Build debug APK + test APK
run: |
./gradlew assembleDebug assembleDebugAndroidTest \
-Pe2eApiBaseUrl="$API_BASE_URL"

- name: Enable KVM for hardware acceleration
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Run Android E2E tests on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: Nexus 6
script: |
cd android
./gradlew connectedDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.ethosprotocol.e2e.CrossPlatformE2ETest \
-Pe2eApiBaseUrl="$E2E_API_BASE_URL"

- name: Upload Android E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: android-e2e-results
path: android/app/build/reports/androidTests/connected/
if-no-files-found: ignore

# ---------------------------------------------------------------------------
# 4. Summary — single job reviewers can watch for pass/fail.
# ---------------------------------------------------------------------------
e2e-summary:
name: E2E summary
runs-on: ubuntu-latest
needs: [ios-e2e, android-e2e]
if: always()
steps:
- name: Report result
run: |
IOS="${{ needs.ios-e2e.result }}"
ANDROID="${{ needs.android-e2e.result }}"
echo "iOS E2E: $IOS"
echo "Android E2E: $ANDROID"
if [ "$IOS" != "success" ] || [ "$ANDROID" != "success" ]; then
echo "❌ One or more E2E suites failed."
exit 1
fi
echo "✅ All E2E suites passed."
116 changes: 116 additions & 0 deletions PARITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Feature Parity — iOS vs Android

This document tracks the implementation status of every user-facing feature across
the iOS and Android clients. Update it whenever a platform-specific change is made
(see [Contributing](#contributing)).

Last audited: 2026-07-27

---

## Status legend

| Symbol | Meaning |
|--------|---------|
| ✅ | Implemented and tested |
| ⚠️ | Partial — works but known gaps / untested edge cases |
| ❌ | Not implemented |
| 🚧 | In progress |

---

## Feature matrix

| Feature | iOS | Android | Notes |
|---------|-----|---------|-------|
| **Authentication** | | | |
| Passkey sign-in (WebAuthn) | ✅ | ✅ | iOS: ASAuthorizationPlatformPublicKeyCredentialProvider; Android: CredentialManager |
| Passkey registration | ✅ | ✅ | |
| Sign out | ✅ | ✅ | |
| **Vault management** | | | |
| List vaults | ✅ | ✅ | |
| Create vault | ✅ | ✅ | |
| Vault detail view | ✅ | ✅ | |
| Check-in | ✅ | ✅ | |
| Biometric confirmation for check-in | ✅ | ✅ | iOS: LocalAuthentication; Android: BiometricPrompt |
| **Funds** | | | |
| Deposit | ✅ | ❌ | Android has no DepositScreen (#87) |
| Withdraw | ✅ | ❌ | Android has no WithdrawScreen (#87) |
| Balance display (formatted XLM) | ✅ | ✅ | |
| **Beneficiary management** | | | |
| View beneficiary | ✅ | ✅ | |
| Update beneficiary | ✅ | ❌ | Android has no ManageBeneficiaryScreen (#87) |
| Beneficiary acceptance deep link | ✅ | ⚠️ | Android parses the deep link but does not call acceptBeneficiary() (#87/#109) |
| Beneficiary acceptance token forwarded | ✅ | ❌ | Android AcceptanceViewModel ignores the token param (#109) |
| **Stellar address validation** | | | |
| StrKey / CRC16-XModem checksum | ✅ | 🚧 | Android StellarAddress utility added in #113 |
| Validated in create-vault flow | ✅ | 🚧 | Android CreateVaultDialog wired in #113 |
| **Two-factor authentication** | | | |
| Enable 2FA (TOTP / SMS / Email) | ✅ | ✅ | |
| Disable 2FA | ✅ | ✅ | |
| Verify 2FA | ✅ | ✅ | |
| Correct copy: TOTP initial setup | ✅ | ✅ | Shows provisioning URI + secret |
| Correct copy: TOTP re-verify (no provisioning data) | ✅ | 🚧 | Fixed in #115; Android was showing "Scan URI" with no URI |
| Correct copy: SMS — "code sent to phone" | ✅ | ✅ | |
| Correct copy: Email — "code sent to email" | ✅ | ✅ | |
| **Push notifications** | | | |
| APNs / FCM device token registration | ✅ | ✅ | |
| TTL expiry warning notification | ✅ | ✅ | |
| Check-in reminder (scaled lead time) | ✅ | ❌ | Android NotificationHelper sends a generic reminder, no lead-time scaling (#TBD) |
| Actionable "Check In" notification action | ✅ | ❌ | Android does not set up a CHECK_IN notification action (#TBD) |
| **Offline support** | | | |
| Network connectivity monitor | ✅ | ✅ | |
| Offline read cache (SHA-256 keyed) | ✅ | ✅ | |
| Offline check-in queue + WorkManager retry | ❌ | ✅ | iOS has no persistent offline queue; mutations fail with an error banner |
| Offline queue badge / notification | ❌ | ✅ | |
| **Widget** | | | |
| Home-screen vault TTL widget | ✅ | ✅ | iOS: WidgetKit TTLWidget; Android: VaultStatusWidget (Glance) |
| TTL-aware refresh policy | ✅ | ❌ | Android widget polls at a fixed interval; no urgency scaling (#TBD) |
| Widget urgency selection (which vault to surface) | ❌ | ❌ | Both platforms always show the first vault (#TBD) |
| **WebSocket / real-time** | | | |
| Live vault updates via WebSocket | ❌ | ❌ | Neither client implements WebSocket yet (#TBD) |
| **Background refresh** | | | |
| Background app refresh (TTL polling) | ✅ | ✅ | iOS: BGAppRefreshTask; Android: WorkManager |
| **Universal / deep links** | | | |
| Vault invitation link | ✅ | ✅ | |
| Beneficiary acceptance link | ✅ | ⚠️ | See beneficiary token gap above |
| Vault action links (check-in, withdraw, …) | ✅ | ✅ | |
| Deep-link input validation (path traversal, length) | ✅ | ✅ | |
| **iCloud / cross-device sync** | | | |
| iCloud KV vault ↔ credential sync | ✅ | ❌ | Android has no equivalent cloud sync |

---

## Known gaps (summary)

These are the divergences identified during the audit that led to issue #116.
Each gap has a tracking issue; fix it on the lagging platform and update this table.

| Gap | Platform missing feature | Tracking |
|-----|--------------------------|---------|
| Deposit / Withdraw screens | Android | #87 |
| Manage Beneficiary screen | Android | #87 |
| Beneficiary acceptance token forwarded in deep link | Android | #87 / #109 |
| TOTP re-verify copy ("Scan URI" shown without URI) | Android | #115 |
| Stellar address validation (StrKey + checksum) | Android | #113 / #71 |
| Check-in reminder lead-time scaling | Android | TBD |
| Actionable push notification action (CHECK_IN) | Android | TBD |
| Offline check-in queue | iOS | TBD |
| TTL-aware widget refresh policy | Android | TBD |
| Widget urgency / vault selection | Both | TBD |
| WebSocket real-time vault updates | Both | TBD |
| iCloud / cross-device sync | Android | TBD |

---

## Contributing

When you open a PR that adds, changes, or removes a user-facing feature on
**either** platform, update the table above before requesting review:

1. Find the affected row(s).
2. Change the status symbol for the platform you modified.
3. Add a "Notes" entry if the change is partial or has known caveats.
4. If you close a gap listed in the "Known gaps" section, remove or update that row.

The PR template will prompt you to do this automatically.
Loading