YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Mouture
Mouture is a modern Android launcher (home screen replacement) built with Kotlin and Jetpack Compose.
It is a minimal home screen with a 100% on-device AI and verifiable privacy: no account, no telemetry by default, and nothing β content or derived insight β ever leaves the phone. The assistant runs a real language model locally (Gemma 3 1B via LiteRT-LM, or Gemma 4 E2B for Pro users who opt in), so it works in airplane mode.
The launcher is free, including everything it computes on its own: smart reminders, smart folders, usage patterns, wellbeing stats, widgets, and the full theming palette. Mouture AI (all LLM features, Pro only) unlocks what the language model writes β chat, narrations, AI names and labels β plus the premium surface styles and home transitions. The underlying entitlement is Mouture Pro via RevenueCat.
Table of contents
Module dependency graph
42 Gradle modules. :app wires together nine features, each split into an api module (the
navigation entry point and public contract) and an impl module (screens, view models, DI), which in
turn build on 19 :core:* modules β :core:designsystem for theming, :core:launcher for the
installed-apps data layer, :core:data/:core:database/:core:datastore for persistence,
:core:llm for the on-device model.
The graph below is :app's dependency closure, so it omits what the shipped app does not compile
against: the four test-support modules (:core:testing, :core:launcher-test,
:core:datastore-test, :core:screenshot-testing, all pulled in via test configurations),
:app-mouture-catalog (the design-system catalog app), :lint (custom lint checks, wired via
lintPublish), :ui-test-hilt-manifest, and the build-logic included build.
The graph below is generated β do not edit it by hand. Regenerate every module's graph with
./gradlew graphUpdate; CI runs the same task and auto-commits any drift. Each module's own README
carries its own scoped graph.
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :feature
direction TB
subgraph :feature:assistant
direction TB
:feature:assistant:api[api]:::android-library
:feature:assistant:impl[impl]:::android-library
end
subgraph :feature:calendar
direction TB
:feature:calendar:api[api]:::android-library
:feature:calendar:impl[impl]:::android-library
end
subgraph :feature:email
direction TB
:feature:email:api[api]:::android-library
:feature:email:impl[impl]:::android-library
end
subgraph :feature:launcher
direction TB
:feature:launcher:api[api]:::android-library
:feature:launcher:impl[impl]:::android-library
end
subgraph :feature:notes
direction TB
:feature:notes:api[api]:::android-library
:feature:notes:impl[impl]:::android-library
end
subgraph :feature:onboarding
direction TB
:feature:onboarding:api[api]:::android-library
:feature:onboarding:impl[impl]:::android-library
end
subgraph :feature:search
direction TB
:feature:search:api[api]:::android-library
:feature:search:impl[impl]:::android-library
end
subgraph :feature:settings
direction TB
:feature:settings:api[api]:::android-library
:feature:settings:impl[impl]:::android-library
end
subgraph :feature:wellbeing
direction TB
:feature:wellbeing:api[api]:::android-library
:feature:wellbeing:impl[impl]:::android-library
end
end
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:appfunctions[appfunctions]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::jvm-library
:core:designsystem-components[designsystem-components]:::android-library
:core:designsystem-theme[designsystem-theme]:::android-library
:core:email[email]:::android-library
:core:launcher[launcher]:::android-library
:core:launcherimport[launcherimport]:::android-library
:core:llm[llm]:::android-library
:core:model[model]:::jvm-library
:core:navigation[navigation]:::android-library
:core:premiumui[premiumui]:::android-library
:core:ui[ui]:::android-library
end
:benchmarks[benchmarks]:::android-test
:app[app]:::android-application
:app -.->|baselineProfile| :benchmarks
:app -.-> :core:analytics
:app -.-> :core:appfunctions
:app -.-> :core:common
:app -.-> :core:data
:app -.-> :core:designsystem-components
:app -.-> :core:launcher
:app -.-> :core:model
:app -.-> :core:ui
:app -.-> :feature:assistant:api
:app -.-> :feature:assistant:impl
:app -.-> :feature:calendar:api
:app -.-> :feature:calendar:impl
:app -.-> :feature:email:api
:app -.-> :feature:email:impl
:app -.-> :feature:launcher:api
:app -.-> :feature:launcher:impl
:app -.-> :feature:notes:api
:app -.-> :feature:notes:impl
:app -.-> :feature:onboarding:api
:app -.-> :feature:onboarding:impl
:app -.-> :feature:search:api
:app -.-> :feature:search:impl
:app -.-> :feature:settings:api
:app -.-> :feature:settings:impl
:app -.-> :feature:wellbeing:api
:app -.-> :feature:wellbeing:impl
:benchmarks -.->|testedApks| :app
:core:appfunctions -.-> :core:data
:core:appfunctions -.-> :core:model
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data -.-> :core:database
:core:data --> :core:datastore
:core:data --> :core:email
:core:data -.-> :core:launcher
:core:data -.-> :core:llm
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:designsystem-components --> :core:designsystem-theme
:core:designsystem-theme --> :core:model
:core:email --> :core:common
:core:email -.-> :core:model
:core:launcher --> :core:common
:core:launcher --> :core:model
:core:launcherimport --> :core:common
:core:launcherimport --> :core:model
:core:llm --> :core:common
:core:llm -.-> :core:datastore
:core:premiumui --> :core:designsystem-components
:core:premiumui --> :core:model
:core:ui --> :core:analytics
:core:ui --> :core:designsystem-components
:core:ui --> :core:launcher
:core:ui --> :core:model
:feature:assistant:api --> :core:navigation
:feature:assistant:impl -.-> :core:data
:feature:assistant:impl -.-> :core:designsystem-components
:feature:assistant:impl -.-> :core:launcher
:feature:assistant:impl -.-> :core:llm
:feature:assistant:impl -.-> :core:model
:feature:assistant:impl -.-> :core:premiumui
:feature:assistant:impl -.-> :core:ui
:feature:assistant:impl -.-> :feature:assistant:api
:feature:calendar:api --> :core:navigation
:feature:calendar:impl -.-> :core:data
:feature:calendar:impl -.-> :core:designsystem-components
:feature:calendar:impl -.-> :core:premiumui
:feature:calendar:impl -.-> :core:ui
:feature:calendar:impl -.-> :feature:calendar:api
:feature:email:api --> :core:navigation
:feature:email:impl -.-> :core:data
:feature:email:impl -.-> :core:designsystem-components
:feature:email:impl -.-> :core:model
:feature:email:impl -.-> :core:ui
:feature:email:impl -.-> :feature:email:api
:feature:launcher:api --> :core:navigation
:feature:launcher:impl -.-> :core:data
:feature:launcher:impl -.-> :core:designsystem-components
:feature:launcher:impl -.-> :core:launcher
:feature:launcher:impl -.-> :core:ui
:feature:launcher:impl -.-> :feature:launcher:api
:feature:notes:api --> :core:navigation
:feature:notes:impl -.-> :core:data
:feature:notes:impl -.-> :core:designsystem-components
:feature:notes:impl -.-> :core:premiumui
:feature:notes:impl -.-> :core:ui
:feature:notes:impl -.-> :feature:notes:api
:feature:onboarding:api --> :core:navigation
:feature:onboarding:impl -.-> :core:common
:feature:onboarding:impl -.-> :core:data
:feature:onboarding:impl -.-> :core:designsystem-components
:feature:onboarding:impl -.-> :core:launcher
:feature:onboarding:impl -.-> :core:model
:feature:onboarding:impl -.-> :core:ui
:feature:onboarding:impl -.-> :feature:onboarding:api
:feature:search:api --> :core:navigation
:feature:search:impl -.-> :core:data
:feature:search:impl -.-> :core:designsystem-components
:feature:search:impl -.-> :core:launcher
:feature:search:impl -.-> :core:ui
:feature:search:impl -.-> :feature:search:api
:feature:settings:api --> :core:navigation
:feature:settings:impl -.-> :core:data
:feature:settings:impl -.-> :core:designsystem-components
:feature:settings:impl -.-> :core:launcher
:feature:settings:impl -.-> :core:launcherimport
:feature:settings:impl -.-> :core:model
:feature:settings:impl -.-> :core:premiumui
:feature:settings:impl -.-> :core:ui
:feature:settings:impl -.-> :feature:settings:api
:feature:wellbeing:api --> :core:navigation
:feature:wellbeing:impl -.-> :core:data
:feature:wellbeing:impl -.-> :core:designsystem-components
:feature:wellbeing:impl -.-> :core:launcher
:feature:wellbeing:impl -.-> :core:model
:feature:wellbeing:impl -.-> :core:ui
:feature:wellbeing:impl -.-> :feature:wellbeing:api
classDef android-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-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
π Graph legend
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-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-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
Install
You need JDK 17+ (CI builds on Zulu 21), the Android SDK platform 37, and Android Studio (latest stable) if you want the IDE.
git clone ssh://git@github.com/doppio-lab/mouture.git
cd mouture
./gradlew :app:installDebug
No secrets are required for a fresh clone. app/google-services.json is committed, the RevenueCat
keys in MoutureApplication.kt are public SDK keys, and release builds fall back to the debug signing
key when keystore.properties is absent β copy keystore.properties.template only if you need to
sign with the real upload key.
To use Mouture as your launcher, press the home button (Android will ask which home app to use), or set it directly:
adb shell cmd package set-home-activity \
com.doppio.android.mouture.debug/com.doppio.android.mouture.MainActivity
Enable the Git hooks once per clone so commits are auto-formatted (Spotless) and never fail the CI
spotlessCheck gate:
git config core.hooksPath .githooks
And hide the repo-wide ktfmt reformat from git blame, so it does not sit on top of every line
(GitHub's blame view applies this file on its own):
git config blame.ignoreRevsFile .git-blame-ignore-revs
One runtime note: Mouture AI downloads a 557 MB Gemma 3 1B model on first use. Pro users can swap it
for Gemma 4 E2B (2.6 GB, confirmed in a dialog first) from the model manager; one model is on disk
at a time. Debug builds ship an
aiTestMode toggle (Settings β developer tools) that swaps in a fake LLM and skips the download.
Test
| Layer | Command |
|---|---|
| Unit (JVM + Robolectric) | ./gradlew testDebug |
| A single test | ./gradlew testDebug --tests "com.doppio.android.mouture.MyTest" |
| Screenshot (Roborazzi) | ./gradlew verifyRoborazziDebug |
| Instrumented (Gradle-managed device) | ./gradlew pixel6api31aospDebugAndroidTest |
| End-to-end (Maestro) | maestro test .maestro |
Screenshot goldens are recorded by CI, not from a workstation β if verifyRoborazziDebug fails on a
PR, CI runs recordRoborazziDebug and commits the new goldens for you. A screenshot failure you
cannot reproduce locally is usually a rendering difference between your machine and the Linux runner:
replay the job through act (see Deploy) and you get CI's exact pixels, plus the diff PNGs
in /tmp/act-artifacts/. See .maestro/README.md for the E2E flows and their
tags.
Quality gates, in the order CI runs them:
./gradlew spotlessCheck # ./gradlew spotlessApply to fix
./gradlew detekt # ./gradlew detektBaseline after a large refactor
./gradlew dependencyGuard # ./gradlew dependencyGuardBaseline to regenerate
./gradlew graphUpdate # module dependency graphs
./gradlew :app:lintRelease :app-mouture-catalog:lintRelease :lint:lint
./gradlew :app:checkReleaseBadging
./gradlew koverXmlReportMerged # 40% overall, 60% on changed files
Deploy
Five GitHub Actions workflows, tuned for the free runner tier (no emulator on the hot path).
| Workflow | Trigger | Outcome |
|---|---|---|
ci.yml |
PR, push to main/dev |
Quality gates + JVM tests. On push: signed release APK β Firebase App Distribution (testers group) |
release.yml |
tag v*, manual |
Signed AAB β Google Play internal track, draft + a draft GitHub Release with the AAB and R8 mapping.txt |
nightly-baseline.yml |
cron 42 4 * * * |
Regenerates the baseline profile on an emulator, auto-commits it to dev |
landing-deploy.yml |
changes under landing/** |
Firebase Hosting β preview channel on PRs, live on main/dev |
emulator-debug.yml |
branch ci/emulator-debug |
Throwaway diagnostic, not part of the pipeline |
Cutting a release: push a tag and the pipeline does the rest.
git tag v1.2.3 && git push origin v1.2.3
Both the Play upload and the GitHub Release land as drafts β publishing stays a manual decision in
the Play Console. A manual workflow_dispatch run has dry_run defaulting to true: it resolves a
version and stops before building.
Versioning is read from Play, not from the repo. The
resolve-version composite action queries the Play Developer API
and computes versionCode = max(bundles, apks, tracks, floor) + 1. Because Play is the authority that
enforces uniqueness, re-running a release is safe and a run that fails before uploading consumes no
code. release.yml calls it in strict mode (any API failure fails the run); ci.yml calls it
non-strict, so Play being unreachable does not turn every push red.
Expect π€ commits on your PR. CI auto-commits regenerated dependency graphs, screenshot goldens, dependency-guard baselines, and nightly baseline profiles.
You can run the workflows locally before pushing. nektos/act replays them on your machine, in a container that carries the Android SDK, against the real secrets:
./scripts/act.sh pull_request -W .github/workflows/ci.yml -e .github/act/events/pull_request.json
Three things to remember. The event payload under .github/act/events/ is what decides which
if: branches fire β pull_request.json and push-main.json are mirror images of each other.
Adding -n lists the steps a payload selects in two seconds without running anything, which is
worth doing before paying for a full Gradle run. And artifacts, including the Roborazzi diff
PNGs, land under /tmp/act-artifacts/. First-time setup β Rosetta, the runner image, .secrets β
plus the recipes for the other three workflows and the handful of act quirks worth knowing are in
.github/CICD.md.
Required secrets, one-time Play/Firebase setup, and how signing works in CI are documented in the same file.
Documentation
| Document | What it covers |
|---|---|
AGENTS.md |
Architecture, module layout, testing conventions |
.github/CICD.md |
CI/CD in depth: secrets, signing, versioning, R8, and running the workflows locally with act |
docs/mouture-ai-rules.md |
Invariants RG1βRG6 that every AI feature must honor |
docs/appfunctions.md |
Driving Mouture's agent surface from a phone: discover, execute, read errors |
docs/audit-mouture-ai-smart-reminders.md |
Β§2 is the authoritative Free/Pro inventory π«π· |
docs/book/ |
Engineer onboarding and beta-tester guide π«π· |
.maestro/README.md |
End-to-end flows and tags |
*/README.md |
Every module documents its own dependency graph |
π«π· marks documents written in French.
License
Mouture is distributed under the terms of the Apache License (Version 2.0). See LICENSE for more information.