Crosswake

Crosswake

Declare the crossing. Phoenix-native route policy for apps that go mobile.

Crosswake is an open-source Elixir library for shipping iOS and Android apps from Phoenix applications without pretending one runtime should own every screen. Server-centric routes can stay LiveView, device-heavy flows can move into explicit native screens, and local-first work can live in honest offline islands.

Crosswake's one job is to declare, enforce, and diagnose which runtime owns each route as a Phoenix app crosses into mobile.

Steps marked "Needs a clone" are for contributors; every other step works from Hex alone.

What this is

Crosswake gives Phoenix apps a mobile runtime contract built around:

The core idea is simple: runtime ownership is explicit per route.

What this is not

Crosswake is not:

See it run

bin/see-it-run.sh

Needs a clone of the Crosswake repository; skip this if you installed from Hex.

Boots the shared backend on port 4700 and auto-opens http://localhost:4700/, the showcase hub for the checked-in example host. Requires Docker.

Crosswake showcase hub at localhost:4700 — the SaaS/Admin, Field Service, and Learning/Training lanes with route-owner and support-truth labels

Open the showcase hub first at http://localhost:4700/. It introduces the SaaS/Admin, Field Service, and Learning/Training lanes with route-owner labels before asking you to inspect proof routes.

The root route / — home showcase hub, LiveView route, cached read-only — is the newcomer entrypoint for this checked-in host.

Proof routes stay one click deeper and are secondary to the product-shaped showcase:

Support-truth labels in this first run stay narrow: Available today and Proof-backed example describe web proof surfaces, while Demo pressure and Future gap describe native-control candidates that are not shipped broadly. Showcase screenshots explain the product surface; route-tour assertions prove route-owner semantics.

Read Capability Map: guides/capability_map.md separates available support, proof-backed examples, demo pressure, future gaps, and v20 next-pack candidates. Screenshots remain collateral after route-tour assertions, not proof posture.

Advisory native collateral. iOS Simulator and Android Emulator runs are emulator evidence — advisory, not physical-device proof. A successful simulator or emulator run confirms the dev wiring reaches the local backend, but does not prove it works on a physical device. See the support-truth label legend.

For a guided walkthrough: guides/see_it_run.md. For the full proof command reference: examples/QUICK_START.md.

Choose your path

Evaluating Crosswake

Current answer: Crosswake is a Phoenix-first route-policy and runtime-contract system; it assigns one explicit owner to each managed route and fails closed when that contract cannot be satisfied. Start with the architecture guide for the model.

Use this map:

Use the canonical support matrix for current support and proof detail.

Integrating Crosswake

Current answer: your Phoenix router owns route declarations and each generated shell is host-owned. Crosswake owns compilation, compatibility checks, and bounded diagnostics. Use this current path, then let mix crosswake.doctor name the exact owner or action when proof cannot proceed:

Add the dependency, fetch it and install:

def deps do
  [
    {:crosswake, "~> 0.2"}
  ]
end
mix deps.get
mix crosswake.install

Wire the bridge hook, tell esbuild to leave the absolute import alone and emit ESM, declare one route and attach the bridge (each step is explained in guides/install.md):

import {CrosswakeBridge} from "/crosswake/crosswake.esm.js";
const liveSocket = new LiveSocket("/live", Socket, {params, hooks: {CrosswakeBridge}});
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets --external:/crosswake/* --format=esm)
live "/study", StudyLive, crosswake: [id: "study-session", runtime: :live_view, offline: :unavailable]
defmodule MyAppWeb.StudyLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(
        crosswake_manifest: MyAppWeb.Crosswake.Policy.manifest(),
        crosswake_route_id: "study-session"
      )
      |> Crosswake.Bridge.attach()

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <%!-- ONE element per page. Client events broadcast to EVERY mounted hook, so a
          second element would post every bridge request to the shell twice. --%>
    <div id="crosswake-bridge" phx-hook="CrosswakeBridge" phx-update="ignore"></div>
    """
  end
end

Then check the host:

mix crosswake.doctor

Add native targets only when this host starts claiming them. For an iOS shell, declare its build inputs first:

config :crosswake, :ios_host,
  router: MyAppWeb.Router,
  origin: "https://study.crosswake.test",
  entry_route_id: "study-session",
  app_bundle_id: "org.crosswake.host",
  proof_app_bundle_id: "org.crosswake.host.proof",
  device_signing: [mode: :automatic, team_id: "ABCDE12345"]

mix crosswake.gen.shell ios reads this setting in Crosswake releases after 0.2.11; see the CHANGELOG [Unreleased] section for the release that adds it.

mix crosswake.gen.shell ios
mix crosswake.gen.shell android

The example-host proof lane and the native verification hooks are repository scripts:

bash script/verify_phase5_example_hosts.sh

Needs a clone of the Crosswake repository; skip this if you installed from Hex.

mix crosswake.doctor --native-checks

Needs a clone of the Crosswake repository; skip this if you installed from Hex.

Crosswake owns the DSL, manifest contract, doctor tooling, and proof posture. Your Phoenix host and generated shells are host-owned after generation.

Contributing or maintaining

Read:

The checked-in example hosts under examples/ are the primary public proof artifacts.

Architecture at a glance

Crosswake is strongest when teams can answer these route-by-route questions clearly:

The current runtime ladder is:

  1. :live_view
  2. :live_view inside a native shell
  3. :live_view plus bounded native affordances
  4. cached read-only routes
  5. :offline_island
  6. :native_screen

Where Crosswake fits

Crosswake is currently shaped around three adopter profiles:

See guides/adopter_profiles.md for the profile matrix, representative routes, and explicit non-goals. For the fastest "how would I actually use this in my app?" pass, start with guides/user_flows.md.

Proof and support posture

Crosswake treats diagnostics, support truth, and proof lanes as part of the product surface.

Blocked — sanitized route policy and signed-device proof are required before this host can be promoted. Retained reference evidence is dated, source-bound, and does not verify the first adopter's host. See the current first adopter claim layers for the executable support boundary.

Crosswake stays deliberately narrow and explicit. Unsupported or incompatible routes fail closed onto explicit denial behavior instead of silently degrading into a generic container.

Guide map

Current baseline

See mix.exs and guides/support_matrix.md for the current package and platform baseline.