Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7ce455a
Checking
jongan69 Aug 10, 2026
540202b
Disclose safety screening in-app and harden policy consent
jongan69 Aug 10, 2026
bf89b30
fix: address all CodeRabbit review comments on dev
jongan69 Aug 10, 2026
4ad9969
Merge remote-tracking branch 'origin/main' into dev
jongan69 Aug 13, 2026
0c04f7c
Differentiate private introductions
jongan69 Aug 13, 2026
05e34f5
Merge remote-tracking branch 'origin/dev' into dev
jongan69 Aug 20, 2026
a459abd
feat: gate future Plus monetization
jongan69 Aug 20, 2026
2cedb18
docs: correct release handoff state
jongan69 Aug 20, 2026
9c73f78
Publish Expo web preview
jongan69 Aug 29, 2026
679a4c3
Enable encrypted web accounts
jongan69 Aug 29, 2026
278dcf4
Keep Nostr web events CSP-safe
jongan69 Aug 29, 2026
e6e2bfb
Secure static web routes
jongan69 Aug 29, 2026
515dde3
Avoid web route hydration errors
jongan69 Aug 29, 2026
d51c496
Polish the web shell and introductions
jongan69 Aug 29, 2026
e995182
Verify production web exports in CI
jongan69 Aug 29, 2026
f5bc1f9
Make web account deletion reliable
jongan69 Aug 29, 2026
5be2a79
Handle profileless match records
jongan69 Aug 29, 2026
ac5cd8f
Guard protected web deep links
jongan69 Aug 29, 2026
90ea66a
Complete core web dating flows
jongan69 Aug 29, 2026
510c6ac
Resume persisted onboarding drafts
jongan69 Aug 29, 2026
f7b0f0d
Match web shell to app theme
jongan69 Aug 29, 2026
c9659b3
Lock persisted onboarding routes
jongan69 Aug 29, 2026
b845890
Keep unlocked onboarding local
jongan69 Aug 29, 2026
cb3f90d
Decouple web route authorization
jongan69 Aug 29, 2026
5aaa55c
Lock onboarding only on reload
jongan69 Aug 29, 2026
e4811eb
Resume onboarding after browser unlock
jongan69 Aug 29, 2026
ec3883d
Open Passport after onboarding
jongan69 Aug 29, 2026
33228bf
Add lifetime Plus themes
jongan69 Aug 29, 2026
c505b83
Draft Shipaton submission
jongan69 Aug 29, 2026
e86f2fb
Record RevenueCat project
jongan69 Aug 29, 2026
aa3a61c
Record iOS candidate evidence
jongan69 Aug 29, 2026
8f85188
Clarify Shipaton eligibility
jongan69 Aug 29, 2026
55dfa4b
Record App Review submission
jongan69 Aug 29, 2026
edd512f
Prepare final Devpost assets
jongan69 Aug 29, 2026
1d91f58
Record Devpost screenshot upload
jongan69 Aug 29, 2026
49bc15d
Complete Devpost screenshot gallery
jongan69 Aug 29, 2026
b2ae396
Remove release diff whitespace
jongan69 Aug 29, 2026
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
EXPO_PUBLIC_OPENDATING_RELAY_URL=wss://opendating-relay.jonathang132298.workers.dev
EXPO_PUBLIC_OPENDATING_INFO_URL=https://opendating-relay.jonathang132298.workers.dev
EXPO_PUBLIC_OPENDATING_PROTOCOL_VERSION=0.1
# Both gates must be true before a paywall can appear. Keep feature readiness
# false until paid customization/convenience features and safety gates ship.
EXPO_PUBLIC_REVENUECAT_ENABLED=false
EXPO_PUBLIC_OPENDATING_PLUS_FEATURES_READY=false
EXPO_PUBLIC_REVENUECAT_IOS_API_KEY=
EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEY=
21 changes: 20 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,25 @@ jobs:
- run: bun install --frozen-lockfile
- run: bun run lint

