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:
147
docs/ARCHITECTURE.md
Normal file
147
docs/ARCHITECTURE.md
Normal 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
26
docs/README.md
Normal 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
68
docs/ROADMAP.md
Normal 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.
|
||||
Reference in New Issue
Block a user