PWA Scope and Architecture

Decisions from the PWA scope alignment discussion (2026-05-28). Reviewed and approved by architect before spec writing.

Goal

Feature parity with native mobile apps (iOS and Android), minus hardware-dependent features. A user should be able to do anything on the PWA that they can do on the native apps, with degraded or excluded features clearly communicated.

Visual design language is a separate future effort — PWA launches with basic/functional styling.

Auth: Required (No Guest Mode)

PWA requires login/registration, same as native apps. Guest/deferred-registration is post-MVP. The existing placeholder user system (admin-created roster slots) provides a conceptual foundation for future guest support, but no current implementation supports self-service guest access.

Feature Inventory

Audited both iOS (SwiftUI) and Android (Jetpack Compose) native apps. 63 features mapped across 5 specs.

Fully Supported (16 features)

Pure UI + API — identical in browser:

Login, Register, Password Reset, Drill List + Filtering, Score Entry, Score History + Detail, Favorites, Leaderboards, Challenges, Event List + Detail, Range Bag, Scheduled Range Days, Friends, Achievements, Content Feed, Profile + Sign Out, Notification Preferences UI, Dry Fire Drill Browsing.

Supported with Offline Bridge (3 features)

Offline Score Queue (IndexedDB), Offline Status Indicator, Score Sync (manual + auto).

Degraded (7 features)

FeatureGap
Media Upload (photos/videos)Browser file picker, no custom camera UI
Share OverlayCanvas API composition + Web Share API (varies by browser)
Par TimerWeb Audio API 880Hz beep, no haptics, ~10-50ms latency
Add to Calendar.ics file download instead of native intent
Session SecuritylocalStorage instead of hardware-backed keystore

Excluded for MVP (3 features)

FeatureReason
Push NotificationsWeb Push API is viable (iOS 16.4+) but hostile permission UX and unreliable delivery vs APNs/FCM. Product decision, not technical limitation.
Haptic FeedbackNo web API. Par timer is audio-only.
Notification Deep LinksNo push in MVP, so no notification-triggered routing. Internal URL routing works.

Not in native apps (no work needed)

Biometric auth, GPS/location services, background sync — none implemented in either native app.

Architecture Decisions

WASM Persistence (ScoringEngine → IndexedDB)

ScoringEngine compiles directly to WASM (no UniFFI wrapper). The UniFFI save_state() / load_state() methods are not available on the WASM path.

PWA persistence adapter:

  • Save: engine.all_local_scores()serde_json::to_string() → IndexedDB
  • Load: IndexedDB → serde_json::from_str::<Vec<LocalScore>>()engine.restore(scores)

apps/pwa depends on rangeday-core with default-features = false to avoid compiling UniFFI for WASM.

IndexedDB Schema

One versioned database, one object store (score_queue) keyed by user ID. Value is the serialized JSON from all_local_scores(). No broad API data caching in the first pass.

Service Worker

Caches static/shell assets only (WASM bundle, index.html, CSS, manifest). Does NOT cache or intercept ConnectRPC/API POST requests. No background sync.

Auth Token Storage

Fixed localStorage keys (rdp_access_token, rdp_refresh_token) matching the dashboard pattern. Score queue IndexedDB storage is separately keyed by user ID.

CORS

Server ALLOWED_ORIGINS must include https://my.range-day.app and https://staging-my.range-day.app. Applied via apply-secrets GitHub Action into server-env k8s secret.

Shared Components: Deferred

Share transport/auth helpers and DTO patterns if clean. Avoid pulling dashboard Dioxus components into PWA until mobile UX stabilizes.

Spec Structure: Split by Feature Area

Feature parity is the destination, not a single implementation item. Five specs:

SpecScope
pwa-foundation-shellDioxus scaffold, auth, router/shell, Tailwind, manifest, service worker, build/hosting, CORS
pwa-scoring-offlineDrill list, score entry, ScoringEngine, IndexedDB queue, sync, score history/detail, void
pwa-events-rangebagEvents, range bag, scheduled range days (free-text location), calendar export
pwa-social-trainingFriends, challenges, leaderboards, achievements, content feed, favorites
pwa-media-dryfireMedia upload/gallery/share overlay, dry fire browsing, par timer

Implementation order: foundation first, scoring-offline second (hardest lift), remaining three parallelizable.

Data Shape Notes

  • Content feed unread dots: Client-side derived (comparing entry IDs against locally-tracked read set), not server-provided. Same approach as native apps.
  • Achievement icons/colors: Deterministic local mapping from achievement name/type — not API-provided fields.
  • Scheduled range day location: Free-text location_text field (max 200 chars), matching native app model. Location-API picker is post-MVP.
  • Content feed action_url: Exists in proto but may be unset in server v1. Render conditionally.

Native App Screen Hierarchy (reference)

Auth Gate
├── Login
├── Register
└── Password Reset

Main Shell (Tab-based)
├── Training Tab
│   ├── Drill List (live fire / dry fire filter)
│   │   ├── Score Entry
│   │   ├── Score Queue (pending / syncing / synced / failed)
│   │   ├── Score History
│   │   │   └── Score Detail (zones, media gallery, share overlay)
│   │   └── Leaderboard (all users / friends)
│   ├── Favorites
│   ├── Dry Fire
│   │   └── Par Timer
│   └── Challenges
│       ├── Challenge Detail
│       └── Create Challenge
├── Events Tab
│   ├── Event List (org events + personal range days)
│   └── Event Detail (drills, courses, roster)
├── Range Bag Tab
│   ├── Gear List (add / edit / delete)
│   └── Scheduled Range Days (add / edit / delete / calendar export)
└── Profile Tab
    ├── Friends (list / requests / search)
    ├── Achievements (earned / locked)
    ├── Content Feed (messages / announcements)
    ├── Notification Preferences
    └── Sign Out

What’s NOT in Scope

  • Visual design language / branding (separate cross-platform effort)
  • Guest/deferred registration (post-MVP)
  • Push notifications (post-MVP, product decision)
  • Background sync (not supported, foreground only)
  • Broad API data caching in IndexedDB (deferred)
  • Location entity picker for scheduled range days (post-MVP, free-text for now)