feat(x): rowboat-server vertical slice + iOS companion app - #758
Conversation
2ab9925 to
116a2bc
Compare
|
Gagan — this is great work. It follows the RFC decision-by-decision, you even nailed the addendum stuff (delta gating, offset splice), and making the server a library that never boots core was exactly the right call. Two things I want before we merge, then ship it. 1. Turn forwarding on in packaged builds, now. Right now 2. Fix the stale-turn-on-reconnect bug. If the phone's WS drops and the turn finishes while it's offline, the turn renders frozen mid-stream forever — Everything else can be follow-up issues: the split-brain lock is one-sided (main's in-process host never takes |
|
Both fixed in 07f5b2c:
Filed the non-blocking items as #879 (one-sided lock + silent port fallback), #880 (WS token in query string), #881 (Host validation), #882 (symlink escape on the workspace route). On Phase 1 ownership — want me to take the server-side piece (channel migration + the child-process flip), or do you want to drive it with your refactor? Happy either way; I'll keep driving the phone client (voice + TestFlight next) regardless, and +1 on the rule — anything we touch goes through |
Implements the phone-client slice of the server/client RFC (#700): - apps/server (@x/server): HTTP transport generated from ipcSchemas (POST /rpc/{channel}, allowlisted subset), server-key bearer auth, WS /events hub (per-connection seq, durable broadcast + per-turn delta subscriptions, hello handshake), authenticated workspace file route, headless standalone entrypoint with pid lockfile - main hosts the transport in-process on its single core instance and forwards migrated channels over localhost HTTP (strangler-fig, ROWBOAT_FORWARD_MIGRATED kill switch, on by default in dev) - Phone-app settings tab: QR pairing, LAN opt-in, key rotation - packages/client (@x/client): portable typed RPC + reconnecting WS client (seq-gap detection, refcounted delta subs, 401/4401 handling) - apps/mobile (@x/mobile): Expo iOS app — QR/manual/dev-link pairing, sessions list, live chat (shared turn-follower + reduceTurn, delta streaming, permission/ask-human prompts), read-only notes browser, PostHog events (platform=mobile), EAS build profiles - turn-follower moves from renderer into @x/shared (unchanged), so desktop and phone share the join/streaming protocol verbatim
The new packages are gitignored dist consumers like shared/core, but the forge generateAssets hook and the root test/typecheck scripts didn't build them: the Electron package smoke test failed resolving @x/server from main, and the server vitest suite failed importing @x/client.
…h turns on WS reconnect
- rpc-forwarder: forwarding defaults ON everywhere (packaged builds
included) so production desktops exercise the HTTP path daily;
ROWBOAT_FORWARD_MIGRATED=0 stays as the kill switch
- turn-follower: followTurn now returns { stop, refetch }. refetch()
forces a fresh snapshot — required for droppable transports where a
turn that finished during an outage never emits another event, so
offset-gap detection alone can leave it frozen mid-stream. The phone
calls it from the events client's onResync; renderer behavior is
unchanged. Covered by a new turn-follower test.
d7f4798 to
a0c0ed0
Compare
What this is
The phone-client vertical slice of the server/client RFC (#700), working end-to-end: a minimal-but-end-state-correct rowboat-server transport hosted inside the Electron app, and an Expo iOS app that pairs with it over LAN — chat with live turn streaming, read-only notes, QR pairing. Verified live in the iOS simulator against real sessions (screenshots in the thread on request).
How it's shaped (for the server refactor)
The RFC's hard constraint this slice respects: exactly one core instance exists.
apps/serveris a transport library — it does not boot core. Its dependencies (handler map, turn/session buses, workspace resolver) are injected:apps/x/apps/server/src/server.ts—createRowboatServer({handlers, events, resolveWorkspacePath, ...})assembles HTTP router + WS hub + workspace file route on onenode:httpserver (Hono + ws).src/channels.ts— the allowlisted channel subset (RPC_CHANNELS). This is the strangler-fig frontier: migrating a channel group = adding names here + a handler insrc/core-deps.ts(thincontainer.resolvepass-throughs, lifted verbatim from main'sipc.ts).src/ws-hub.ts— wire protocol: per-connection monotonicseq,hellohandshake (capability slot for RFC Q14), durable events broadcast to all clients,text/reasoningdeltas per-connection per-turn ({type:'subscribe', topic:'turn-deltas', turnId}) — the network twin of main'sturnDeltaSubsregistry.src/standalone.ts— the RFC end-state headless entrypoint (fallback DI services, pid lockfile against split-brain). Test/dev-only until the Phase 1 flip; the flip is boot-wiring only (main stops booting core, spawns this, becomes a WS client).apps/main/src/server-host.ts) and keeps its ownwebContents.sendfan-out alongside the hub (dual fan-out dies with the flip). The renderer's migrated channels cross real localhost HTTP viaapps/main/src/rpc-forwarder.ts(ROWBOAT_FORWARD_MIGRATED, default on in dev, off in packaged builds) so the API is exercised daily.Auth is the RFC Q8 scheme: server-key bearer (
~/.rowboat/server-key, 0600), QR pairing from Settings → Phone app,127.0.0.1bind by default with LAN opt-in, key rotation revokes all clients (phones auto-return to pairing on 401/4401).Client side
packages/client(@x/client): portable typed RPC + reconnecting WS client (backoff, seq-gap → resync callback, refcounted delta subs). Also the future implementation of main's forwarder.apps/mobile(@x/mobile, Expo SDK 57): pairing (QR/manual/dev deep-link), sessions, chat (reusesfollowTurn+reduceTurnfrom @x/shared verbatim —turn-follower.tsmoved renderer → shared unchanged), notes, PostHog (platform=mobile), EAS profiles.Tests / verification
apps/server/src/*.test.ts)npm run typecheckgreen;expo export --platform iosbundles clean through pnpmNot in this PR
Voice (needs a dev build + signed-in account — client code seams are ready), push notifications (RFC Q14 reverse calls), remaining ~200-channel migration, TLS/remote deployment.
🤖 Generated with Claude Code