docs: changelog, docs/ set, CONTRIBUTING, LICENSE

Bring floret-kit up to the family's repo conventions:
- CHANGELOG.md (Keep a Changelog) — what each module added.
- README.md — full module table (core-time/core-crash/identity/components) +
  consumption notes and a docs index.
- docs/README.md (index), docs/ARCHITECTURE.md (module layering, the submodule +
  composite-build consumption model, JVM vs android-library, the reproducibility
  rules, the share-mechanics-not-look contract), docs/ROADMAP.md (extracted vs
  deferred vs deliberately-not-shared, from the cross-app survey).
- CONTRIBUTING.md — the no-app-coupling rules, how to add a module, how an app
  adopts the kit.
- LICENSE (MIT, matching the apps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 13:42:53 +02:00
parent 0db0e3883f
commit 25ec248e73
7 changed files with 416 additions and 6 deletions

147
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,147 @@
# 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
// <app>/settings.gradle.kts
includeBuild("floret-kit")
```
```kotlin
// <app>/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:<module>` to the local `project(":<module>")` 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 `<app>/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.<area>` (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.

26
docs/README.md Normal file
View File

@@ -0,0 +1,26 @@
# floret-kit — documentation
floret-kit is the shared **Material 3 Expressive design system + plumbing** for
the Floret family of Android apps
([Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula),
[Agendula](https://gitea.jeanlucmakiola.de/makiolaj/agendula), and future
siblings). It is consumed from source as a git submodule + Gradle composite
build. See the top-level [`../README.md`](../README.md) for the pitch.
## Index
| Doc | What it covers |
|---|---|
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | How the kit is built and **consumed** — the module layering, JVM vs Android-library modules, the submodule + composite-build model, and the reproducibility rules. Start here. |
| [`ROADMAP.md`](ROADMAP.md) | **What's extracted, what's deferred, and what's deliberately not shared** — the module status and the divergence decisions from the cross-app survey. |
| [`../CONTRIBUTING.md`](../CONTRIBUTING.md) | The practical how — adding a module, the no-app-coupling rules, build/test, and how an app adopts the kit. |
| [`../CHANGELOG.md`](../CHANGELOG.md) | Keep a Changelog; what each module added. |
## The one principle
> Share the **mechanics**, keep the **look** per-app.
Theme machinery, plumbing, and component primitives are shared; seed colours,
palettes, identity chips, and app-specific wording stay in each app. This is what
lets the family read as one design language without homogenising apps that are
meant to have distinct personalities.

68
docs/ROADMAP.md Normal file
View File

@@ -0,0 +1,68 @@
# floret-kit — roadmap
What's extracted, what's deferred, and what's deliberately **not** shared. The
defer/skip calls come from a cross-app survey of both Calendula and Agendula —
the guiding rule is to share only what is genuinely common, and to keep
divergent or app-specific code where it lives.
---
## Extracted (done)
| Module | Type | Consumers |
|---|---|---|
| `core-time` | JVM | Agendula (and Calendula's pending branch) |
| `core-crash` | Android | Agendula |
| `identity` | Android | Agendula |
| `components` | Android | Agendula |
See [`../CHANGELOG.md`](../CHANGELOG.md) for the contents of each.
---
## Deferred / not yet worth it
- **`core-prefs`** (DataStore base: `ThemeMode`, `dynamicColor`, the typed
wrapper, `toEnum()`) and **`core-di`** (`@IoDispatcher` + provider) — genuinely
shared but **low value**: pure relocation, no new capability, the shared
surface is tiny once the store name and bindings stay app-local. Sensible to
defer until a third app makes the duplication hurt.
- **`ReminderFormatting`** (`ReminderUnit` + `reminderLeadTimeLabel` +
`decomposeReminder`) — the labels are `@Composable` over app-specific
`R.plurals`, and the presets differ per app. Extract later behind a
**label/preset callback API**, not before.
## Deferred — needs migration first
- **`core-provider`** (the ContentProvider seam: `ColumnReader`, failures,
observer→Flow) — the two apps' `ColumnReader`s diverged (Calendula index-based,
Agendula name-based + null-safe). True sharing means migrating Calendula to the
better name-based design first (~a few hundred LOC), so it waits until Calendula
is actively on the kit.
## Not shared (by design)
- **A generic Settings scaffold** — the two apps' settings *domains* are too
divergent for a config-driven `SettingsScaffold` to stay readable. The win is
captured instead at the **component** level (shared `GroupedRow`/section
primitives + `CollapsingScaffold` + `OptionPicker`); each app composes its own
settings from those.
- **`core-reminders`** (unified) — Agendula self-schedules alarms (pull),
Calendula is provider-event-driven (push). Opposite architectures over
incompatible data models; forcing a shared abstraction would be leaky. A tiny
`core-notification` (idempotent channel setup, `POST_NOTIFICATIONS` checks)
*might* land later; schedulers stay app-local.
- Domain-specific UI (date/time fields tied to a domain, calendar/timeline
widgets, recurrence text, domain pickers) and all string wording — these encode
each app's model and resist parameterization.
---
## Consumers & cadence
- **Agendula** consumes the kit on `main` (active development).
- **Calendula** (ships via official F-Droid reproducible builds) has a pending
adoption branch. Bringing it onto the kit is where the canonical-`GroupedSurface`
convergence becomes visible, and where the F-Droid reproducibility checklist
(`submodules: true` in the recipe; the repro guard scanning the submodule)
applies — see [`ARCHITECTURE.md`](ARCHITECTURE.md) §2 and §5.