Current Product State
This document is the current-source-of-truth snapshot for the rewrite as of April 8, 2026.
For remaining work, start with development/v1-remaining-milestones.md, then use development/handoff-next-chat.md, development/follow-up-backlog.md, and development/manual-validation-remaining.md as the detailed source material.
Repo Shape
aspectavy-next is a monorepo.
- iOS app: apps/ios/AspectAvy
- backend: apps/backend
- legacy export tooling: tools/legacy-firebase-export
What Works Today
Core app shell
- Launch -> Auth -> MainShell flow is in place.
- The iOS app now supports system, light, and dark appearance preferences through in-app settings, and Milestone
0QA should validate the shared map chrome and sheet surfaces in both light and dark mode. - The map is the primary app root.
- Search, layers, library, settings, paywall, offline maps, and route/plan details all open from the map shell.
- Large-detent sheet presentation is now the default top-level pattern, including route details, while the library sheet owns deeper in-sheet navigation for plan and track tools.
- Dismissing route or incident details now clears the underlying map-selection state instead of leaving stale toolbar or chrome state behind.
- Row-style navigation and interaction controls across the main list/detail surfaces now use full-container hit targets instead of only the intrinsic text/icon bounds.
Authentication, account, and settings
- Owned backend email/password registration and sign-in work.
- Backend-first legacy-account migration now exists behind config: when enabled, sign-in can fall back to imported Firebase credentials, materialize a local auth record, and continue through the owned auth/session path without client-side special cases.
- Backend-supported magic-link authentication now works through shared request/consume APIs plus hosted
/auth/magic/:tokenhandoff routes for iOS and web, withpreview,log, and SMTP-backed delivery modes. - Backend-supported password reset now works through shared request/consume APIs plus hosted
/auth/reset/:tokenhandoff routes that reopen the native reset flow and can also complete from the browser-hosted QA scaffold. - The configured app universal-link host now also serves a minimal hosted browser auth scaffold at
/login,/forgot-password, and/success, backed by the same session APIs as iOS, so live QA can verify real cookies, login, reset-password entry, logout, and account identity before the full web companion exists. - Staging and production deployments now support an explicit shared session-cookie domain, so split-host browser setups such as
app.*plusapi.*can share one authenticated session whenSESSION_COOKIE_DOMAINis configured to the parent host. - Existing sessions restore on cold launch.
- Sign-out and sign-back-in work.
- iOS now supports biometric quick sign-in backed by enrolled keychain credentials after a successful authenticated login.
- Auth onboarding now requests location access from a dedicated
Location Accesshandoff after biometric enrollment, and the map launch path only auto-centers when location was already authorized instead of prompting on raw app load. - The launch surface now supports backend-driven iOS version policy, so bootstrap config can optionally require or recommend an App Store update before the app continues into auth or the signed-in shell.
- Recommended update prompts can now be resolved against the live App Store version server-side, while required-update policy remains backend-owned and authoritative.
- Backend admin auth tooling now exists as a first-class CLI surface for manual legacy-account migration, auth-state inspection, and account-lockout recovery instead of relying on one-off scripts.
- Profile editing works, including clearing optional fields.
- Backend profile hydration now self-heals missing local-user rows on
/api/userand works after auth restore and sign-in. - Account and settings are now split into separate first-class surfaces.
- The account screen owns backend-backed identity details, membership status, invite-link sharing, buddy-pass actions, safety contacts, and sign-out.
- Safety contacts now support backend-owned verification state, hosted verification links, and resend-verification flows before they are used for trip overdue notifications.
- The settings screen owns backend-backed map and layer preferences, communication preferences, legal documents, developer-tools entry in debug, and delete-account controls through a dedicated danger-zone flow.
- User settings are now backend-backed and shared-contract ready for web, including map style preference, default camera mode, forecast polygon opacity preset, layer visibility preferences, communication preferences, and recommended-update prompt preferences.
- The settings screen now also exposes backend-owned pinned forecast zones, primary-zone management, forecast override history, and a support snapshot so account-adjacent forecast/accountability state is visible without dropping into developer-only tools.
- The in-app settings editor now surfaces backend-served settings-contract metadata, including schema version, settings groups, and last-synced timestamps, so cross-platform preference ownership is inspectable from the client.
- The settings screen now exposes a backend-backed anonymous analytics preference, phrased as opt-in product analytics rather than user tracking.
- The login flow no longer routes through tutorials or education surfaces; education remains a standalone settings/library capability plus hosted
/educationlibrary/detail pages that reopen the same native surface. - The same hosted universal-link host now also serves backend-owned
/docspages for QA, implementation notes, and future public support content. - The backend now ships a richer interactive API documentation portal at
/docs/api, with same-origin session testing, authored guides/tutorials, default working parameter values where possible, and a machine-readable OpenAPI export at/api/openapi.json. - Privacy Notice and Terms & Conditions now load through backend-owned legal-document endpoints that fetch live HTML/CSS from the canonical hosted sources.
- The paywall now exposes both
Buy in Appand storefront-gatedBuy on Webpurchase actions when the backend catalog and StoreKit storefront allow them. - Hosted
/membershipuniversal-link returns now reopen the account tab and trigger an entitlement refresh path after web checkout. - Buddy-pass records now serialize share URLs, and dedicated
/buddy-pass/:slughosted landing pages now reopen the account tab into the same typed redemption flow used by the app-entry pipeline.
Forecast-first map
- Custom AspectAvy basemap style is loaded.
- Forecast polygons render and can be opened for details.
- Forecast rendering is now override-aware: the map, badges, and hero treatments use the user-scoped effective level when a current forecast override exists, while official bulletin text and avalanche-problem content remain unchanged.
- forecast snapshots now expose canonical problem titles, stable problem tokens, and structured aspect/elevation slices when the upstream source provides them
- forecast zone details now use generated safety components: asset-backed danger/problem pictograms remain, but the danger header, problem cards, badges, and rose geometry are rendered in code instead of one-off text rows
- Slope shading renders and transitions with the forecast zoom threshold.
- Incidents, popular routes, and ski areas render from backend-owned or configured data sources.
- popular routes now ship richer backend-owned cartography metadata, including normalized route name, route-kind, and route-direction confidence properties so directional arrows only render when the backend can defend the line orientation
- Search moves the map to selected results.
- Search is now backend-owned through
/api/searchand returns normalized mixed-domain results for places, forecast zones, incidents, routes, route plans, tools, and future expandable surfaces through the sharedAppSearchResultcontract. - On iOS 26 and later, natural-language search input now gets an on-device interpretation pass before retrieval, but
/api/searchremains the only canonical result source. - search results now size to the current result count instead of always opening at one fixed height
- search results now feed durable Core Spotlight indexing so indexed forecast zones, routes, route plans, and selected tool surfaces can reopen through the same typed app-entry contract from the iPhone home-screen search surface
- App Intents and Shortcuts now expose the main high-value typed entry points for the map, settings, inclinometer, and checklist tools
- typed continuation and Handoff activities now exist for forecast zones, routes, route plans, route-draft sessions, and track sessions, all resolving through the same app-entry pipeline as universal links and other routed surfaces
- Layers toggles work.
- Browse and Field map modes now expose a placeholder top-right action stack under search/account for
Update Forecast,Pro Tools, andTools, plus a dedicated settings button. - The browse-mode recenter control now resolves to true user location instead of content selection, and its icon/tint reflect live authorization or waiting state.
Update ForecastandPro Toolsnow resolve strictly against the current forecast zone under the crosshair and disable when no current zone is available.- When the current zone already has a local forecast override, the map action stack now exposes an explicit
Reset Overrideaction and the override flow itself exposesReset To Official Forecast, so the reset path is available without detouring into forecast details. - When the crosshair sits inside an active forecast zone, the map can now surface a compact live slope-guidance banner with backend-owned
No-GoandVerifyranges instead of relying on a client-only angle table. - forecast-home launch now resolves to selected-zone focus after data loads, while restored task and explicit content flows keep camera ownership across refreshes
- centered-zone feedback keeps the map-center crosshair and uses a restrained outline-only zone highlight
- popular routes now use the refined V1 whole-line tri-state risk styling, while segmented route risk remains deferred to the post-V1 platform work
- map presentation now supports an optional 3D camera mode that keeps the same data and task model as 2D
- forecast polygons now overlap slope shading a bit longer through the transition zoom range so bare basemap does not flash through during the handoff
- higher-danger forecast polygons now support a dedicated patterned overlay treatment instead of relying on flat fill alone, which sets up the broader polygon-pattern design direction without changing forecast interaction behavior
- popular routes now use a more explicit cartographic treatment with directional glyphs, centered route-name labels, and a backend-normalized approach versus descent distinction rather than a plain styled line alone; low-confidence lines now fall back to neutral line styling instead of misleading arrows
- incident details now load richer backend-owned records by
incident_id, with source links treated as provenance rather than the main UI - forecast-zone details now support backend-backed pinned-zone preferences, primary-zone promotion, a compact on-device quick briefing surface aligned with the shared edge-surface roadmap, and shared
Update Forecast/Pro Toolsoverride flows with reset support and explicit official-to-effective level messaging - forecast-zone quick briefs, home-summary favorite zones, and route-plan briefings now carry backend-owned weather summaries plus attribution from Apple WeatherKit when configured, so iOS and future web/watch clients can consume the same weather seam
- on-device Foundation assistance now opportunistically rewrites forecast and route-plan briefings into tighter iOS-native summaries when the system model is available, with deterministic fallbacks to the canonical backend briefing payloads
Tools and checklist flows
- Trailhead Checklist - Drop-In Checklist - Inclinometer
- The app now has a reusable checklist system built as shared domain data plus a single TCA-backed checklist renderer, rather than one-off checklist views.
- That shared checklist surface currently powers
Update Forecast,Pro Tools,Trailhead Checklist, andDrop-In Checklist. - Checklist action footers now live inside the scroll content instead of being pinned above the safe area.
Update Forecastis now a real guided override flow built as a pushed navigation sequence: warning -> field-observation checklist -> applyConsiderable, withNoneexiting without change.Pro Toolsis now a real guided override flow built as a pushed navigation sequence: warning -> defensible-reason checklist -> manual level selection, with membership gating preserved andNo reasonexiting without change.- The new
Toolslauncher currently contains: - The inclinometer is now rebuilt as a full-screen camera-based tool with a live preview, heading and inferred-aspect readouts, a horizontal slope-matching guide,
Across Slope/Downhill/Runout Checkmodes, shared permission handling, a help sheet, and forecast-aware slope guidance. - The map-center crosshair now responds to zoom with tighter geometry, a progressively smaller center gap, and stronger visibility at higher zoom levels instead of staying one fixed size and weight.
- In debug builds, the
Pro Toolspaywall path can route directly into the developer-tools membership override surface for local entitlement testing without weakening production gating. - Applying or clearing a developer-tools membership override now refreshes both the account-owned membership summary and the live map membership policy, so
Pro Toolsgating and the signed-in account surface update app-wide instead of only inside the debug panel.
Routes, plans, and tracks
- Route drafting, resume, save, and reopen flows work.
- Route drafts and saved user routes now carry backend-owned presentation metadata, including a constrained route color token instead of a hard-coded single route color.
- Route drafting now exposes a dedicated details flow for route title, notes, and route color selection, and the live planning overlay immediately reflects the chosen presentation color.
- Route drafting now supports direct point dragging on the map instead of only long-press-then-tap repositioning, and the planning chrome reflects whether a draft is only on device, saving against an active backend session, or already checkpointed.
- Route-point dragging now uses an in-map loupe overlay so high-zoom placement can resolve against a tighter reticle without giving up the shared map editing surface.
- Route-draft details now also render backend-owned terrain analysis for the in-progress geometry, including the same profile, slope-guidance, footprint-rose, exposure-budget, and slope-distribution surfaces that finalized saved routes use.
- Saved routes render from Library, can reopen route details from the map, and now support backend-owned share links.
- Saved routes and shared route fetches now also carry backend-owned
safetysnapshots with highest-risk, exposed-distance, evaluated-danger-level, freshness, and source-revision metadata. - Saved-route details now render backend-owned terrain-analysis cards: a Swift Charts profile with danger, steepness, and forecast-problem overlays by segment, plus footprint-rose, exposure-budget, and slope-distribution summaries sourced from the same server analysis contract.
- Popular-route catalog records now also carry cached backend terrain analysis, so the same route-graph and terrain-summary surfaces can render without inventing client-only heuristics for public routes.
- Route plans can be created, edited, saved, shared, reopened, and imported from custom-scheme deep links.
- Route-plan setup now runs as a pushed multi-step flow (
Basics->Team->Routes->Review) with an explicit progress treatment instead of one long mixed editor. - Route-plan readiness now comes from a shared backend contract with persisted acknowledgement semantics, and the plan editor merges that backend state with device-local offline-map and shared-forecast-cache checks before trip start.
- Supported
.gpx,.kml,.geojson,.json, and.xmlroute files can now be imported from the library surface or external file URLs, with preview normalization handled by the backend import API before the app starts a new route-draft session. - Saved route plans now separate setup from an explicit
Plan Workspacesurface that owns route briefings, latest-trip safety-share state, plan-scoped and trip-scoped decision-log entries, field observations, structured debrief reflection, and recent trip history, with backend-owned latest-trip lookup and hosted trip status pages. Brand-new plans start directly onPlan Basics, while reopening an existing plan lands on the saved-plan overview and workspace entry surface. - Route-plan briefing surfaces now render backend-owned weather context and attribution alongside the canonical route-safety and forecast briefing payloads.
- The route-plan editor now also supports on-device drafting assistance for planning decisions, field observations, and trip debrief reflection, but the saved backend record remains the canonical source of truth.
- Starting a trip safety session from a saved route plan now requires a readiness pass: blockers stop launch, warnings require acknowledgement once per saved plan revision, and that acknowledgement is persisted server-side.
- Active trip sessions now support explicit trip check-ins, verified-contact share resends, expected-return updates, and verified-contact counts from inside the route-plan editor.
- Route-plan safety summaries now come from backend-owned explicit route exposure analysis instead of client-only route-risk styling assumptions.
- Canonical shared route, plan, invite, and buddy-pass URLs now resolve through backend-hosted universal-link landing pages instead of 404ing on the web, and hosted
/educationpages now use the same app-entry pipeline for tutorial continuity. - Recorded tracks can be recorded, paused, resumed, saved, shown on map, reopened, and deleted.
- Route, route-plan, and recorded-track mutations now survive backend outages through a local sync outbox; finalization and delete failures fall back to local-save messaging plus queued replay instead of ending in a dead error state.
- Opening track details or plan editing from Library now stays inside the library sheet’s navigation stack instead of spawning nested sheets.
- Unsaved route drafts survive relaunch and can be reopened.
- Active track recovery across relaunch works.
- Shared simulator GPX fixtures now include longer San Juan and Wasatch tours, and the default run scheme starts with the longer San Juan loop for better recording validation.
Offline
- Offline region selection, estimating, downloading, pausing, resuming, deleting, and persistence across relaunch work in local validation.
- Offline bundle composition includes forecasts, incidents, popular routes, drafts, saved routes, tracks, and base map data.
- Offline region list persistence works.
- Saved offline regions now use a root
Offline Mapsscreen plus a dedicated pushed region-detail screen instead of inline row expansion. - Saved offline regions can jump the live map back to the downloaded area through a dedicated
View on Mapaction, and map taps on offline coverage reopenOffline Mapswith that region detail already pushed. - Offline coverage now participates in the real map layer system as a dedicated optional layer family, with downloaded-coverage styling that uses rounded-rectangle geometry, a lighter base tint, a dedicated hatch pattern, dashed perimeter, and metadata labels instead of a generic polygon treatment.
View on Maptemporarily reveals offline coverage when the layer is hidden and fits the camera, but it no longer leaves the region selected once the Offline Maps presentation dismisses.- Offline membership gating now follows the resolved policy: Free includes one real region up to
36 mi², slope shading remains included, preset selection itself is not paywalled, and technical storage issues stay as plain errors. - The in-map offline selector now uses a lighter sectional bottom-edge layout so the selected region stays more visible during estimate and download flows.
- The debug-only developer tools surface now opens from within the Account flow without dismissing it first, and it is organized as a drill-down dashboard for runtime, account, offline, forecast, trips, routes, network, deep links, logs, and test tools.
- The developer-tools dashboard now also exposes analytics diagnostics and a debug test-event trigger backed by the same analytics client and backend ingestion path as the app runtime.
- A hidden logged-out developer-tools entry also exists on the login screen in debug builds, so diagnostics remain reachable before auth succeeds.
- Offline style-pack loading now refreshes custom Mapbox style resources intentionally during offline downloads instead of relying on passive cache behavior.
- Mapbox ornaments now adjust their lower insets by map mode so they clear the browse dock as well as the taller route-planning, tracking, and offline overlays more consistently.
- The debug routes dashboard now exposes shared sync diagnostics, including sync status, pending queued operations, and replay conflicts from the local outbox.
Shared Data Spine And Apple Edge Foundations
- Shared App Group-backed edge-surface storage is now in place for quick actions, forecast briefs and snapshot summaries, active field-session summaries, and active trip-status summaries.
- The widget extension is now wired to the same App Group container as the app through entitlements and runtime configuration.
- Field sessions now have a real Live Activity client and widget surface backed by the shared edge-store and compact summary models.
- Shared forecast brief edge payloads now also persist backend-owned weather summaries and attribution so future widgets and watch surfaces can reuse the same compact contract.
- Home-summary responses now expose a compact backend-owned
activeTripsummary, and iOS mirrors that payload into the shared App Group store so future widget and watch clients can consume trip state without inventing a client-only contract. - Route-plan trip start, check-in, finish, and share-resend flows now refresh the shared edge-surface store immediately after backend success so active trip status stays aligned with the canonical server state.
- Future home-screen widgets, richer edge summaries, and a dedicated watch app are still not shipped; the current watch work is preparation-only.
- The AppServices Xcode test target now includes the real shared-edge, transport-helper, and contract-fixture tests that had existed on disk but were not previously wired into the project.
Backend
- /api/user/forecast-overrides - PUT /api/user/forecast-overrides/:zoneID - DELETE /api/user/forecast-overrides/:zoneID
- auth-platform-routes.ts owns auth entry, elevation, invite creation, push-device registration, and provider webhook/platform integration routes. - route-management-routes.ts owns route CRUD, route-plan CRUD and shared-copy flows, work sessions, sync, import/export, and search routes. - route-intelligence-routes.ts owns route exposure, readiness, trip safety, decision logs, trip observations, debrief, and AI assistant routes.
- GET /api/user/forecast-override-history - GET /api/user/support-summary - GET /api/user/settings and PUT /api/user/settings now return both settings and typed metadata
/livezand/readyzhealth checks work.- Backend-owned bootstrap config, forecast snapshot delivery, auth/session, routes, route plans, work sessions, invites, route safety, elevation, commerce, and buddy-pass APIs are in place.
- Route and route-plan sync now expose revision-aware page endpoints, while delete flows use soft-delete tombstones so offline replay and downstream clients can reconcile deletes instead of losing them.
- Forecast APIs are now user-override aware.
/api/forecastand/api/forecast/briefsreturn official and effective danger-level fields plus any active current-revision user override, and backend-owned forecast-override endpoints now exist at: - User-owned backend endpoints now live under
api/user/*, and the iOS app routes profile, settings, favorite zones, forecast overrides, safety contacts, and account deletion through sharedBackendUserRouterequest helpers instead of repeating endpoint strings per client. - AppServices now also routes shared JSON request construction, auth policy, backend error extraction, and typed JSON decoding through BackendJSONTransport.swift, which removed repeated authenticated transport code across commerce, invites, buddy passes, routes, work sessions, push-device registration, home summary, and legal-document clients.
- The shared transport layer now also owns request execution for the remaining commerce/debug seams, so BackendSubscriptionClient.swift, CommerceDiagnosticsClient.swift, DebugToolsClient.swift, and SafetyContactsClient.swift no longer hand-roll authenticated request sending.
- Backend and iOS now validate user-domain payloads against shared checked-in fixtures under contracts/fixtures/user, and the backend serializer layer now normalizes optional buddy-pass fields to canonical
nullvalues for stable cross-platform contracts. - The commerce/debug domain now has an extracted backend contract layer in commerce-contracts.ts, and backend plus iOS fixture tests now share checked-in payloads under contracts/fixtures/commerce for catalog, entitlements, diagnostics, and debug-state responses.
- The commerce/debug backend route surface is no longer registered inline in server.ts; it now lives in commerce-routes.ts, which owns catalog, entitlements, debug-state, buddy-pass, and commerce-diagnostics route registration as one extracted domain seam.
- The forecast domain now has an extracted backend contract layer in forecast-contracts.ts, and backend plus iOS fixture tests now share checked-in payloads under contracts/fixtures/forecast for snapshot, quick-brief, and home-summary responses.
- Home-summary favorite-zone briefs are now explicitly contract-normalized as override-aware forecast payloads, so the backend and iOS both validate the same effective-level, official-level, and override metadata at the favorite-zone edge surface seam.
- The backend can now optionally enrich forecast briefs, home-summary favorite zones, and route-plan briefing payloads with Apple WeatherKit summaries plus attribution through shared weather contracts and checked-in fixtures, keeping weather server-owned for future web parity.
- User settings payloads are now contract-normalized with backend-served metadata for schema version, settings groups, and update timestamps, and the iOS app decodes those settings envelopes through the shared user-contract fixture suite.
- The forecast backend route surface is no longer registered inline in server.ts; it now lives in forecast-routes.ts, which owns forecast snapshot, quick-brief, home-summary, and change-feed route registration as one extracted domain seam.
- The public-content backend route surface is no longer registered inline in server.ts; it now lives in public-content-routes.ts, which owns AASA delivery, hosted route/plan/invite/membership/education pages, bootstrap config, legal content, public catalogs, incident detail, and public invite resolution as one extracted edge-domain seam.
- Auth email delivery is now split into shared backend-owned helpers for magic links and password reset, so preview, log, and SMTP delivery all render the same hosted URLs and email copy contracts.
- The remaining large authenticated and integration-heavy backend route clusters are now extracted as well:
- Route safety is now a first-class backend contract instead of presentation-only styling: saved-route responses carry a
safetysnapshot, explicit/api/routes/:routeID/exposureand recompute flows exist, route plans aggregate those summaries through enriched plan-safety responses, and route compilation precomputes the exposure cache in the background. - Route-plan readiness is now a first-class backend contract too:
/api/route-plans/:planID/readinessreturns canonical readiness state includingacknowledgedAt,requiresAcknowledgement, andcanStartTrip,/api/route-plans/:planID/readiness/acknowledgepersists review state by plan revision, and/api/trips/startnow enforces that readiness policy before creating a trip session. - server.ts now primarily owns backend bootstrapping, shared security hooks, background-task orchestration, health checks, shared utility helpers, and route-domain registration instead of carrying the full backend route surface inline.
- Backend startup is now split cleanly: app.ts owns the Fastify app factory and route/plugin registration, while server.ts is now just the process entrypoint and shutdown wrapper.
- The backend persistence decomposition is now largely complete. Trip safety persistence lives in trip-safety-store.ts, user/account persistence lives in user-account-store.ts, forecast snapshot persistence lives in forecast-store.ts, forecast-user persistence lives in forecast-user-store.ts, commerce/debug persistence lives in commerce-backend-store.ts, runtime health persistence lives in runtime-store.ts, auth lockout persistence lives in auth-lockout-store.ts, content snapshots live in content-snapshot-store.ts, domain revisions live in domain-revision-store.ts, route sync state lives in route-sync-store.ts, and route exposure cache persistence lives in route-exposure-cache-store.ts.
- Backend HTTP seam coverage now includes authenticated success-path checks for
/api/user/settingsand/api/debug/statein route-http-guards.test.ts, not just unauthenticated guard assertions. - database.ts is now reduced to database bootstrap, migrations, and shared backend record types instead of carrying live persistence logic for every domain.
- Cookie-authenticated API writes now pass through a shared origin policy in request-origin-policy.ts: browser requests with session cookies must come from an allowed app origin, while native cookie-backed requests without browser origin headers continue to work.
- Proxy trust is now explicit instead of implicit. app.ts configures Fastify trust-proxy behavior from
TRUST_PROXY_HOPS, and request-base-url.ts now resolves hosted-page base URLs from Fastify’s trusted request state rather than directly reading raw forwarded headers. - Production environment validation now requires both
TRUST_PROXY_HOPS > 0andREVENUECAT_WEBHOOK_AUTHso proxy-derived IP/security behavior and RevenueCat webhook trust are not left implicit. - Backend HTTP seam coverage now also includes cross-origin write rejection, same-origin write success, native write success without browser headers, and RevenueCat webhook auth behavior in route-http-guards.test.ts, plus dedicated request-base-url and subscription-auth tests.
- The backend now exposes a reusable app builder in app.ts via
buildBackendApp, so app-level HTTP integration tests can exercise the real global hooks and route registration path without booting the production entrypoint. - Stable backend security and contract reference docs now exist in backend-security-model.md and backend-contract-catalog.md.
- Backend API reference is now a first-class hosted surface instead of only repo markdown:
/docs/apirenders the interactive explorer and/api/openapi.jsonemits the generated contract document from the same registry. - Backend user APIs now expose dedicated forecast-accountability and support surfaces:
- Backend audit events for forecast-override apply and clear now flow through that user-facing override-history surface, including official versus effective danger-level context and current-active markers at the account/settings layer.
- Backend account APIs now include account deletion plus backend-served legal-document rendering payloads for native in-app display.
- Backend universal-link hosting is now in place for AASA delivery plus hosted auth/reset/route/plan/invite/buddy-pass/membership/education/docs landing pages, including the richer API explorer at
/docs/api. - Backend trip safety APIs now include latest-trip lookup plus hosted trip-status pages, so saved plans can resume or share the most recent safety session without reconstructing state client-side.
- Backend trip safety now also owns safety-contact verification tokens, hosted
/safety-contact/verify/:tokenconfirmation pages, verified-contact trip attachments, trip check-in updates, and worker-driven overdue evaluation state transitions. - Worker-based forecast syncing and route compilation are in place.
- Stripe customer, checkout-session, billing-portal, and webhook flows now resolve through the rewrite backend’s grant ledger.
- RevenueCat sync and webhook flows now reconcile into the same entitlement model as Stripe and buddy passes.
What Has Been Manually Validated
The local rewrite has already been validated for:
- backend readiness
- app boot to auth
- account creation and sign-in
- session restore
- map rendering and forecast interaction
- layers and overlay visibility
- route creation, route save, and route library flows
- route draft resume and draft recovery
- track recording in simulator flow, save, reopen, and delete
- route plan creation, editing, saving, sharing, reopening, and custom-scheme import
- profile editing and optional-field clearing
- paywall presentation and dismissal
- backend outage handling and recovery
- library persistence and offline-region persistence across relaunch
- background/foreground resilience
See development/manual-validation-remaining.md for the remaining unvalidated slices.
Known Gaps Or Open Areas
These are the important remaining gaps, not already-fixed bugs:
- the backend owns POST /api/analytics/events, typed event validation, forbidden-key rejection, and the Aptabase-backed sink adapter seam - hosted staging and hosted production now support separate Aptabase app keys so QA traffic can stay isolated from production analytics - signed-in user settings persist the anonymous analytics preference through the shared backend contract, while signed-out state keeps a local fallback - iOS now has a typed analytics client, reducer-driven instrumentation for the key shipped product flows, and a developer-tools analytics diagnostics surface
- forecast quick-brief and route-plan briefing refinement use Foundation Models when available and fall back to the existing backend-owned briefing payloads when not - natural-language search interpretation now narrows canonical /api/search queries instead of acting as a second search system - decision-log, observation, and debrief drafting assistance now use structured on-device responses with deterministic heuristic/backend fallbacks - shared or hosted AI remains intentionally out of scope for V1
- MapFeature.swift now uses grouped camera/search storage and delegates presentation/view handling into MapFeature+PresentationReducer.swift, MapFeature+ViewReducer.swift, and the later dedicated helper modules under Features/Map - MapView.swift remains a thin entry surface, and the former monolithic map-content file is now split into MapView+Presentation.swift, MapView+Chrome.swift, MapView+BottomContent.swift, and MapView+Helpers.swift
- live-location, recenter, and tracking-runtime handling now route through MapFeature+LocationReducer.swift instead of staying inline in MapFeature.swift - the tracking-runtime helpers moved into MapFeature+LocationHelpers.swift, which makes the existing LocationState in MapFeature.swift a real ownership seam instead of placeholder structure only
- chrome and tool-launch view actions now route through MapFeature+ChromeReducer.swift instead of staying mixed into MapFeature+ViewReducer.swift - forecast/detail/paywall/offline-map presentation builders and override-coordination helpers now live in MapFeature+PresentationHelpers.swift
- camera updates plus search UI actions and search-response state now route through MapFeature+SearchCameraReducer.swift - centered-zone resolution, camera-focus updates, and recenter fallback helpers now live in MapFeature+CameraHelpers.swift instead of the generic helper bucket
- shared-link entry points and shared-content result handling now route through MapFeature+SelectionPresentationReducer.swift - shared-route and shared-plan loading helpers now live in MapFeature+SelectionPresentationHelpers.swift, and selection-driven route-detail opening no longer sits in the generic view switch
- selection-related storage now lives in MapFeature.State.SelectionState inside MapFeature.swift, including selected map hits and selected-zone ownership - MapEngineState no longer owns selectedMapHit, which narrows engine state back toward rendering and runtime concerns while keeping selection semantics in the map reducer
- route-draft and tracking-task storage now lives in MapFeature.State.TaskState inside MapFeature.swift, including draft persistence IDs, active work-session IDs, route-draft content, tracking content, route-draft launch prompting, and field-presentation state - the map shell keeps compatibility accessors for routeDraft, tracking, routeDraftStartPrompt, and related task fields, so current reducers and views preserve behavior while task ownership moves out of the top-level state bucket
- route-content coordination storage now lives in MapFeature.State.RouteContentState inside MapFeature.swift, including popular-route exposure loading state, popular-route exposure content, exposure errors, and showcased routes - selection ownership is now narrower and cleaner, while current reducers and views keep working through compatibility accessors during the larger map-shell refactor
- broader map-content coordination now lives in MapFeature.State.ContentState inside MapFeature.swift, grouping forecast snapshot state, offline region content, selection state, and route-content state behind one map-content boundary - forecast loading/offline content concerns are no longer scattered directly across the top-level map state bucket, while compatibility accessors keep the active reducers and views behavior-stable
- modal and full-screen map presentations now flow through the owned MapFeature.Presentation child reducer instead of twelve separate top-level action cases in MapFeature.swift - the root reducer now receives one presentation coordinator action surface, while MapFeature+PresentationReducer.swift handles only the parent-level delegate bridge back into map state and side effects
- map-hit handling, selection actions, popular-route exposure refresh, and forecast/offline content responses now route through MapFeature+ContentReducer.swift - the related content helpers moved into MapFeature+ContentHelpers.swift
- route-draft and tracking workflows now route through MapFeature+TaskReducer.swift, which pulls route-draft, tracking, and work-session coordination out of the root reducer and generic view switch - the remaining mixed helper bucket is gone: bootstrap helpers now live in MapFeature+BootstrapHelpers.swift, offline helpers in MapFeature+OfflineHelpers.swift, task/work-session helpers in MapFeature+TaskHelpers.swift, and route/plan/offline camera focus helpers in MapFeature+CameraHelpers.swift
- browse-mode location authorization now loads at map bootstrap, and browse mode can run its own light user-location stream independently of tracking runtime through MapFeature+LocationHelpers.swift - the recenter control now requests when-in-use authorization when needed, waits for the first available fix, and centers on actual user location through MapFeature+LocationReducer.swift and MapFeature+CameraHelpers.swift instead of resetting to content selection - a shared AppPermissionClient.swift now reports centralized permission snapshots for location, notifications, camera, and motion - PermissionCenterFeature.swift and PermissionCenterView.swift now provide a shared Permissions & Access surface from both Settings and Developer Tools - the cartography prototype surfaces now live under Developer Tools > Cartography Lab through CartographyLabFeature.swift, CartographyLabView.swift, and AppDebugPanelView.swift instead of a map-chrome-only debug affordance - this closes the current map-shell decomposition milestone
- AppServices is now organized by domain folders instead of one flat source root, which restores readable ownership boundaries for backend, auth, commerce, forecast, routes, safety, account, location, and storage seams - the remaining high-duplication clients now route through the shared validated transport helpers in BackendJSONTransport.swift and BackendUserRoute.swift, including route-readiness, route-exposure, trip-session, decision-log, AI briefing, favorite-zones, and home-summary calls - the package overview doc is now current in AppServices.md, and focused transport seam coverage now exists in BackendJSONTransportTests.swift
- the new forecast override flows, checklist tools, inclinometer, split settings surface, and persisted user-map preferences still need manual validation on-device and in simulator, but that work is now explicitly deferred to Milestone
0 - outbound trip-safety delivery is still intentionally preview/log-backed; real provider delivery and final end-to-end verification remain part of Milestone
0 - the new shared permission/access baseline still needs a larger UX and policy pass for pre-prompt education, denial recovery, feature gating polish, and richer diagnostics
- privacy-first product analytics are now implemented for the backend and iOS surfaces in this repo:
- future web work should consume the same backend-owned contract instead of defining a second analytics model
- on-device AI support is now implemented for iOS-only assistive flows:
- the new unified search, Spotlight indexing, App Intents, and continuation pipeline still need full end-to-end manual validation in Milestone
0, but the backend contract and iOS implementation are now in place - shared-data-spine-dependent Apple surfaces are still incomplete: the shared App Group, widget-extension wiring, and field Live Activity foundation now exist, but richer home-screen widgets, fuller edge summaries, and any dedicated watch app remain unshipped
- real App Store sandbox/TestFlight purchase validation is still pending
- real hosted Safari/universal-link validation is still pending even though the routing, AASA, and hosted landing-page plumbing now exist
- push delivery is not yet end-to-end validated
- saved-route share links now exist through backend-owned
shareableIDvalues and/route/:shareableID - shared route-plan imports now resolve through backend-owned context, preferred-copy reopening, and persisted import lineage
- forecast-zone details and some remaining map/detail surfaces still need a deeper navigation cleanup pass, but route details now match the large-detent sheet pattern and route-plan setup now uses a pushed stepper flow
- the second iOS map-shell decomposition milestone is now in place:
- the third iOS map-shell decomposition milestone is now in place:
- the fourth iOS map-shell decomposition milestone is now in place:
- the fifth iOS map-shell decomposition milestone is now in place:
- the sixth iOS map-shell decomposition milestone is now in place:
- the seventh iOS map-shell decomposition milestone is now in place:
- the eighth iOS map-shell decomposition milestone is now in place:
- the ninth iOS map-shell decomposition milestone is now in place:
- the tenth iOS map-shell decomposition milestone is now in place:
- the eleventh iOS map-shell decomposition milestone is now in place:
- the twelfth iOS map-shell decomposition milestone is now in place:
- the thirteenth iOS map-shell decomposition milestone is now in place:
- the shared permissions, live-location, and recenter milestone is now in place:
- the AppServices reorganization milestone is now in place:
- a larger post-V1 route-risk platform expansion still exists for backend tiles plus
routeZoneForecast, but V1 keeps the current whole-line route-class presentation
Current Runtime And Dev Constraints
Simulator and local backend
- Debug simulator builds point at
http://127.0.0.1:3001. - That is correct for the simulator, but not for a physical device.
- Device runs need either a LAN-reachable backend host, a tunnel, or a staging/production API.
Local secrets
- apps/ios/AspectAvy/Config/LocalSecrets.xcconfig
- apps/ios/AspectAvy/.xcode-cloud - apps/ios/AspectAvy/ci_scripts
- The rewrite expects local simulator tokens in:
- That file is intentionally local-only and ignored by git.
- The generated Xcode project now separates
AspectAvy Dev,AspectAvy Staging,AspectAvy Production, andAspectAvy CIschemes, with committed.xctestplanfiles as the canonical iOS test entrypoint. - Xcode Cloud preparation is now checked in under:
Testing and CI status
- ios-architecture.md - ios-security-and-data-handling.md
- xcodebuild build -project apps/ios/AspectAvy/AspectAvy.xcodeproj -scheme 'AspectAvy Dev' ...
- xcodebuild test -project apps/ios/AspectAvy/AspectAvy.xcodeproj -scheme 'AspectAvy Dev' ...
- The iOS project now has generated unit-test targets for package tests, committed test plans, environment-separated schemes, and committed Xcode Cloud script scaffolding.
- Macro trust export and install scaffolding now exists for Xcode Cloud, along with post-build artifact collection for future snapshot runs.
- Stable iOS-local reference docs now exist for:
- The large iOS ownership files now have explicit ownership comments and invariants documented in-place, including MapFeature.swift, ForecastMapCanvas.swift, RoutePlanEditorFeature.swift, and OfflineRegionBundleClient.swift.
- TCA reducer coverage was expanded again, especially for forecast-zone details, offline maps, layer settings, legal documents, location search, route details, and the new route-plan trip-safety flows.
- App-hosted integration coverage now exercises the live app-edge routing seams in AspectAvyIntegrationTests.swift, including custom URLs, universal links, user activities, static and dynamic quick actions, notification payload routing, and route-bridge event buffering.
- A disabled snapshot harness now exists in AspectAvySnapshotTests.swift; it is intentionally gated behind
ENABLE_ASPECTAVY_SNAPSHOTS=1and should stay off until the planned visual overhaul begins. - Local app builds are currently green through:
- The default scheme-level
xcodebuild testpath is green again through: - Backend HTTP seam coverage now includes route-plugin guard tests for auth, user, route-management, route-intelligence, and debug-route registration, so the extracted backend route surfaces are no longer covered only by pure module tests.
- initial map camera application now waits for a real loaded map size before applying startup focus, which avoids the occasional Mapbox
64x64fit fallback and rare zoomed-out launch state - Feature-test dependency scoping now uses app-owned clock/date/UUID keys where needed, and the SQLiteData service suite uses explicit bootstrapped test databases instead of blank in-memory fallbacks.
- Manual validation remains the highest-value next step. Snapshot artifact assertions and additional host-app suites beyond the current app-edge coverage remain follow-on CI work, while snapshot execution itself stays disabled until the visual overhaul.
Remaining noisy logs
Some runtime noise is still expected during simulator development:
- RocketSim startup logs
- Apple simulator accessibility duplication warnings
- some
nw_connection_*chatter from simulator networking internals
These are not currently treated as blockers.
App-generated startup and forecast noise is lower now:
- RocketSim framework linking no longer uses raw
printstatements - expected bootstrap-config fallback no longer logs like a hard failure when the app can safely continue with bundled config
- successful forecast fetch/decode chatter now stays at debug level