Current section

Files

Jump to
crosswake README.md
Raw

README.md

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/szTheory/crosswake/main/brandbook/collateral/readme-header-dark.svg">
    <img src="https://raw.githubusercontent.com/szTheory/crosswake/main/brandbook/collateral/readme-header.svg" alt="Crosswake">
  </picture>
</p>

# 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:

- route policy
- a versioned runtime manifest
- host-owned native shells
- a bounded bridge for low-frequency native affordances
- explicit pack and transfer seams
- offline islands and cached read-only routes

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

## What this is not

Crosswake is not:

- React Native for Phoenix
- a generic WebView wrapper
- LiveView rendering native UI directly
- a universal shared UI framework
- “write once, run anywhere”
- “offline magically works”

## See it run

```bash
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.

<img
  src="https://raw.githubusercontent.com/szTheory/crosswake/main/brandbook/collateral/see-it-run/web-home.png"
  alt="Crosswake showcase hub at localhost:4700 — the SaaS/Admin, Field Service, and Learning/Training lanes with route-owner and support-truth labels"
  width="900"
/>

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:

- `/offline` — offline island (app-owned, socketless)
- `/bridge-proof` — bounded bridge (share capability)
- `/native/claims` — native-pressure route list

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](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](guides/support_matrix.md#support-truth-label-legend).

For a guided walkthrough: [guides/see_it_run.md](guides/see_it_run.md).
For the full proof command reference: [examples/QUICK_START.md](https://github.com/szTheory/crosswake/blob/main/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:

- [guides/architecture.md](guides/architecture.md) for the system mental model from route declaration to runtime owner or denial
- [guides/route_policy.md](guides/route_policy.md) for the start-here route-owner map
- [guides/web_to_mobile_migration.md](guides/web_to_mobile_migration.md) for an operational Phoenix route inventory pass
- [guides/user_flows.md](guides/user_flows.md) for the fastest JTBD and user-flow ramp-up
- [guides/adopter_profiles.md](guides/adopter_profiles.md) for the three target app shapes
- [guides/install.md](guides/install.md) for the public install and proof path
- [guides/support_matrix.md](guides/support_matrix.md) for the current supported baseline
- [examples/phoenix_host/README.md](https://github.com/szTheory/crosswake/blob/main/examples/phoenix_host/README.md) for the shared exemplar host contract

Use the [canonical support matrix](guides/support_matrix.md) 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:

<!-- cleanroom:apply id=readme-deps-crosswake file=mix.exs mode=deps -->
```elixir
def deps do
  [
    {:crosswake, "~> 0.2"}
  ]
end
```

<!-- cleanroom:run id=readme-deps -->
```bash
mix deps.get
```

<!-- cleanroom:run id=readme-install -->
```bash
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](guides/install.md)):

<!-- cleanroom:apply id=readme-bridge-hook file=assets/js/app.js mode=js-hook -->
```javascript
import {CrosswakeBridge} from "/crosswake/crosswake.esm.js";
const liveSocket = new LiveSocket("/live", Socket, {params, hooks: {CrosswakeBridge}});
```

<!-- cleanroom:apply id=readme-esbuild file=config/config.exs mode=esbuild-args -->
```elixir
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets --external:/crosswake/* --format=esm)
```

<!-- cleanroom:apply id=readme-route file=lib/my_app_web/router.ex mode=router-route -->
```elixir
live "/study", StudyLive, crosswake: [id: "study-session", runtime: :live_view, offline: :unavailable]
```

<!-- cleanroom:apply id=readme-attach file=lib/my_app_web/live/study_live.ex mode=create -->
```elixir
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:

<!-- cleanroom:run id=readme-doctor -->
```bash
mix crosswake.doctor
```

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

<!-- cleanroom:apply id=readme-ios-host file=config/config.exs mode=append since=0.2.12 -->
```elixir
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.

<!-- cleanroom:show -->
```bash
mix crosswake.gen.shell ios
mix crosswake.gen.shell android
```

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

<!-- cleanroom:clone id=readme-phase5-example-hosts -->
```bash
bash script/verify_phase5_example_hosts.sh
```

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

