When the WS is down but the gateway is reachable over HTTP, the app
stays sendable instead of waiting out the WS redial backoff:
- HttpGateway.kt: OkHttp client for the HTTP leg — GET /v1/health
(2 s probe), POST /v1/frame (accept-and-ack; 4xx error frames parsed),
SSE GET /v1/events (hand-rolled line parser: event/id/data, comments,
multi-line data, Last-Event-ID bookkeeping), long-poll GET /v1/poll.
Per-purpose call timeouts (SSE heartbeat 15 s / poll hold 25 s exceed
OkHttp's 10 s default read timeout). deriveHttpUrl: ws(s)://host:port
-> http(s)://host:8791 (pure, unit-tested).
- GatewayClient.kt: new State.HttpFallback (sibling of Connected, both
implement State.HelloInfo). connectLoop races the WS dial against the
health probe (sendable in < 1 s on a dead WS port); on WS loss it
enters fallback immediately (no backoff gate on the send path); on WS
reconnect it stops the HTTP leg (media available again). sendMessage/
sendFrame route to POST in fallback (mediaRefs dropped — media is
WS-only in v1); SSE frames feed the same _events flow, so request-id
correlation is unchanged. The SSE hello is treated like hello.ack
(caps/channels/lastPushedCursor + onHelloAck fast path). After two
consecutive SSE open failures the receive loop switches to long-poll
until the next full (re)connect.
- IrisController.kt: onHelloAck/onConnectedLane take State.HelloInfo;
state collector + deep-link/commands-catalog gates accept fallback.
- ChatScreen.kt: send gate accepts fallback; attach button disabled in
fallback (media needs the live connection); status pill shows
'connected · http' (green).
- Tests: HttpGatewayTest (URL derivation + SSE parser, 8 tests); full
:shared desktop + android-host suites green.