Skip to content

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 /posters adapt 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

IDRequirement
M1Installs from Google Play on Android 10+ and the App Store on iOS 16+
M2All 14 routes (11 primary + 3 read-only) render correctly at 360 px–412 px viewport widths (common Android range)
M3All 14 routes render correctly at 375 px–430 px viewport widths (iPhone SE through iPhone 15 Pro Max)
M4Three.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
M5App functions fully offline. Already true via the v0.6 PWA service worker (ADR-029); must be verified in the Capacitor container on both platforms
M6The Lambert worker (per ADR-022) runs correctly in the WebView context on both platforms
M7Touch interactions work: orbit (one-finger drag), zoom (pinch), tap (object selection), per-route hit-testing via window.__pickAt (ADR-056)
M8Bottom-sheet panel pattern (already shipped per ADR-018) is verified in the Capacitor WebView
M9App icons + splash screens meet Google Play + App Store submission requirements
M10Privacy policy page exists and is linked from each store listing
M11Bundle-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
M12The 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

IDRequirement
S1Native share-sheet integration via @capacitor/share — "Share this mission arc" surfaces a native Android / iOS sheet
S2Haptic feedback on mission events (TLI confirmed, Mars arrival) via @capacitor/haptics
S3Status bar / system bar styling matches --color-bg (#04040c) on both platforms — no white flash between launch and first paint
S4Safe-area insets respected — Android gesture nav bar + status bar; iOS notch + Dynamic Island + home indicator
S5App state preserved across background/foreground cycle — no full reload, WebGL context restored if lost (more critical on iOS WKWebView than Android Chromium)
S6Deep 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:

ShippedWhereNotes
Hamburger nav drawer ≤ 500 pxcommit 5f4cc05aAll 11 primary routes accessible; locale picker + lens + contrast toggles next to it
Horizontal layer-filter chip flowcommit 5f4cc05aFirst chip stays anchored; remaining chips wrap rightward instead of stacking vertically
/fly HUD collapsible panelscommit 4addaca9e + 0020f128bFive panel toggles in a top-right strip — HUD, CAPCOM, Flight Director, Layers, Conics
/science compact railtask #117Right rail shrinks from 70 % viewport to a tight nav strip
Footer / CTA clearancetask #115Persistent bottom-right footer doesn't overlap landing-page or /plan action buttons
Per-planet pill colorstask #112Mars red / Earth blue / Moon grey / Explore teal — visual distinction without relying on placement
Bottom-sheet panel patternADR-018Right drawer on desktop; bottom sheet ≤ 768 px, swipe-down to dismiss
Touch readiness signalsADR-056window.__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

CriterionMeasure
Ships on both storesGoogle Play listing live + App Store listing live
Offline verifiedAll 14 routes function with no network on a physical device (airplane mode test)
Three.js verified60 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 interactionsOrbit, 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 ratingNo forced rejections due to wrapper-policy violations
Bundle sizeInstall size ≤ 80 MB after the bundle-strategy work in RFC-018
PWA continuityService 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

DependencyStatusNotes
RFC-017 / PRD-014 (Surface Hotspots) shippedRequired firstMobile 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 pageRequired before either store submissionStatic HTML page in static/privacy.html (or a SvelteKit /privacy route) — content is short because no data is collected
Google Play developer accountRequired$25 one-time
Apple Developer accountRequired$99 / yr
App icon designRequiredSee open question Q4
Bundle-size reduction strategyRequired before either store submissionRFC-018 § bundle-strategy proposes the cuts
Mobile-viewport UXS updateOptionalThe 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.

  1. 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.io on tap. Target ~85 MB installed — well under PRD M11 ceiling (150 MB) and iOS OTA cap (200 MB). Three Vite-config branches gated by MOBILE=1; the browser deploy is unaffected.

  2. 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.

  3. 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.

  4. 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

  1. 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.

  2. 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.

  3. 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

Orrery — architecture documentation · MIT · No tracking