web-export:
name: Web export
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: package.json
- run: bun install --frozen-lockfile
- run: bun run export:web
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: web-export
path: dist
if-no-files-found: error
retention-days: 7

test:
name: Tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -112,7 +131,7 @@ jobs:
# Full check passes only when all release checks pass.
ci:
name: CI
needs: [typecheck, lint, test, security, dependency-review, sbom]
needs: [typecheck, lint, web-export, test, security, dependency-review, sbom]
runs-on: ubuntu-latest
steps:
- run: echo "All checks passed"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ coverage/
*.tmp
screenshots/*.png
screenshots/app-store/

# Local brand source exports; app-ready assets live under assets/brand.
/BrandAssetPack/animations/
142 changes: 70 additions & 72 deletions bun.lock

Large diffs are not rendered by default.

206 changes: 206 additions & 0 deletions devpost-submission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# OpenDating — RevenueCat Shipaton 2026

Status: draft only. The public project page is published, but OpenDating has not
been entered into Shipaton judging.

The live web build is the same Expo application and is the public demo. It does
not replace the native Store URL for a standard Shipaton entry: Shipaton states
that web apps and TestFlight builds are not eligible. The only no-Store path is
the Next Gen Award for an eligible student using a verifiable academic email,
public repository, and demo video.

## Project details

- Project: OpenDating
- Tagline: Deliberate private introductions on an open protocol.
- Devpost draft: https://devpost.com/software/opendating
- Live web preview: https://opendating-mobile.expo.app
- Source: https://github.com/jongan69/opendating-mobile
- Platform: iOS (iPhone and iPad)
- Built with: Expo, React Native, TypeScript, RevenueCat, Nostr, Cloudflare Workers, Cloudflare Workers AI

## Description

### Inspiration

Dating apps often ask people to trade privacy for participation: a phone number,
precise location, readable conversations, and an engagement-optimized swipe
feed. OpenDating explores a smaller, calmer model where members own their
identity and understand each disclosure before making a choice.

### What it does

OpenDating presents one deliberate introduction at a time. Each introduction
explains the visible compatibility facts behind it and includes a privacy
receipt showing what is shared, withheld, and unlocked only after mutual
interest. Interest is private, and a conversation opens only after both people
choose each other.

Members create a self-owned account without email, phone number, or social
login. Exact GPS is reduced on-device to an approximate 5 km area before
discovery. Matched conversations use end-to-end encryption. The app also
includes blocking, private reporting, visibility controls, recovery-key backup,
and permanent account deletion.

OpenDating Plus is an optional $4.99 lifetime purchase powered by RevenueCat.
It unlocks Sage, Ocean, and Plum accent themes. It never changes discovery,
ranking, matching, messaging, recovery, deletion, or safety.

### How we built it

The client uses Expo Router, React Native, and strict TypeScript. Nostr provides
the identity and cryptographic building blocks, while the OpenDating protocol
defines profiles, discovery, private choices, matches, moderation, and encrypted
messages. A Cloudflare Worker operates the relay and media services. RevenueCat
validates the lifetime Plus entitlement using an opaque billing identifier;
profile, location, recovery-key, safety, and message content is not sent to
RevenueCat.

The browser build uses an encrypted local identity vault so the real app can be
tested without a native install. The private recovery key is encrypted with
AES-256-GCM using a PBKDF2-derived browser-lock key and is never persisted in
plaintext.

### Challenges

The hardest boundary was making a decentralized identity understandable and
recoverable without turning the recovery key into a normal password. We also
had to keep coarse location, encrypted messaging, moderation, browser storage,
and StoreKit entitlement state explicit across degraded-network and locked
states.

Apple declined the first iOS candidate under Guideline 4.3(b). We replaced the
swipe-first experience with deliberate private introductions, privacy receipts,
and a Privacy Passport before preparing a fresh release.

### Accomplishments

- One explained introduction instead of an endless swipe feed
- A live Privacy Passport for identity, visibility, and disclosure boundaries
- Coarse-location discovery without transmitting raw GPS
- Match-only end-to-end encrypted conversations
- Operational report, block, unmatch, feedback, and deletion paths
- A browser app with an encrypted recovery-key vault
- A non-consumable Plus product that sells customization, not dating outcomes

### What we learned

Privacy works better as an interaction than as a policy page. Showing a privacy
receipt at the moment of an introduction makes the boundary understandable.
Likewise, a portable identity needs visible backup, lock, and deletion controls
to feel trustworthy rather than merely decentralized.

### How we used AI

Cloudflare Workers AI screens only profile display names and bios for obvious
safety-policy violations before publication. It does not process photos,
location, recovery keys, likes, matches, reports, or encrypted messages. Codex
was used to inspect the repository, implement and review changes, run tests,
and verify deployment and release artifacts.

### What's next

Wait for App Review of iOS 0.1.1 (5), complete the App Store metadata for the
lifetime purchase, run purchase and restore on a physical iOS device, and add
the resulting Store URL, promo code, screenshot, and public demo video to the
Shipaton entry. Federation and independent service providers remain
post-hackathon work.

## Testing instructions

1. Open https://opendating-mobile.expo.app in a modern HTTPS browser.
2. Create a browser account with a 12-character-or-longer browser-lock
passphrase, or import a disposable Nostr `nsec`/hex recovery key.
3. Complete onboarding, then verify Privacy Passport, introductions, profile,
visibility, blocking/reporting, matches, browser lock, and recovery-key
export/import.
4. Reload the page and verify the encrypted identity locks before reconnecting.
5. On iOS, use App Store build 0.1.1 (5) and verify the lifetime Plus purchase,
restore, and entitlement removal with a disposable StoreKit account.

Do not use a personal recovery key or include private keys, exact location, or
message content in screenshots or bug reports.

## Public demo and repository

- Live app: https://opendating-mobile.expo.app
- Source: https://github.com/jongan69/opendating-mobile
- Demo video: pending a public YouTube or Vimeo upload, two minutes or less

## Demo video outline

- 0:00–0:15 — problem and OpenDating's private-introduction model
- 0:15–0:45 — create/import account and coarse-location boundary
- 0:45–1:15 — Privacy Passport, introduction reasons, and privacy receipt
- 1:15–1:35 — mutual match, encrypted messaging, report/block controls
- 1:35–1:50 — $4.99 lifetime Plus themes and RevenueCat restore
- 1:50–2:00 — live web app, open protocol, and closing result

## Screenshot shot list

- Published gallery: six live-web screenshots at exactly 1179 × 2556, without
device frames or user data:
- `screenshots/devpost/01-welcome-1179x2556.png`
- `screenshots/devpost/02-browser-lock-1179x2556.png`
- `screenshots/devpost/03-create-account-1179x2556.png`
- `screenshots/devpost/04-import-recovery-key-1179x2556.png`
- `screenshots/devpost/05-privacy-boundaries-1179x2556.png`
- `screenshots/devpost/06-terms-1179x2556.png`
- Pending IAP review capture: Plus lifetime-purchase screen

## Known limitations

- The public web app is a functional preview, but standard Shipaton eligibility
requires the fully published native Store listing.
- OpenDating Plus remains unavailable until Apple accepts its missing review
metadata and the purchase is proven on a physical device.
- The relay and protocol are an early public implementation without federation
or a production availability SLA.

## Shipaton submission fields

- Includes App Icon (27378): `BrandAssetPack/icons/ios-app-icon-1024.png` is
already the project thumbnail; confirm the checkbox during final review
- Includes screenshot (27379): six exact-size screenshots uploaded and verified
in the public Devpost gallery
- First Version Date Confirmation (27380): answer only after the revised build is published
- Is Staff or Sponsor (27381): false
- App type (27382): iOS (iPhone and/or iPad)
- Published App Store URL (27383): pending
- RevenueCat project ID (28118): `proj3363ecdc`
- Promo code (28135): pending App Store promo-code creation
- HAMM Award (27388): OpenDating uses one transparent lifetime purchase for
cosmetic themes. A non-consumable purchase fits a privacy-first utility better
than recurring access fees or advertising, and core dating and safety features
remain free.
- Peace Prize (27389): OpenDating minimizes sensitive data collection, keeps
choices private until mutual, encrypts matched conversations, and gives members
direct visibility, recovery, reporting, blocking, and deletion controls.
- Design Award (27391): The app replaces swipe mechanics with one deliberate
introduction, visible compatibility reasons, a privacy receipt, and a Privacy
Passport. The interface uses calm platform-aware surfaces and accessible
light, dark, and accent themes.
- Additional notes (27392): The public web preview demonstrates the application
flow, but the standard Shipaton entry will use the published iOS Store URL and
a physical-device demo video.

## Required completion evidence

- [x] Description drafted
- [x] 1024 × 1024 icon exists
- [x] Devpost project, write-up, links, and app icon published
- [x] Production web preview deployed
- [x] Cloudflare Worker responds in production
- [x] App Store non-consumable created
- [x] RevenueCat project, entitlement, offering, and product connected
- [x] Fresh iOS 0.1.1 build 5 uploaded and valid in App Store Connect
- [x] Build 5 attached to App Store version 0.1.1 and internal TestFlight QA
- [ ] Purchase, restore, and entitlement verified on a physical iOS device
- [x] Six 1179 × 2556 screenshots captured without device frames or user data
- [x] Six-image gallery uploaded and verified on the public Devpost project
- [ ] Public YouTube or Vimeo demo, two minutes or less
- [x] Revised iOS 0.1.1 build 5 submitted and waiting for App Review
- [ ] Revised app fully published on the App Store
- [ ] App Store promo code created for judges
- [x] Devpost project update confirmed by Jonathan
- [ ] Final Devpost submission explicitly confirmed by Jonathan
39 changes: 23 additions & 16 deletions docs/APP-REVIEW-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copy the **Reviewer Notes** block at the bottom into the Notes field of the App
Review Information section in App Store Connect. The rest of this document is
the working source for that block.

**Last updated:** 2026-08-09 · **App version:** 0.1.1
**Last updated:** 2026-08-29 · **App version:** 0.1.1

---

Expand All @@ -17,8 +17,9 @@ beginning with app launch, showing the typical user flow. Must include account
registration, login, and deletion; user-generated content with reporting and
blocking; and every permission prompt.

There is no paid content, purchase, or subscription flow in this app, so that
section does not apply. Say so explicitly in the reply rather than omitting it.
Show the optional one-time OpenDating Plus purchase, selecting a custom accent,
and Restore Purchases. The recording must use an Apple sandbox account and must
not show credentials or payment details.

## 2. Devices and operating systems tested

Expand Down Expand Up @@ -100,21 +101,21 @@ review region.
|---|---|---|
| OpenDating relay (Cloudflare Workers) | Sole backend. Profile storage, discovery matching, match/like routing, encrypted message transport, reports, blocks, account deletion. | Operated by the developer at `wss://opendating-relay.jonathang132298.workers.dev`. Service endpoints are discovered at runtime from the relay's public service document. |
| Cloudflare Workers AI | Automated safety screening for profile display names and bios before publication. | The developer-operated relay sends this profile text to Cloudflare's `@cf/meta/llama-3.2-3b-instruct` model. Photos are not sent, and it does not process encrypted messages. Disclosed in-app on the onboarding privacy screen and at Settings → Privacy. |
| RevenueCat | Optional one-time OpenDating Plus purchase and entitlement status. | Receives an opaque, domain-separated billing identifier and Apple purchase/entitlement data. It does not receive profile text, photos, location, recovery keys, likes, matches, reports, or messages. |
| Expo Application Services (EAS) | Build and submission tooling only. | Build-time only. Not contacted by the shipped app at runtime. |

**Not used:** no payment processor, no in-app purchases or subscriptions, no
advertising SDK, no third-party analytics or crash reporting, no third-party
authentication provider, no data broker, and no App Tracking Transparency
prompt (the app does not track).
**Not used:** no subscriptions, advertising SDK, third-party analytics or crash
reporting, third-party authentication provider, data broker, or App Tracking
Transparency prompt (the app does not track).

The app's privacy manifest declares `NSPrivacyTracking: false` with no tracking
domains.

## 6. Regional differences

**The app functions identically in all regions.** There is no region-locked
content, no country-specific feature gating, no regional pricing (the app is
free everywhere), and no geographic availability restriction in the app itself.
content or country-specific feature gating. The app is free; Apple localizes the
optional one-time Plus price by storefront.

A country code derived from the user's coarse location is used only to scope
discovery to a sensible area. It does not enable or disable features.
Expand All @@ -135,7 +136,7 @@ enforced at a minimum of 18, and the App Store age rating is set to 17+.
**User-generated content controls,** as required by Guideline 1.2:
- Terms of Service and Community Standards are shown during onboarding and at
Settings → Terms. Profile creation is disabled until the member explicitly
accepts the August 9, 2026 policy version; version and acceptance time are
accepts the August 29, 2026 policy version; version and acceptance time are
recorded locally for the account.
- Any profile can be reported from the profile screen or from a chat.
- Any user can be blocked; blocks take effect immediately on-device and are
Expand All @@ -154,8 +155,8 @@ SCREEN RECORDING
A physical-device recording on the latest iPadOS is attached to the App Review
reply. It begins with app launch and shows account access and registration,
policy consent, permission prompts, discovery, reporting, blocking, matching,
encrypted messaging, and account deletion. There are no purchases or
subscriptions to demonstrate.
encrypted messaging, the one-time Plus purchase and restore flow, and account
deletion.

DEVICES TESTED
- Samsung SM-A166U1, Android 16: physical-device onboarding and discovery.
Expand Down Expand Up @@ -204,7 +205,10 @@ PERMISSIONS AND PURCHASES
for nearby discovery; exact GPS is not transmitted.
- Photo library: adds profile photos.
- No App Tracking Transparency prompt; the app does not track.
The app is free with no paid content, purchase, or subscription.
- OpenDating Plus is an optional $4.99 one-time purchase that unlocks Sage,
Ocean, and Plum app accents. It does not affect discovery, ranking, matching,
messaging, recovery, deletion, or safety. Restore Purchases is available on
the same screen. There is no subscription.

EXTERNAL SERVICES
- Developer-operated OpenDating relay on Cloudflare Workers: profile storage,
Expand All @@ -214,14 +218,17 @@ EXTERNAL SERVICES
of profile display names and bios before publication. Photos are not sent to
it and it does not process encrypted messages. This is disclosed to the user
in-app on the onboarding privacy screen and at Settings -> Privacy.
- RevenueCat: validates the optional lifetime Plus entitlement using an opaque
billing identifier. It does not receive profile, location, key, safety, or
message content.
- Expo Application Services: build and submission tooling only; not contacted
by the shipped app at runtime.
No payment processor, advertising network, analytics provider, data broker, or
third-party authentication service is used.
No advertising network, analytics provider, data broker, or third-party
authentication service is used.

REGIONS
The app behaves consistently across all regions. There is no region-locked
content, feature gating, regional pricing, or in-app availability restriction.
content or feature gating. Apple localizes the optional one-time Plus price.

REGULATED SERVICES AND THIRD-PARTY MATERIAL
OpenDating is a dating/social-networking app, not a financial, medical, legal,
Expand Down
Loading