Skip to content

Desktop Compose UI App

✅ Implemented 🧪 Tested

Current state: The desktop app is a standalone Compose Desktop application that provides a multi-dashboard UI for device observation, navigation mapping, failure analysis, performance monitoring, layout inspection, storage editing, telemetry, test management, and diagnostics. It communicates with the AutoMobile MCP server via three transport strategies (Streamable HTTP, Unix socket, STDIO). See the Status Glossary for chip definitions.

Three-Module Architecture

The desktop app is split into three Gradle modules under android/:

graph TD
    A["desktop-app
(entry point)"] --> B["desktop-core
(business logic + UI)"] B --> C["desktop-domain
(pure Kotlin interfaces)"] classDef app fill:#CC2200,stroke-width:0px,color:white; classDef core fill:#525FE1,stroke-width:0px,color:white; classDef domain fill:#007A3D,stroke-width:0px,color:white; class A app; class B core; class C domain;
Module Purpose Key Contents
desktop-domain Pure Kotlin data models with no framework dependencies NavigationModels, FailureModels, PerformanceModels, LayoutModels, StorageModels, TestModels
desktop-core Business logic, daemon clients, data sources, ViewModels, and all Compose UI daemon/, datasource/, failures/, navigation/, performance/, layout/, storage/, telemetry/, test/, shell/, settings/, testing/
desktop-app Thin entry point: window creation, Metro DI bootstrap, single-instance lock Main.kt, AutoMobileDesktopApp.kt, AutoMobileGraph.kt, AutoMobileTheme.kt

desktop-domain

Contains six model files that define the domain vocabulary. All types are plain data class or enum class with no serialization annotations, keeping this module dependency-free. Highlights:

  • NavigationGraph, ScreenNode, ScreenTransition – graph-based navigation model
  • FailureGroup, FailureOccurrence, FailureType, FailureSeverity – failure aggregation
  • PerformanceMetric, PerformanceAnomaly, PerformanceRun, PerformanceThresholds – metrics and thresholds
  • UIElementInfo, ParsedHierarchy, ObservationData, ConnectionStatus – layout inspection
  • DatabaseInfo, KeyValueFile, KeyValueEntry – on-device storage inspection
  • TestCase, TestRun, TestStep, RecordedAction – test management

desktop-app

The entry point module is intentionally small:

  1. Main.kt – acquires a file-lock (automobile-desktop.lock) to enforce single-instance, creates the Metro AutoMobileGraph, sets macOS native transparent title bar properties, and launches a 1440x900 Compose window.
  2. AutoMobileDesktopApp.kt – wraps AutoMobileContent (from desktop-core) with an AutoMobileTheme and an ObservableSettingsProvider that bridges SettingsProvider changes into Compose snapshot state.
  3. AutoMobileTheme.kt – Material 3 color scheme with light and dark variants. Resolves themeMode strings ("dark", "light", "system") to the appropriate scheme. Defaults to dark to match the IDE plugin.

DI System

The app uses Metro for compile-time dependency injection.

``` @DependencyGraph(scope = AppScope::class) interface AutoMobileGraph { val autoMobileClient: AutoMobileClient val settingsProvider: SettingsProvider

@DependencyGraph.Factory
fun interface Factory { fun create(): AutoMobileGraph }

} ```

ApplicationModule contributes bindings to AppScope:

Binding Provider
AutoMobileClient McpClientFactory.createPreferred(null) – selects the best available transport
SettingsProvider FakeSettingsProvider() (placeholder until persistent settings are wired)

Scope annotations @SingleIn(AppScope::class) ensure singletons live for the app lifecycle.

Daemon Communication

The desktop app communicates with the AutoMobile MCP server through the AutoMobileClient interface, which exposes JSON-RPC methods for MCP resources, tools, device management, observation, and storage operations.

graph LR
    UI["Desktop UI"] --> AC["AutoMobileClient"]
    AC --> HTTP["McpHttpClient
Streamable HTTP"] AC --> UNIX["McpDaemonClient
Unix Socket"] AC --> STDIO["McpStdioClient
STDIO Process"] HTTP --> D["MCP Server"] UNIX --> D STDIO --> D classDef client fill:#CC2200,stroke-width:0px,color:white; classDef core fill:#525FE1,stroke-width:0px,color:white; classDef ext fill:#007A3D,stroke-width:0px,color:white; class UI client; class AC,HTTP,UNIX,STDIO core; class D ext;

Transport Implementations

Client Transport Session Management Retry
McpHttpClient HTTP POST to /auto-mobile/streamable mcp-session-id header RetryPolicy with exponential backoff, jitter, retryable error classification
McpDaemonClient Unix domain socket at /tmp/auto-mobile-daemon-{uid}.sock Per-request connection None (single-shot)
McpStdioClient stdin/stdout of a spawned child process Persistent process None

McpClientFactory

McpClientFactory selects the transport automatically:

  1. If an McpHttpServer is provided (from discovery), use McpHttpClient.
  2. If AUTOMOBILE_MCP_HTTP_URL env var or system property is set, use McpHttpClient.
  3. If AUTOMOBILE_MCP_STDIO_COMMAND env var or system property is set, use McpStdioClient.
  4. Fall back to McpDaemonClient (Unix socket).

createFromProcess(McpProcess) binds a client to a detected running process by its connection type.

RetryPolicy

McpHttpClient uses a configurable RetryPolicy for transient failures:

  • Max retries: 3
  • Initial delay: 1000ms, backoff multiplier: 2x, max delay: 30s
  • Jitter fraction: 10%
  • Retryable errors: ConnectException, HttpTimeoutException, HTTP 5xx

McpHttpDiscovery

Discovers running MCP servers by scanning listening TCP ports and probing /health endpoints:

  1. DefaultPortScanner – runs lsof/netstat/ss to find listening ports
  2. HttpHealthProbe – sends GET to /health and /auto-mobile/health with 800ms timeout
  3. Matches discovered servers against GitWorktreeLister output (maps branches to worktrees)
  4. Returns McpDiscoverySnapshot with McpServerOption entries combining server + worktree info

McpProcessDetector

Detects running AutoMobile processes via ps and classifies their connection type (Streamable HTTP, Unix Socket, STDIO) by inspecting command-line arguments and lsof output. Uses injectable ProcessRunner, SocketFileChecker, and TimeProvider interfaces for testability.

Stream Clients

Real-time data flows over Unix domain sockets, separate from the request/response MCP channel:

Client Socket Path Data
ObservationStreamClient ~/.auto-mobile/observation-stream.sock Hierarchy updates, screenshot updates, navigation graph updates, performance metrics
FailuresStreamSocketClient Failures socket Failure notifications, groups, timeline data
PerformanceAuditStreamSocketClient Performance socket Performance audit poll results
TelemetryPushSocketClient Telemetry socket Custom telemetry events

ObservationStreamClient

Maintains a persistent connection with subscribe/unsubscribe semantics. Exposes Kotlin SharedFlow instances:

  • hierarchyUpdates: SharedFlow<HierarchyStreamUpdate>
  • screenshotUpdates: SharedFlow<ScreenshotStreamUpdate>
  • navigationUpdates: SharedFlow<NavigationGraphStreamUpdate>
  • performanceUpdates: SharedFlow<PerformanceStreamUpdate>
  • connectionState: SharedFlow<StreamConnectionState>

Connection state is modeled as a sealed class: Connecting, Connected, Disconnected(reason).

Data Source Layer

Each data domain follows a two-part pattern (interface with Real and Fake implementations). Navigation and AppList additionally use a cached wrapper:

Interface --> RealXxxDataSource (calls AutoMobileClient) --> FakeXxxDataSource (returns canned data) --> CachedXxxDataSource (wraps delegate with InMemoryCache) [Navigation, AppList only]

DataSource Domain Cache TTL
NavigationDataSource NavigationGraph 30s (via CachedNavigationDataSource)
AppListDataSource List<InstalledApp> 30s (via CachedAppListDataSource)
LayoutDataSource Layout/observation data
PerformanceDataSource Performance metrics
StorageDataSource Key-value files, databases
TestDataSource Test cases and runs
FailuresDataSource Failure groups

InMemoryCache

A generic TTL-based cache using ConcurrentHashMap for lock-free reads and per-key Mutex for coalesced fetches. Injectable clock lambda enables deterministic testing.

DataSourceFactory / DataSourceMode

DataSourceFactory creates the appropriate data source implementation based on DataSourceMode (Real, Fake), allowing the UI to switch between real server data and demo data.

Unified Result Type

kotlin sealed interface Result<out T> { data class Success<T>(val data: T) : Result<T> data class Error(val exception: Throwable, val message: String?) : Result<Nothing> data object Loading : Result<Nothing> }

The asResult() Flow extension wraps any Flow<T> into Flow<Result<T>>, emitting Loading on start and catching exceptions as Error (while re-throwing CancellationException).

Dashboard UI

ThreePaneShell Layout

The top-level layout is an Xcode-inspired three-pane shell with resizable, collapsible panels:

  • Left sidebar (220dp default) – device list, app filter, daemon status, MCP connection
  • Center canvas (flex) – the active dashboard content, with a status bar at the bottom
  • Right inspector (300dp default) – context-sensitive detail panel
  • Bottom timeline (120dp default) – collapsible event timeline

Keyboard shortcuts: Cmd+0 (left), Cmd+Shift+0 (right), Cmd+Shift+Y (bottom), Tab/Shift+Tab (cycle focus), Cmd+/ (cheat sheet), Cmd+K (quick jump). Optional Vim mode (j/k/g/G//).

Dashboard Tabs

The center content area uses a split layout. When the Navigation view is active, it fills the center. Otherwise, Telemetry is the primary center content with secondary dashboards available via bottom tabs:

Dashboard Position Description
Navigation Primary (full center) Flow map with screen nodes and transitions, canvas view, detail panels
Telemetry Primary (center top) Network request inspector, custom event renderer
Test Secondary (bottom tab) Test case browser, run history, recording, plan execution
Storage Secondary (bottom tab) SharedPreferences/database inspector, key-value editor
Diagnostics Secondary (bottom tab) System health, daemon status, MCP process list
Performance Inspector panel Real-time FPS/memory/CPU, anomaly detection, run comparison
Layout Inspector panel Device screen mirror, hierarchy tree, property inspector
Failures Inspector panel Grouped failure list, timeline chart, stack trace viewer

ViewModels

Navigation and Failures each have a dedicated ViewModel that manages UI state via StateFlow and dispatches one-shot effects via Channel. Other dashboards do not yet have standalone ViewModels:

NavigationViewModel - State: Loading | Content(graph, selectedScreenId, currentSection) | Error(message) - Actions: Refresh, SelectScreen, SelectScreenByName, BackToFlowMap, UpdateGraph - Effects: OpenSource(fileName, lineNumber)

FailuresViewModel - State: Loading | Content(failureGroups, selectedFailure, filterType) | Error(message) - Actions: Refresh, SelectFailure, ClearSelection, FilterByType, SelectFailureById, UpdateGroups - Effects: OpenStackTrace, NavigateToScreen, NavigateToTest

Both follow the same pattern: sealed UiState, sealed Action, sealed Effect, with the ViewModel accepting a DataSource and CoroutineScope via constructor injection.

Testing Strategy

FakeAutoMobileClient

A reusable fake that records all method calls in a calls: MutableList<String> and returns configurable values for every AutoMobileClient method. Supports per-URI resource responses via setResourceResponseWithText(). Write calls (setKeyValue, removeKeyValue, clearKeyValueFile) are additionally captured in typed call lists for assertion.

Test Structure

Tests live under android/desktop-core/src/test/kotlin/ and cover:

Area Test Files Techniques
Daemon clients McpHttpClientCancellationTest, McpHttpDiscoveryTest, RetryPolicyTest, SocketConnectionStateTest Fake HTTP responses, port scanner mocks
Data sources CachedAppListDataSourceTest, CachedNavigationDataSourceTest, InMemoryCacheTest, ResultExtensionsTest, RealStorageDataSourceTest Injectable clock, Turbine Flow testing
ViewModels NavigationViewModelTest, FailuresViewModelTest Turbine test {}, fake data sources
UI composables FailuresBadgeUiTest, FailuresCollapsedContentUiTest, ConnectionStatusIndicatorUiTest, StatusBarBadgeUiTest Compose Desktop test rule
Process detection McpProcessDetectorTest, DefaultPortScannerTest Fake ProcessRunner, SocketFileChecker, TimeProvider
Other HierarchyPerformanceTest, NavigationGraphLayoutTest, TelemetryModelsTest, NetworkBodyRendererTest

All fakes and test utilities follow the interface + fake pattern mandated by the project, with injectable time/clock for deterministic behavior.

See Also