Files

6.1 KiB

:feature:docs

Overview

The :feature:docs module is an in-app documentation browser with Compose Multiplatform UI. It bundles the Meshtastic user guide and developer guide as Compose resources at build time, provides full-text keyword search, Crowdin-backed multilingual content, optional ML Kit runtime translation (Google Play flavor), and a "Chirpy" AI Q&A assistant (Gemini Nano on Google Play; keyword fallback on F-Droid / Desktop / iOS).

Targets: Android · JVM (Desktop) · iOS (via meshtastic.kmp.feature convention plugin)

Key Responsibilities

  • Bundle docs/en/user/**/*.md and docs/en/developer/**/*.md as Compose resources at build time
  • Sync Crowdin-translated locales into composeResources/files/{locale}/docs/
  • Keyword search (TF-IDF style) across the full doc bundle
  • Locale-aware content loading with Crowdin → ML Kit fallback chain
  • Adaptive list/detail layout (single pane on phones, split pane on tablets/desktop)
  • Chirpy AI assistant: streaming Q&A against in-app docs via Gemini Nano or keyword fallback

Source Structure

src/commonMain/kotlin/org/meshtastic/feature/docs/
├── ai/
│   ├── AIDocAssistant.kt            ← interface (Gemini Nano / keyword fallback)
│   ├── ChirpySessionHolder.kt
│   └── KeywordFallbackAssistant.kt
├── data/
│   ├── DocBundleLoader.kt           ← interface + DefaultDocBundleLoader
│   └── KeywordSearchEngine.kt       ← TF-IDF keyword search
├── model/
│   └── DocModels.kt                 ← DocSection, DocPage, DocBundle, DocSearchResult, ...
├── navigation/
│   └── DocsNavigation.kt            ← docsEntries(), ChirpyUiState, rememberChirpyState()
├── translation/
│   ├── DocTranslationService.kt     ← ML Kit (Google) or no-op (fdroid/desktop/iOS)
│   ├── DocTranslationCache.kt
│   ├── MarkdownTranslationSegmenter.kt
│   └── NoOpDocTranslator.kt
└── ui/
    ├── DocsBrowserScreen.kt         ← list pane
    ├── DocsPageRouteScreen.kt       ← detail pane
    ├── DocsSearchBar.kt
    ├── ChirpyAssistantSheet.kt      ← AI assistant bottom sheet
    ├── ComposeResourceImageTransformer.kt
    ├── DocPageIconResolver.kt
    └── DocsPreviews.kt

Key Types

DocSection (sealed interface)

sealed interface DocSection {
    data object UserGuide      : DocSection
    data object DeveloperGuide : DocSection
}

DocPage

data class DocPage(
    val id: String,
    val title: String,
    val section: DocSection,
    val navOrder: Int,
    val resourcePath: String,
    val keywords: List<String>,
    val charCount: Int,
)

AIDocAssistant (interface)

interface AIDocAssistant {
    suspend fun isSupported(): Boolean
    suspend fun answer(question: String, currentPageId: String? = null): AIDocAssistantResult
    fun answerStream(question: String, currentPageId: String? = null): Flow<AIDocAssistantResult>
    fun resetSession()
}

AIDocAssistantResult is a sealed interface: Partial, Success, Fallback, Error.

Platform bindings:

  • Google flavor: Gemini Nano via on-device ML
  • F-Droid / Desktop / iOS: KeywordFallbackAssistant (keyword search + summarisation, no network)

ChirpyMessage

@Serializable
data class ChirpyMessage(
    val id: String,
    val role: ChirpyRole,   // USER | ASSISTANT | SYSTEM
    val text: String,
    val sources: List<SourceRef>,
)

Gradle Tasks

Two custom tasks keep bundled docs in sync:

Task Description
syncDocsToComposeResources Copies docs/en/user/**/*.md, docs/en/developer/**/*.md, and docs/screenshots/**/*.png into src/commonMain/composeResources/files/docs/. Runs automatically before resource generation.
syncTranslatedDocsToComposeResources Copies Crowdin-translated locales from docs/{locale}/user/**/*.md into src/commonMain/composeResources/files/{locale}/docs/ using CMP qualifier format (e.g. pt-rBR).

These tasks run automatically — no manual invocation is required during normal development.

Navigation

Routes (registered under the Settings nav graph):

Route Description
SettingsRoute.HelpDocs Doc browser list pane
SettingsRoute.HelpDocPage Individual doc page detail pane

The docsEntries() extension uses Material3 adaptive ListDetailSceneStrategy to automatically provide a split-pane layout on large screens.

Dependency Graph

feature:docs
  ├── core:common, core:navigation, core:resources, core:ui, core:di
  ├── coil                      (image loading in Markdown)
  ├── markdown-renderer-m3      (Compose Markdown rendering)
  ├── compose.material3.adaptive, compose.material3.adaptive.navigation3
  └── kotlinx.collections.immutable

Dependency Graph

graph TB
  :feature:docs[docs]:::kmp-feature
  :feature:docs -.-> :core:common
  :feature:docs -.-> :core:navigation
  :feature:docs -.-> :core:resources
  :feature:docs -.-> :core:ui
  :feature:docs -.-> :core:di
  :feature:docs -.-> :core:testing

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef compose-desktop-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-library-compose fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library-compose fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;