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 modelFailureGroup,FailureOccurrence,FailureType,FailureSeverity– failure aggregationPerformanceMetric,PerformanceAnomaly,PerformanceRun,PerformanceThresholds– metrics and thresholdsUIElementInfo,ParsedHierarchy,ObservationData,ConnectionStatus– layout inspectionDatabaseInfo,KeyValueFile,KeyValueEntry– on-device storage inspectionTestCase,TestRun,TestStep,RecordedAction– test management
desktop-app¶
The entry point module is intentionally small:
Main.kt– acquires a file-lock (automobile-desktop.lock) to enforce single-instance, creates the MetroAutoMobileGraph, sets macOS native transparent title bar properties, and launches a 1440x900 Compose window.AutoMobileDesktopApp.kt– wrapsAutoMobileContent(from desktop-core) with anAutoMobileThemeand anObservableSettingsProviderthat bridgesSettingsProviderchanges into Compose snapshot state.AutoMobileTheme.kt– Material 3 color scheme with light and dark variants. ResolvesthemeModestrings ("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:
- If an
McpHttpServeris provided (from discovery), useMcpHttpClient. - If
AUTOMOBILE_MCP_HTTP_URLenv var or system property is set, useMcpHttpClient. - If
AUTOMOBILE_MCP_STDIO_COMMANDenv var or system property is set, useMcpStdioClient. - 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:
DefaultPortScanner– runslsof/netstat/ssto find listening portsHttpHealthProbe– sends GET to/healthand/auto-mobile/healthwith 800ms timeout- Matches discovered servers against
GitWorktreeListeroutput (maps branches to worktrees) - Returns
McpDiscoverySnapshotwithMcpServerOptionentries 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¶
- Android Overview
- Observe – observation pipeline that feeds the Layout dashboard
- IDE Plugin – IntelliJ plugin that shares
desktop-core