Skill: Compose Multiplatform (CMP) UI

SkillDev tools

Lets your agent work with the Meshtastic Android app, an open-source client for off-grid mesh radio messaging.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Skill: Compose Multiplatform (CMP) UI skill

About this capability

Android application for Meshtastic

What this skill tells your AI

The instructions your AI receives, as published by meshtastic/meshtastic-android in .skills/compose-ui/SKILL.md and read by ahel’s review.

Description

Guidelines for building shared UI, adaptive layouts, and handling strings/resources in Meshtastic-Android. The codebase uses Material 3 Adaptive.

1. UI Components & Layouts

  • Material 3 / Adaptive: Use currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true) to support Large (1200dp) and XL (1600dp) breakpoints. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktopApp/tablets.
  • Dialogs & Alerts: Use centralized components like AlertHost(alertManager) from core:ui/commonMain. Do NOT trigger alerts inline or duplicate alert logic. Use SharedDialogs(uiViewModel) for general popups.
  • Placeholders: Use PlaceholderScreen(name) from core:ui/commonMain for unimplemented desktopApp/JVM features.
  • Theme Picker: Use ThemePickerDialog from feature:settings/commonMain.
  • Platform Implementations: Inject platform-specific behavior (e.g., Map providers) via CompositionLocal from the androidApp or desktopApp shells. Do not tightly couple Google Maps dependencies to commonMain; the MapLibre surfaces live in :feature:map-maplibre, not in a core module.

2. Strings & Resources

  • Multiplatform Resources: MUST use core:resources (e.g., stringResource(Res.string.your_key)). Never use hardcoded strings.
  • ViewModels/Coroutines: Use the asynchronous getStringSuspend(Res.string.your_key). NEVER use blocking getString() in a coroutine context.
  • Formatting Constraints: CMP stringResource only supports %N$s (string) and %N$d (integer).
    • No Float formatting: Formats like %N$.1f pass through unsubstituted. Pre-format in Kotlin using NumberFormatter.format(value, decimalPlaces) from core:common and pass as a string argument (%N$s):
      val formatted = NumberFormatter.format(batteryLevel, 1) // "73.5"
      stringResource(Res.string.battery_percent, formatted)   // uses %1$s
      
    • Percent Literals: Use bare % (not %%) for literal percent signs in CMP-consumed strings.

String Formatting Decision Tree

Choose the right tool for the job:

ScenarioToolExample
Metric display (temp, voltage, %, signal)MetricFormatter.*MetricFormatter.temperature(25.0f, isFahrenheit)"77.0°F"
Simple number + unitNumberFormatter + interpolation"${NumberFormatter.format(val, 1)} dB"
Localized template from strings.xmlstringResource(Res.string.key, preFormattedArgs)stringResource(Res.string.battery, formatted)
Non-composable template (notifications, plain functions)formatString(template, args)formatString(template, label, value)
Hex formattingformatStringformatString("!%08x", nodeNum)
Date/timeDateFormatterDateFormatter.format(instant)

Rules:

  1. NEVER use %.Nf in strings.xml — CMP cannot substitute them. Use %N$s and pre-format floats.
  2. Prefer MetricFormatter over scattered formatString("%.1f°C", temp) calls.
  3. formatString (pure Kotlin) is a pure-Kotlin commonMain implementation for: hex formats, multi-arg templates fetched at runtime, and chart axis formatters. Located in core:common Formatter.kt.
  4. NumberFormatter always uses . as decimal separator — intentional for mesh networking precision.
  • Workflow to Add a String:
    1. Add to core/resources/src/commonMain/composeResources/values/strings.xml.
    2. Run python3 scripts/sort-strings.py — keeps the file sorted and regenerates strings-index.txt.
    3. Use the generated org.meshtastic.core.resources.<key> symbol.
    4. Validate UI presentation.

3. Tooling & Capabilities

  • Image Loading: Use libs.coil (Coil Compose) in feature modules. Configuration/Networking for Coil (coil-network-ktor3) happens strictly in the androidApp and desktopApp host modules.
  • QR Codes: Use rememberQrCodePainter from core:ui/commonMain powered by qrcode-kotlin. No ZXing or Android Bitmap APIs in shared code.

4. Compose Previews

  • Preview in commonMain: CMP 1.11+ supports @Preview in commonMain via compose-multiplatform-ui-tooling-preview. Place preview functions alongside their composables.
  • Import: Use androidx.compose.ui.tooling.preview.Preview. The JetBrains-prefixed import (org.jetbrains.compose.ui.tooling.preview.Preview) is deprecated.

5. Dialog & State Patterns

  • Dialog State Preservation: Use rememberSaveable for dialog state (search queries, selected tabs, expanded flags) to preserve across configuration changes. Boolean and String types are auto-saveable — no custom Saver needed.

6. Driving the running desktop app

CMP 1.12+ ships an MCP server inside Compose Hot Reload; .mcp.json registers it as compose-hot-reload (:desktopApp:hotMcpServer). With ./gradlew :desktopApp:hotRun running, it drives the live app — inspect, input and reload without a rebuild.

  • Tools: status, reload, await_reload, get_semantic_tree, click, type_text, scroll, get_logs, get_ui_error, take_screenshot.
  • Poll status until connected: true before anything else — the server accepts requests before the app has connected to it.
  • get_semantic_tree is the assertion surface: roles, text, selected/focused, available actions and bounds. click addresses nodes by nodeId taken from that tree. Prefer it over take_screenshot, whose output depends on the host renderer.
  • reload after editing sources applies the change into the running app; use await_reload instead when the app was started with --auto.

Reference Anchors

  • Shared Strings: core/resources/src/commonMain/composeResources/values/strings.xml
  • Platform abstraction contract: core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt
  • Provider wiring: androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt

Signals

GitHub stars
2k
Forks
512
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
compose-ui-meshtastic
Source
github.com/meshtastic/meshtastic-android