adapter.py was a 3,493-line monolith. Split it into focused modules with
clear separation of responsibilities, bringing it down to ~857 lines:
- Module-level helpers: hooks, classify, pickers, commands, setup,
defaults, secrets
- Frame-handler mixins: inbound, tool_frames, push_frames, media_frames,
picker_frames, channel_frames, query_frames
- mixin_base: IrisAdapterBase (declaration-only base for shared attrs)
- adapter.py now holds only IrisAdapter (the composition of the 7 mixins
+ BasePlatformAdapter), register(), and test-facing re-exports
The mixins come before BasePlatformAdapter in the MRO so their methods
override the base; super() calls (e.g. send_image) still resolve to
BasePlatformAdapter. No circular imports; dispatch.py and http_server.py
(instance-method callers) are unaffected.
Ruff complexity ceilings (PLR0911/0912/0913/0915) restored to Ruff's
built-in defaults (12/50/6/5) instead of "just above the current maxima",
which ratchets the bar down as code grows. The existing genuinely-complex
functions (frame builders mirroring the wire schema, the QR matrix builder,
the dispatch table) carry an explicit `# noqa: PLR09xx` marking them as
reviewed, frozen exceptions; new code is held to the default ceilings.
All 125 tests green (94 test_android + 31 test_android_http); no new ruff
errors introduced.
Gateway (docs/19):
- Remove ws_server.py; frame dispatch factored into dispatch.py
- http_server: media upload/pull, pairing over HTTP
- protocol: media frames mirrored; tests + ws_probe updated for HTTP
App:
- HttpGateway: postFrame/uploadMedia/pullMedia no longer throw on
network failure (PostResult ok=false / Result.failure) — uncaught
SocketTimeoutException on Dispatchers.Default crashed the app
- GatewayClient: dead-stream watchdog (health probe every 10s, 2
failures -> redial in ~20s instead of the 45s SSE read timeout);
state flips to Reconnecting when the stream dies, restored from the
last hello.ack on long-poll success; poke() + backoff reset on app
resume (MainActivity.onResume)
- Offline sends: composer enabled while disconnected; a send with no
response (status 0) stays queued (Pending) and is auto-resent on the
next (re)connect after a 2s outbox-replay grace; gateway 4xx
rejections fail the bubble (tap to retry, no auto-loop)
- ChatStore: echo-replace and thread-relocate also match Failed
bubbles (POST response lost in a network drop); loadHistory dedupes
local failed bubbles the server already has; failMessage()
- MainActivity: poke() on resume so a backgrounded app reconnects
promptly instead of waiting out the backoff