# Browser fallback fabric

**Implemented:** the local apps and bounded, hash-verified same-origin HTTP part reader do not require WebRTC. **Not implemented here:** generic peer-to-HTTP proxying, an automatic cross-carrier private-chat handoff, or a deployed WebSocket relay.

For the nontechnical walkthrough, see [Cursors on iPhone and iPad](ios-safari.md).

## Startup and explicit choice

The browser performs a local peer/data-channel construction check without media permission or ICE negotiation. Missing and throwing constructors select `http-only` (or `local-only` without fetch). WebTorrent is imported only after that probe succeeds. Successful construction is labeled API support, not proof of a remote connection.

The explicit `peers=off` URL setting skips the peer library and automatic reference-swarm join. It is useful when an API exists but peers never connect. Start, Chat, Sign in, Guides, and the cockpit still open; cloud controls continue to obey current service health and existing authorization gates.

## Published parts, not arbitrary magnets

A part already published in the deployment has this address:

```text
/parts/<content-id>/<piece>.part
```

The implemented request path is:

```text
caller supplies expected length + independently trusted SHA-256
  -> bounded in-memory / same-origin-tab candidate lookup
  -> on miss, bounded same-origin HTTP fetch
  -> verify exact length and SHA-256 on either carrier
  -> return verified bytes, carrier, and observation time
```

The part reader does not first negotiate WebRTC. The WebTorrent observer is separate. A magnet containing only an info hash is not enough to synthesize a published part or retrieve unavailable torrent metadata.

```js
await window.__cursorsAppRuntime.requestPart('cursors-fallback-demo', 0, {
  expectedBytes: 189,
  sha256: '6b1aec59252d6dc3f6ce601a8109e3661433cb9314be494862cd5aea15ddf726',
  timeoutMs: 8000,
});
```

This pin identifies only the checked-in public diagnostic part. Real callers must get their content commitments from an independently verified manifest or accepted cursor reference, never the untrusted response itself.

## Rejection and resource limits

Every request requires a SHA-256 commitment and exact expected length. Local candidates cannot bypass verification or supply their own trusted evidence. Keys are validated rather than normalized into colliding addresses. Parts are at most 4 MiB, cached memory at most 8 MiB/32 entries, and concurrent requests/tab waiters at most 16 each.

The default total request deadline is eight seconds, capped at thirty seconds when configured. A missing or blocked BroadcastChannel falls directly through to HTTP. Close and caller cancellation abort requests. HTTP must be a complete 200 response, not an unrequested partial response, redirect, or HTML SPA fallback. Bodies are read incrementally within the caller's length bound. Incorrect bytes are not returned or cached as verified content.

Integrity is not identity, authorization, or successful execution. A verified public test part does not prove mailbox admission, private-message confidentiality, or the availability of unrelated content.

## Other carriers and mobile lifecycle

Existing Cloudflare mailbox/GitHub actions use their HTTPS APIs independently of WebRTC, subject to the unchanged release and authorization gates. This change does not activate those services. A generic WebSocket relay remains unconfigured; TURN still requires WebRTC.

The diagnostic never automatically sends user data or retries writes. Hiding/leaving the page cancels it and clears its current-connectivity claim. Visible-page return and page-cache restoration refresh cloud health. Browser-local drafts are not a guaranteed cold offline boot or an always-running background service.

Remaining cross-carrier/hostile-source work stays in the existing audit and carrier-resolver gates. Do not interpret this bounded static-part path as completion of the entire distributed fallback design.