<!-- cleanroom:clone id=readme-native-checks -->
```bash
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:

- [guides/code-walkthrough.md](guides/code-walkthrough.md) for a source-level trail through policy, manifests, activation, and the native contract
- [AGENTS.md](https://github.com/szTheory/crosswake/blob/main/AGENTS.md)
- [guides/install.md](guides/install.md)
- [guides/native_shell.md](guides/native_shell.md)
- [guides/packs.md](guides/packs.md)
- [guides/offline.md](guides/offline.md)
- [docs/COMPANION-PUBLISH-RUNBOOK.md](https://github.com/szTheory/crosswake/blob/main/docs/COMPANION-PUBLISH-RUNBOOK.md) for pre-release procedure and [docs/RELEASE-INCIDENT-RESPONSE.md](https://github.com/szTheory/crosswake/blob/main/docs/RELEASE-INCIDENT-RESPONSE.md) if a publish fails. `mix crosswake.release.status [--json] [--live]` reports package-family status; it is read-only, and `--live` adds best-effort public registry probes. It reports findings and a next action but does not create a candidate receipt or authorize publication.

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:

- Which routes stay `:live_view`?
- Which routes can be cached read-only?
- Which flows need an `:offline_island`?
- Which routes must become `:native_screen`?
- Which capabilities, packs, transfers, and security posture does each route need?

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:

- **Phoenix SaaS Portal**: mostly LiveView, one bounded native affordance
- **Selective Native Flow**: mostly Phoenix-owned, one explicit native route
- **Local-First Study Flow**: one honest offline island plus cached neighbors

See [guides/adopter_profiles.md](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](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](guides/support_matrix.md#first-adopter-readiness) for
the executable support boundary.

- [guides/support_matrix.md](guides/support_matrix.md) is the canonical support-status surface.
- [guides/support_matrix.md#support-truth-label-legend](guides/support_matrix.md#support-truth-label-legend) defines support-truth labels: merge-blocking proof, advisory evidence, checked-in public-coordinate proof, local-dev proof, generated public-coordinate proof, JVM hermetic proof, emulator evidence, device evidence, verification-required, and rebuild-required.
- [guides/troubleshooting.md](guides/troubleshooting.md) maps doctor findings, denial reasons, route-unavailable states, offline replay outcomes, and native evidence labels to route-owner fixes.
- [guides/install.md](guides/install.md) is the canonical install and proof-entry guide.
- The checked-in `examples/ios_shell_host` and `examples/android_shell_host` hosts are `checked-in public-coordinate proof`; use `--local` only for maintainer/local-dev proof.
- `bash script/verify_phase5_example_hosts.sh` is the primary checked-in proof lane (needs a clone).
- The route-tour evidence path uses `merge-blocking proof` for browser semantic assertions, manifest labels for artifacts, and `advisory evidence` for native simulator/emulator collateral.
- `mix crosswake.doctor` discovers the installed router and stays green with native support `not_claimed` before a shell exists; `--native-targets none` makes that CI posture explicit.
- `mix crosswake.doctor --native-checks` reruns verification hooks for generated or explicitly claimed native targets (needs a clone).

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

- [guides/architecture.md](guides/architecture.md) — outside-in route ownership, manifest, activation, package, and proof model
- [guides/code-walkthrough.md](guides/code-walkthrough.md) — inside-out reading trail through current Elixir, Swift, and Kotlin contracts
- [guides/install.md](guides/install.md) — Phoenix install path and native shell generation
- [guides/route_policy.md](guides/route_policy.md) — start-here route-owner decisions and DSL examples
- [guides/web_to_mobile_migration.md](guides/web_to_mobile_migration.md) — route inventory pass for existing Phoenix SaaS apps
- [guides/user_flows.md](guides/user_flows.md) — JTBD and route-by-route adopter ramp-up
- [guides/native_shell.md](guides/native_shell.md) — manifest-first activation and shell contract
- [guides/bridge.md](guides/bridge.md) — bounded bridge vocabulary
- [guides/packs.md](guides/packs.md) — required packs, transfers, and native capture handoff
- [guides/offline.md](guides/offline.md) — cached read-only and offline-island posture
- [guides/compatibility.md](guides/compatibility.md) — compatibility and denial posture
- [guides/support_matrix.md](guides/support_matrix.md) — proof classes, support labels, and rebuild truth
- [guides/troubleshooting.md](guides/troubleshooting.md) — route-owner fixes for doctor findings, denials, offline outcomes, and native evidence labels

## Current baseline

- Elixir `~> 1.19`
- Phoenix `~> 1.8`
- Phoenix LiveView `~> 1.1`
- Crosswake version `0.2.12` <!-- x-release-please-version -->
- Generated non-local native shell core coordinates resolve at the Crosswake package version.

See [mix.exs](mix.exs) and [guides/support_matrix.md](guides/support_matrix.md) for
the current package and platform baseline.