Preset chip row, searchable project list, debounced directory
autocomplete, and a branch/worktree picker matching the web spawn
modal, wired to SpawnViewModel and a self-contained snackbar for
submit errors.
Pure request-building logic ported from web/src/components/spawn/model.ts,
plus a testable SpawnViewModel wiring presets, projects, debounced dir
autocomplete, branch loading, and session creation to MaestroClient.
ActivityRail (coloured gutter bar, blink on AwaitingInput, flash on
Error), SessionCard (two-line card with a long-press context menu for
rename/kill/spawn-similar), FolderNode (collapsible repo group), and
SidebarContent assembling the drawer's session list from the
CatalogStore built in #264.
Pure Kotlin ports of the web/TUI SIDEBAR.md logic for the Android
sidebar: duration floor-format, program-name extraction (env/sudo/env
wrappers, npx/pnpm/yarn dlx, path), diff-stat notation, and
name/name_source/provisional-name reads. Covered by unit tests so they
run without an Android SDK.
Reads the EXTRA_DAEMON_URL intent extra, starts DaemonLink, and shows its
LinkState in the placeholder screen so the connection indicator has
something to bind to. usesCleartextTraffic is required for the daemon's
plain-HTTP LAN endpoint; ACCESS_NETWORK_STATE is required for
ConnectivityManager.registerNetworkCallback.
Ports the DaemonLink state machine from web/src/components/daemon/DaemonLink.tsx
and crates/maestro-tui/src/daemon_link.rs to Android: LinkState (Connecting,
Online, Reconnecting, Down, Updating), build-stamp identity comparison, a
session catalog folded from the events WebSocket, and exponential backoff
(500ms-10s) in place of the flat poll the LAN clients use, since a phone's
network changes are bigger and longer-lived than a wifi blip.
DaemonLink reacts to ProcessLifecycleOwner (foreground resume) and
ConnectivityManager (network change) to re-probe and reconnect. DaemonPrefs
persists the daemon base URL via SharedPreferences and exposes an intent
extra key for overriding it at launch.
MaestroClient covers the daemon's REST surface: sessions CRUD, rename,
kill (with force), focus reporting, dirs/recent-dirs/projects/gitroot/
names/branches lookups, and the structured-session endpoints (prompt,
attachment upload, cancel, config, approval).
MaestroWebSocket covers all three WS connections — /api/events
(catalog deltas), /api/attach/:id (binary PTY frames both directions
plus ClientControl text frames for resize/ping), and
/api/structured/:id (transcript/state projections) — each exposing a
Flow of decoded events behind a connection handle.
Adds kotlinx-coroutines-android explicitly rather than relying on it
being pulled in transitively via lifecycle-runtime-ktx.
Compiled and exercised standalone against the same library versions
(no Android SDK available in this environment to run the app module
itself) to verify wire-format handling before landing.
Kotlin data classes mirroring maestro-protocol's wire types (Session,
CatalogEvent, ClientControl, structured-session projections, etc.),
@Serializable with kotlinx-serialization. ActivityState and NameSource
carry custom lenient deserializers matching the Rust side's forward-
compat behavior: an unknown variant degrades to the neutral default
instead of failing the whole payload.
Verified against fixtures mirrored from maestro-protocol's own test
suite (tag placement, snake_case renames, legacy-payload defaults).
Greenfield Gradle project under android/ — single-activity, Compose-only
UI, Kotlin DSL + version catalog throughout. MainActivity shows a
placeholder screen; no daemon connection yet (#262).
minSdk 26, targetSdk 34. OkHttp and kotlinx-serialization-json wired in
for the upcoming daemon client. Debug signing config uses a committed
debug.keystore so the APK installs without extra setup.