PRD-015 — Mobile App · Capacitor wrapper
Status · Draft v0.3 (4 blocking decisions resolved 2026-05-16) Date · 2026-05-16 Owner · Marko Audiences · curious learner, STEM student, educator, space enthusiast Closes into · RFC-018 (Capacitor mobile integration) Slice gate · v0.8 (post-RFC-017 surface hotspots in v0.7) Why this is a PRD · Orrery already runs in mobile browsers, but a browser tab is the wrong container for the product. PRD-015 commits to distributing Orrery as a native Android + iOS app via Capacitor, with the existing SvelteKit web app as the source of truth and the native shell adding only what Google Play and the App Store require: an icon, a splash, a privacy policy, a system share sheet, and the ability to install from a store rather than from a URL bar. The user-value argument is reach (the audience reaches for their phone, not their laptop) and platform legitimacy (an installed app is treated differently from a bookmarked tab). The PRD locks scope to "wrap and ship the same product"; no native screens, no rewrite, no parallel codebase.
Problem
Orrery is a browser-first application that runs well in a mobile browser — Three.js renders in mobile Safari and Chrome WebView, the v0.6 mobile pass shipped a hamburger nav, horizontal-flow layer chips on /explore /earth /moon /mars, a collapsible /fly HUD, a compact /science rail, and footer/CTA clearance — but a browser tab is still the wrong container.
The 3D solar system, the mission arc, the porkchop plot, the cislunar Earth-centred view, the ISS module-pickability, the Mars surface map: these are experiences, not pages. They belong on a home screen and in an app drawer.
Mobile browser distribution also limits reach. App-store presence is the discovery channel for Orrery's audience — students, curious people, educators. The curious person who watched a Mars landing reaches for their phone, not a laptop, and they reach for the app store, not a search engine.
There is also a capability gap. PWA install on iOS is hidden behind Safari's share sheet; Android's "Add to home screen" is more discoverable but still requires a browser visit first. A native wrapper is also the prerequisite for mobile-first features — system share sheet for mission arcs, haptics on trajectory events, native deep links — that a browser tab can't surface.
Goal
Ship Orrery as a native application on Android and iOS in parallel, distributed via Google Play and the App Store, using Capacitor as the wrapper layer.
The web application is the product. Capacitor provides only the shell.
Sequencing within the parallel build. Both platforms are co-equal targets; public store releases happen on the same day. During development, Android Internal Testing comes online faster than TestFlight (no provisioning-profile rigmarole, no Apple review for internal testers, $25 one-time vs $99/year), so the practical iteration loop runs Android-first while iOS builds in lock-step for parity testing.
Non-goals
- Not a rewrite. The SvelteKit application is the source of truth. Capacitor wraps the static
build/output — it does not replace any of it. - Not new screens. No mobile-only routes; the existing 11 primary nav routes (
/explore·/plan·/fly·/missions·/earth·/moon·/mars·/iss·/tiangong·/science·/fleet) plus/credits/library/postersadapt to mobile viewports as already shipped in v0.6. - Not native UI components. No UIKit, no Jetpack Compose, no native nav. The Capacitor WebView renders the SvelteKit app as-is.
- Not background processing. No background fetch, no push notifications, no background location. Launch-window countdowns are a Phase 3 consideration.
- Not enterprise distribution. Public Google Play + public App Store only. MDM / enterprise profiles out of scope.
- Not a parallel data layer. Mission JSON ships bundled with the app. Updates ride on app-version submissions plus the existing PWA service worker (autoUpdate per the May 2026 PWA refresh).
Requirements
Must have
| ID | Requirement |
|---|---|
| M1 | Installs from Google Play on Android 10+ and the App Store on iOS 16+ |
| M2 | All 14 routes (11 primary + 3 read-only) render correctly at 360 px–412 px viewport widths (common Android range) |
| M3 | All 14 routes render correctly at 375 px–430 px viewport widths (iPhone SE through iPhone 15 Pro Max) |
| M4 | Three.js r128 (per ADR-001) renders in Chromium WebView (Android 10+) and WKWebView (iOS 16+) for all 7 3D scenes: /explore, /fly heliocentric, /fly cislunar (per ADR-058), /earth, /moon, /mars, /iss, /tiangong |
| M5 | App functions fully offline. Already true via the v0.6 PWA service worker (ADR-029); must be verified in the Capacitor container on both platforms |
| M6 | The Lambert worker (per ADR-022) runs correctly in the WebView context on both platforms |
| M7 | Touch interactions work: orbit (one-finger drag), zoom (pinch), tap (object selection), per-route hit-testing via window.__pickAt (ADR-056) |
| M8 | Bottom-sheet panel pattern (already shipped per ADR-018) is verified in the Capacitor WebView |
| M9 | App icons + splash screens meet Google Play + App Store submission requirements |
| M10 | Privacy policy page exists and is linked from each store listing |
| M11 | Bundle-size strategy is documented + measured. Naive build is ~355 MB (fleet galleries + science diagrams + 14-locale overlays + textures); RFC-018 § bundle-strategy proposes a stripped on-device baseline with optional fetch-on-demand for large galleries. Required before either store submission |
| M12 | The single orrery_locale cookie permitted by ADR-057 works in the Capacitor WebView on both platforms (or a Capacitor Preferences shim is added) |
Should have
| ID | Requirement |
|---|---|
| S1 | Native share-sheet integration via @capacitor/share — "Share this mission arc" surfaces a native Android / iOS sheet |
| S2 | Haptic feedback on mission events (TLI confirmed, Mars arrival) via @capacitor/haptics |
| S3 | Status bar / system bar styling matches --color-bg (#04040c) on both platforms — no white flash between launch and first paint |
| S4 | Safe-area insets respected — Android gesture nav bar + status bar; iOS notch + Dynamic Island + home indicator |
| S5 | App state preserved across background/foreground cycle — no full reload, WebGL context restored if lost (more critical on iOS WKWebView than Android Chromium) |
| S6 | Deep links work — orrery://fly?mission=curiosity opens the mission-arc screen directly. Android App Links + iOS Universal Links (https://chipi.github.io/orrery/fly?mission=curiosity) deferred to Phase 3 |
Will not have (this phase)
- Push notifications for launch-window alerts
- Background fetch for mission data updates (PWA autoUpdate covers refresh-on-next-launch)
- Cloud sync for saved missions (Google Drive / iCloud)
- Wear OS / Apple Watch companion
- Widgets (Android / iOS)
- In-app purchases of any kind
- AR / spatial-anchored views (separate ADR if pursued)
What's already shipped (mobile-readiness inventory)
Tasks delivered in the v0.6 mobile-pass that PRD-015 inherits:
| Shipped | Where | Notes |
|---|---|---|
| Hamburger nav drawer ≤ 500 px | commit 5f4cc05a | All 11 primary routes accessible; locale picker + lens + contrast toggles next to it |
| Horizontal layer-filter chip flow | commit 5f4cc05a | First chip stays anchored; remaining chips wrap rightward instead of stacking vertically |
/fly HUD collapsible panels | commit 4addaca9e + 0020f128b | Five panel toggles in a top-right strip — HUD, CAPCOM, Flight Director, Layers, Conics |
/science compact rail | task #117 | Right rail shrinks from 70 % viewport to a tight nav strip |
| Footer / CTA clearance | task #115 | Persistent bottom-right footer doesn't overlap landing-page or /plan action buttons |
| Per-planet pill colors | task #112 | Mars red / Earth blue / Moon grey / Explore teal — visual distinction without relying on placement |
| Bottom-sheet panel pattern | ADR-018 | Right drawer on desktop; bottom sheet ≤ 768 px, swipe-down to dismiss |
| Touch readiness signals | ADR-056 | window.__pickAt(x, y) test hook + data-route-ready + data-loading attributes on every canvas route |
The mobile viewport adaptation work that earlier drafts of this PRD pre-empted is already done. PRD-015's primary work is the wrap + distribute layer.
Distribution
Android — Google Play (first to ship)
Less strict than Apple for educational content; faster to iterate on.
- Target API level 35 (Android 15) as of the August 2025 Play requirement; revisit before submission.
- Privacy policy required (links to a page hosted on
chipi.github.io). - Listing category: Education.
- Internal Testing track is available without a public listing — useful for the first physical-device runs before promoting to Production.
- Closed Testing for an external test cohort (≥ 12 testers, 14-day opt-in) when needed before Production.
Expected review cycle: 1 submission, automated review passes within hours for educational content.
iOS — App Store
Apple scrutinises web-wrapper apps. Acceptance criteria for a web-app-as-native:
- The app must provide clear educational value beyond pointing at a URL. Orrery qualifies: 11 primary routes, 85-section science encyclopedia, 137-entry fleet, 71 hand-authored SVG diagrams, real Keplerian + Lambert physics, fully offline-capable.
- Must not be a thin wrapper around a website that could be accessed in Safari. Orrery's offline capability + native integrations (Share, Haptics) + bundled-data model distinguish it from a Safari bookmark.
- Privacy policy required. Orrery collects no personal data per ADR-057 + the project's privacy stance; the policy states this explicitly.
- Listing category: Education primary, Reference secondary.
Expected review cycle: 1–2 submissions before approval. Apple commonly requests screenshots proving offline functionality.
Store metadata (both)
- App name: Orrery — Solar System Explorer
- Subtitle (iOS) / Short description (Play): Mission simulator + orbital mechanics
- Long description: adapted from the docs landing (
docs/index.md) - Screenshots: 8–10 per device class (Android phone, Android tablet, iPhone, iPad) — pull from
docs/screenshots/(already curated for the README + user guide) - Privacy policy URL: hosted at
https://chipi.github.io/orrery/privacy - Keywords (iOS): solar system, orbital mechanics, Mars, mission simulator, space, NASA, astronomy, education, Apollo, ISS
Success criteria
| Criterion | Measure |
|---|---|
| Ships on both stores | Google Play listing live + App Store listing live |
| Offline verified | All 14 routes function with no network on a physical device (airplane mode test) |
| Three.js verified | 60 fps orbit rendering on a mid-range Android (Snapdragon 778G or equivalent) and iPhone 12 (A14); 30 fps acceptable on /iss + /tiangong (heaviest scenes) |
| Lambert worker | /plan porkchop renders within 5 s on the target devices for a single destination |
| Touch interactions | Orbit, zoom, object selection, panel-tab switching all work correctly on touch screens on both platforms |
| Cislunar view | /fly?mission=apollo11 switches to the cislunar Earth-centred camera correctly per ADR-058 |
| App store rating | No forced rejections due to wrapper-policy violations |
| Bundle size | Install size ≤ 80 MB after the bundle-strategy work in RFC-018 |
| PWA continuity | Service worker registers and autoUpdate works inside the WebView so bundled-asset refresh on next launch is silent (matches the May 2026 PWA refresh decision) |
Dependencies + sequencing
| Dependency | Status | Notes |
|---|---|---|
| RFC-017 / PRD-014 (Surface Hotspots) shipped | Required first | Mobile distribution should ship the best-case desktop experience of landing-site exploration first, not a degraded mid-pass version. v0.7 lands the Mars/Moon hi-res imagery + per-mission 3D models; v0.8 wraps for mobile |
| Privacy policy page | Required before either store submission | Static HTML page in static/privacy.html (or a SvelteKit /privacy route) — content is short because no data is collected |
| Google Play developer account | Required | $25 one-time |
| Apple Developer account | Required | $99 / yr |
| App icon design | Required | See open question Q4 |
| Bundle-size reduction strategy | Required before either store submission | RFC-018 § bundle-strategy proposes the cuts |
| Mobile-viewport UXS update | Optional | The v0.6 mobile work shipped without a dedicated UXS; documenting the patterns retroactively (similar to how ADR-052/053/054 documented Fleet retrospectively) is a clean-up task |
Resolved decisions
All four blocking questions resolved 2026-05-16. Tracked here for the audit trail; new questions go below.
Bundle-size strategy — RESOLVED. Ship RFC-018 §4 slim plan: 4K planet textures (mobile only), lazy-load 11 non-default locales, 256-px fleet thumbnails with hero-quality images streaming from
chipi.github.ioon tap. Target ~85 MB installed — well under PRD M11 ceiling (150 MB) and iOS OTA cap (200 MB). Three Vite-config branches gated byMOBILE=1; the browser deploy is unaffected.Data-update mechanism — RESOLVED. PWA service worker is disabled under Capacitor for v1.0 (RFC-018 §8.2). Content updates require a Play / App Store release. Trade-off accepted to dodge App Store §2.5.2 review surprises on the first ship; revisit network-aware SW (RFC-018 §8.1) as a v1.1 ADR if content-update friction surfaces.
Tablet — RESOLVED. Ship as-is for v1.0. Desktop layout fits 1024+ reasonably (
/explore,/missions,/science,/fly); no new breakpoint code. Mark tablets as "supported, not optimised" in the Play / App Store listings. Revisit after first physical-device testing.iOS code-signing — RESOLVED. Marko owns the Apple Developer membership (personal account, $99/yr). Listed as the developer on the App Store. Resolution applies whenever iOS submission begins (Android Google Play ships first regardless).
Open questions
App icon design. The Bebas Neue wordmark doesn't translate to a 60×60 px home-screen icon. Options:
- Stylised orrery-as-clockwork-model (concentric rings + a planet)
- Lambert porkchop shape (the V-shape that's the project's most distinctive visual)
- Simple star + ring on
#04040c
Open design task; not blocking until after v0.7 work lands.
Internal Testing cohorts. Need at least 5 Android testers (range: Pixel through Galaxy mid-range) and ~5 iOS testers (range: SE through Pro Max) with real devices. Recruitment plan TBD — likely "ship to Internal Testing track + post in chipi org channels" rather than active recruiting.
Per-scene WebGL context-loss restore (7 scenes). RFC-018 §11.2 requires a small
reinit()factor-out on/explore,/fly(heliocentric),/fly(cislunar per ADR-058),/earth,/moon,/mars,/iss,/tiangong. Scope as 7 sub-tasks under M5 when implementation begins; not an architectural decision.
Orrery · PRD-015 · Mobile App — Capacitor Wrapper · May 2026See also: RFC-018 · PRD-014 Surface Hotspots · ADR-018 Mobile-first design · ADR-029 Service worker / PWA · ADR-057 Locale cookie carve-out · ADR-058 Cislunar second camera