Stateful journeys/Routes & Navigation
hybridroute planning session aggregate

iOS direction planning and navigation handoff

Own route intent and revisions in the host app, present MapplsDirectionUI for editing and calculation, validate the selected route callback, and hand off to navigation without confusing a UI request with a started or completed trip.

Every state means something operational

A state is not a UI label. It determines which actor may act, what evidence exists, what may be retried, and how recovery proceeds.

01
draft

Draft

The host owns one route intent, traveler context, and revision before presenting provider UI.

02
editing

Editing

One presented controller generation owns source, destination, via points, options, delegates, dismissal, and accessibility focus.

03
calculating

Calculating

The provider surface is resolving route alternatives for the current immutable location and option revision.

04
candidates_ready

Candidates ready

One or more provider route objects are visible for comparison but remain controller-scoped candidates.

05
selected

Selected

The traveler selected an in-range route index and the adapter copied a bounded route handoff value plus the exact location revision.

06
handoff_pending

Handoff pending

The documented start-navigation callback requested a host-owned navigation action, but no target navigator acknowledgement exists yet.

07
handed_offterminal

Handed off

The configured navigation adapter accepted the normalized route request and returned its own attributable session identity or acknowledgement.

08
cancelledterminal

Cancelled

The traveler dismissed or backed out and the host recorded one terminal cancellation without a route handoff.

Transitions are attributable and retry-safe

Commands express intent. The aggregate validates current state and invariants, commits one new version, and emits a fact in the same transaction.

Command & actorAllowed fromResultIdempotency strategy
create_planHost application
new aggregate
draftroute_plan.created

One external journey intent maps to one route-planning aggregate across retries.

open_direction_uiTraveler
draftselected
editingroute_plan.editor_opened

One command creates one presentation generation and one delegate ownership record.

request_routesTraveler
editing
calculatingroute_plan.calculation_requested

Hash normalized locations, options, and revision so duplicate requests share one logical calculation.

receive_routesMapplsDirectionUI
calculating
candidates_readyroute_plan.candidates_received

Accept only the active generation and revision; repeated callbacks replace no committed state.

select_routeTraveler
candidates_ready
selectedroute_plan.route_selected

Validate selectedRouteIndex against the returned collection and commit one normalized handoff value for the route revision.

request_navigationTraveler
selectedcandidates_ready
handoff_pendingroute_plan.navigation_requested

Bind callback generation, route revision, selected index, and host command key to one handoff attempt.

confirm_handoffNavigation adapter
handoff_pending
handed_offroute_plan.navigation_handed_off

Persist the target acknowledgement once and let its separate lifecycle own later progress.

cancel_planTraveler
drafteditingcalculatingcandidates_readyselectedhandoff_pending
cancelledroute_plan.cancelled

Repeated back, dismissal, or cancel signals dispose once and return the same terminal outcome.

Persist enough to recover and explain

Keep provider responses, business identity, state, events, and side-effect delivery distinct so each can be reconciled safely.

Route plan

Host-owned normalized stops, options, revision, lifecycle state, and optimistic version.

planIdexternalIdstaterouteRevisionversionowner

Route handoff candidate

Bounded portable value copied from the active route selection without retaining provider UI objects.

candidateIdrouteRevisionselectedIndexlocationDigestoptionDigestcreatedAt

Handoff attempt

Immutable request and target acknowledgement separating planning from navigation runtime.

attemptIdcandidateIdtargetstatustargetSessionRefrequestedAt

Audit and outbox

Attributable transitions and exactly-once-in-effect downstream notification.

eventIdaggregateVersionactoridempotencyKeyoutboxStatus

Protect truth at the aggregate boundary

  1. 1

    Stops, options, route candidates, and selection share one explicit revision.

  2. 2

    A selected index is validated before dereferencing its Route candidate.

  3. 3

    The provider controller and opaque Route objects never become durable application records.

  4. 4

    The start-navigation callback expresses intent, not proof that navigation started or completed.

  5. 5

    One presentation generation produces at most one terminal handoff or cancellation.

  6. 6

    Credentials and unrestricted location histories never enter route-planning audit events.

Know what to do before it breaks

Recovery changes durable truth only through the same rules as normal operation. A timeout is an unknown outcome, not evidence that nothing happened.

Route calculation fails or returns no alternatives

Detect

The active controller reports an error or has no valid selected route for the current revision.

Recover

Keep the editable draft, show a safe error, and allow option or stop revision before retry.

A delegate callback arrives from an old controller

Detect

The callback presentation generation differs from the aggregate's active generation.

Recover

Ignore it, dispose its resources, and leave the current route revision unchanged.

Selected route index is stale or invalid

Detect

The index is outside the current route collection or belongs to a superseded calculation revision.

Recover

Reject the handoff and require visible reselection from current candidates.

Navigation target rejects or times out

Detect

No target acknowledgement exists for the handoff identity inside the bounded deadline.

Recover

Remain handoff pending, expose retry or return-to-selection, and do not claim an active navigation session.

Observe the journey, not merely HTTP status

Measure state age, event health, retries, reconciliation, and sensitive-data access alongside latency and error rate.

  • Editor presentation, dismissal, and terminal result by released component version
  • Calculation latency, failure, and zero-alternative rate
  • Stop and option revision count before selection
  • Candidate-to-selection and selection-to-handoff conversion
  • Invalid index, stale generation, duplicate callback, and late callback rejection
  • Handoff acknowledgement latency and target rejection rate
  • Idempotency replay and optimistic version conflict rate

Contracts, repositories, workshops, tutorials, and runnable evidence

Only explicitly indexed evidence is linked. Empty sections are not backfilled with invented endpoints or package names.