Crosswake Web-To-Mobile Migration Guide

View Source

This is an operational route inventory guide for existing Phoenix SaaS teams. It is not a mobile rewrite essay. The working rule is simple: default most routes to Phoenix/LiveView, then promote only when a route has a concrete owner reason.

Use this guide after guides/user_flows.md and guides/route_policy.md. Those guides explain the route-owner mental model; this one turns it into a migration pass you can run against a real Phoenix product.

Default

Default most routes to :live_view.

That is not a compromise. It is the expected outcome for dashboards, accounts, settings, approval queues, billing history, admin screens, and normal SaaS forms. Phoenix already owns the data, auth, rendering, and validation loop for those routes. Crosswake makes that ownership explicit when the product crosses into a mobile shell.

Promote only for one of these reasons:

  • degradable bounded native affordance
  • cached read-only
  • true local mutation/replay
  • native-owned device session
  • backend/provider authority
  • explicit defer

Migration Passes

Pass 1: Inventory routes by user job

List routes by what the user is trying to do, not by controller, LiveView module, or navigation location. Start with the three adopter profiles:

  • Phoenix SaaS Portal: mostly LiveView product routes with one narrow native affordance.
  • Selective Native Flow: mostly Phoenix-owned routes with one native-owned device-heavy corridor.
  • Local-First Study Flow: cached read-only neighbors plus one offline island with local mutation and replay.

Pass 2: Assign the initial owner

Give every route an initial owner before adding seams:

  • :live_view for ordinary Phoenix product routes.
  • :live_view plus one bounded bridge affordance when native help is low-frequency and degradable.
  • :live_view plus cached read-only posture when stale reads are useful and safe.
  • :offline_island only when the route truly owns local mutation/replay.
  • :native_screen only when native code owns the session loop.
  • backend/provider seam when device or provider evidence feeds Phoenix/backend authority.
  • explicit defer when support would outpace proof.

Pass 3: Add only required seams

After owner assignment, add the smallest route-local contract that owner needs:

  • bounded bridge capabilities for one-shot actions such as haptics, app info, or a transfer-backed file picker
  • cache_contract for cached read-only routes
  • island_contract, packs, and sync for true offline islands
  • packs and transfers for native-owned capture or transfer corridors
  • auth_min_level, requires_recent_auth, auth_posture, and auth_return for backend-owned auth posture
  • commerce and notification-open declarations only as provider-neutral evidence or activation seams
  • gated_by and on_unavailable for explicit defers or rollout gates

Pass 4: Run doctor and support checks

Run the route through manifest and support truth before turning it into public proof:

mix crosswake.doctor --router Elixir.YourAppWeb.Router
mix crosswake.doctor --router Elixir.YourAppWeb.Router --native-checks

Use guides/support_matrix.md for proof classes and guides/compatibility.md for runtime-line and rebuild guidance. The support matrix is the canonical dense reference; this guide is only the inventory workflow.

Pass 5: Capture evidence for owner classes you use

Do not try to prove every possible mobile path. Capture evidence only for the owner classes your app actually uses:

  • Phoenix-owned LiveView route activation
  • one bounded bridge affordance, if declared
  • cached read-only degradation, if declared
  • one offline island replay path, if declared
  • one native-owned route-unavailable or native-screen path, if declared
  • backend/provider reconciliation posture, if declared

Phase 118 owns the command-verified quick start and adoption rewrite. Treat examples/QUICK_START.md and guides/adoption.md as hands-on follow-up surfaces, not as the source of this route-inventory contract.

Worksheet

RouteUser jobInitial ownerPromotion reasonRequired seamsEvidence
/saas/dashboardcheck current work:live_viewnonemanifest route, auth posturePhoenix-owned route activation
/saas/approvals/:idapprove one item:live_viewdegradable bounded native affordancecapabilities: ["haptics.impact"], entryapproval still succeeds without haptics
/study/historyreview previous work:live_viewcached read-onlyoffline: :cached_read_only, cache_contractstale read is labeled, no mutation
/study/sessionkeep studying offline:offline_islandtrue local mutation/replayisland_contract, packs, syncqueued/replayed outbox with explicit outcome
/native/claims/:id/capturecapture claim evidence:native_screennative-owned device sessionmedia pack, native-capture transfers, securityfail-closed route or native capture evidence
/commerce/purchasestart purchase intentbackend/provider seambackend/provider authoritycommerce corridor and backend reconciliationdevice/provider event remains evidence
/scan/documentpolicy-heavy scanner:native_screenexplicit defergated_by, on_unavailableunavailable posture until proof exists

Promotion Reasons

degradable bounded native affordance

Use this when Phoenix owns the route and the shell can help with one low-frequency semantic action. Read guides/bridge.md for the request/reply bridge contract and denial reasons.

cached read-only

Use this when a stale snapshot is useful but mutation would be dishonest. Read guides/offline.md for the difference between cached read-only and an offline island.

true local mutation/replay

Use this only when the route owns local writes, durable outbox or journal entries, and explicit replay/reconciliation outcomes. Nearby routes can still stay Phoenix-owned cached reads.

native-owned device session

Use this when native code owns a capture, scan, or device-heavy session loop. Read guides/native_shell.md for manifest-first activation, route-unavailable behavior, and native-owned routes.

backend/provider authority

Use this when device, storefront, webhook, notification, or provider data is evidence for Phoenix/backend authority. Read guides/capabilities.md for backend seam vocabulary and guides/compatibility.md for rebuild and compatibility truth.

explicit defer

Use this when the route is real but the proof or support contract is not. A defer with gated_by and on_unavailable is clearer than a silent fallback.

Do Not Migrate This

  • Do not move normal SaaS forms native just because the app is mobile.
  • Do not push high-frequency client authority through the bridge.
  • Do not call cached read-only pages offline mutation.
  • Do not treat device or provider events as authority without backend reconciliation.
  • Do not present local native hosts as generated public-coordinate proof.

Reference Map