Context
Child of #687. The smallest viable security + offline-tolerance story for v1. Big enough to be a separate PR, small enough that a real auth provider is explicitly out of scope.
Tokens
- One token per running room, generated by `aro room` on start. 24 random bytes, base64-url. Printed once.
- The hub accepts a token via query string (`?token=…`) and via the `Sec-WebSocket-Protocol` header. The URL form is for sharing; the header form is what the browser client actually uses after first load (URL token is moved into `sessionStorage` immediately so the share URL works after a refresh too).
- `--token READONLY` mints an additional read-only token. The hub flags the WebSocket connection so `file-edit` frames return `{"error": "read-only"}`.
Reconnect
- Client tracks the last `runtime-event` `ts` and `file-changed` rev it received.
- On socket close: exponential backoff (`0.5 → 1 → 2 → 4 → 8 → 8 …` capped at 8 s) up to a 2-minute total before giving up. Surfaces a banner: "Reconnecting…" then "Lost connection — refresh to retry."
- On reconnect, send `hello` with the cached `lastSeenRev`. The hub replays everything newer than that from its in-memory rolling buffer (last 5000 events ≈ 10 min of activity on a normal run).
IndexedDB cache
- Per-room namespace: `solaro-room-{room-id}`.
- Stores: latest file contents keyed by path (so a refresh shows last-seen state instantly while the WebSocket reconnects), the last 1000 `runtime-event` payloads (so the pulse history doesn't blank on refresh), the user's preferred peer name + colour.
- Eviction: on a successful `welcome` whose `project.rev` differs from the cached rev, evict file contents and runtime history for that room. The peer profile sticks.
Files
- `Sources/ARORuntime/Room/RoomAuth.swift` (new) — token store + check.
- `webclient/src/rooms/RoomClient.ts` — reconnect logic + IndexedDB integration.
Out of scope
- OAuth / SSO. The user said v1 lives on a trusted network.
- Token rotation. The room dies with the host process; new room = new token.
- Per-file ACLs. Either you can edit or you can't, room-wide.
Acceptance
- Sharing a read-only token blocks `file-edit` server-side and the client surfaces the rejection as a banner.
- Cycling the laptop's wifi for 30 s and back: the client shows "Reconnecting…", recovers, and the editor doesn't lose unsaved local edits.
- Hard refresh of the browser tab: file content + canvas appear instantly from IndexedDB before the socket finishes its first round-trip.
Context
Child of #687. The smallest viable security + offline-tolerance story for v1. Big enough to be a separate PR, small enough that a real auth provider is explicitly out of scope.
Tokens
Reconnect
IndexedDB cache
Files
Out of scope
Acceptance