# floret-kit — architecture How the kit is structured, built, and consumed **as it stands today**. For what is and isn't extracted, see [`ROADMAP.md`](ROADMAP.md); for the practical how of adding a module, see [`../CONTRIBUTING.md`](../CONTRIBUTING.md). --- ## 1. The thesis in one sentence floret-kit is a Gradle build of small, focused modules — plumbing and a Material 3 Expressive design layer — that the family's apps draw from instead of re-deriving the same code. The whole design hangs off one rule: > Share the **mechanics**, keep the **look** per-app. Theme machinery, content-provider plumbing, and component primitives are shared; seed colours, palettes, identity chips, domain types, and app-specific wording stay in each app. The kit never knows about a specific app's domain. --- ## 2. How an app consumes the kit Each app embeds this repo as a **git submodule** and wires it in as a Gradle **composite build**. The kit is therefore always built **from source** — there are no published binaries — which keeps every consuming app reproducible (a requirement for official F-Droid). ```kotlin // /settings.gradle.kts includeBuild("floret-kit") ``` ```kotlin // /app/build.gradle.kts implementation("de.jeanlucmakiola.floret:core-time") implementation("de.jeanlucmakiola.floret:components") ``` Every module sets `group = "de.jeanlucmakiola.floret"` (via the root build's `subprojects {}`), so Gradle's dependency substitution maps `de.jeanlucmakiola.floret:` to the local `project(":")` of the included build. Editing the kit is picked up by the app's next build with no publish step. Each app **pins a specific kit commit** through its submodule gitlink. So a breaking change in the kit can't reach an app until that app deliberately re-pins — apps stay on independent cadences (important while Calendula ships and Agendula moves fast). ### SDK location for the included build An included Android build needs to locate the Android SDK independently of the host app. Provide it via: - **CI:** an `ANDROID_HOME` env var (the apps set it at the job level), or - **locally:** a gitignored `/floret-kit/local.properties` with `sdk.dir`. Pure-JVM modules (`core-time`) need neither. ### Submodules in CI / F-Droid Consuming apps must check out submodules (`submodules: recursive` in the checkout step) so `floret-kit/` isn't empty. For an app published via **official F-Droid reproducible builds** (Calendula), the fdroiddata recipe additionally needs `submodules: true` on the build entry, and the app's reproducibility guard should scan the submodule's Gradle scripts (see §5). --- ## 3. Module layering | Layer | Modules | Notes | |---|---|---| | Plumbing | `core-time` (JVM) | Pure logic; no Android, no deps. | | Plumbing | `core-crash` (Android) | Self-contained subsystem; parameterized per app. | | Design — identity | `identity` (Android) | Theme factory + motion. App supplies seed/palette. | | Design — components | `components` (Android) | Shared Compose primitives. App composes screens. | Dependencies between modules are kept minimal: `core-time` depends on nothing in the kit; `identity` and `components` depend only on Compose + (for components) `material-icons-core`. There is no app-level or cross-domain coupling. Each module's package is `de.jeanlucmakiola.floret.` (e.g. `de.jeanlucmakiola.floret.components`). --- ## 4. Module types & the build Two kinds of module: - **JVM library** (`core-time`) — `org.jetbrains.kotlin.jvm`, `jvmTarget = 17`, JUnit 5 + Truth tests. The right home for pure logic; cheapest to build and test. - **Android library** (`core-crash`, `identity`, `components`) — `com.android.library` + the Compose plugin, `compileSdk 37` / `minSdk 29`. > **AGP 9.x provides built-in Kotlin compilation.** Android modules therefore > apply only `com.android.library` and `org.jetbrains.kotlin.plugin.compose` — > **not** a separate `org.jetbrains.kotlin.android` plugin (applying it errors). > They set `jvmTarget` directly in a `kotlin { compilerOptions { … } }` block. Versions live in [`gradle/libs.versions.toml`](../gradle/libs.versions.toml), kept in lockstep with the apps' catalogs. --- ## 5. Reproducibility rules Because consuming apps build the kit from source — and at least one publishes via official F-Droid reproducible builds — the kit follows the same invariants: 1. **No `foojay` toolchain resolver**, anywhere (not even as a token in a comment). It can fetch a JDK over the network at build time, which the offline F-Droid source scanner rejects. Modules set `jvmTarget` directly instead of using a Java toolchain block that would need a resolver. 2. **No published artifacts.** Built from source, pinned by commit. 3. Android library modules emit no APK-level metadata of their own (no `buildConfig`); the consuming app controls APK-level reproducibility (`vcsInfo`/`dependenciesInfo`). A consuming app's reproducibility guard should scan the submodule's `*.gradle.kts` for the resolver too, since the included build is part of the from-source graph. --- ## 6. The design-divergence contract The design modules (`identity`, `components`) carry the family's look, so they extract **mechanics** and leave **identity** to the app: - `identity` exposes a theme **factory**; each app passes its own seed-derived `lightScheme`/`darkScheme` and typography. Seed colours and hand-tuned fallbacks are never shared constants. - `components` are app-agnostic primitives. Where two apps differ, the difference is an explicit **parameter** (e.g. `InlineTextField`'s `capitalization`) or a **callback**, never a silently-picked winner. Apps keep their own identity chips/icons and compose their own screens. - **String resources** in the kit are English **fallbacks**; an app overrides them (with its localized values) via resource merge. App-specific wording stays in the app. When a primitive is shared by adopting one app's version as canonical (e.g. `components`' `GroupedSurface` follows Agendula's cleaner abstraction), that is a deliberate, signed-off convergence — the other app's surfaces change when it re-pins, reviewable at that point.