Add a grounded doc set under docs/ plus a root CONTRIBUTING.md: - docs/ARCHITECTURE.md: current code shape (layers, data seam, provider resolution, reminder engine, DI, build/tooling, manifest) - docs/ROADMAP.md: status view (M0/M1 done, M2 in progress, open decisions) - docs/RELEASING.md: tag-driven release flow, CI jobs, F-Droid repo, secrets (was referenced by build.gradle.kts and CHANGELOG but missing) - docs/README.md: docs index and how the docs relate - CONTRIBUTING.md: build/test/lint, layer rules, style, PR conventions Also refresh the stale "M0 — skeleton" status note in README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.2 KiB
Contributing to Floret
Thanks for your interest in Floret — a Material 3 Expressive task app that's a
pure front-end over the OpenTasks TaskContract provider, with no own database
or sync stack. Before diving in, skim docs/ARCHITECTURE.md
(how it's built), docs/ROADMAP.md (what's next), and
docs/PLAN.md (the design rationale). This file covers the
practical how.
The one architectural rule
Everything above the data layer talks to TasksRepository and sees only domain
types and Flows. Provider column names, TaskContract, ContentResolver, and
the authority string never leak above data/tasks/. This is what keeps
"Posture B" (bundling the provider later) an additive change instead of a
rewrite — see docs/ARCHITECTURE.md §7. If a change
would expose provider details to a ViewModel or the UI, it's in the wrong layer.
Prerequisites
- JDK 17
- Android SDK: compileSdk 37, build-tools 36.0.0 (the Gradle wrapper handles AGP/Kotlin)
- A device or emulator with OpenTasks or tasks.org installed for
anything touching the read/write paths (ideally with DAVx5 syncing a CalDAV
task list, so there's real data). Debug builds fall back to
DemoSeederfor sample data.
Build, test, lint
./gradlew :app:assembleDebug # build the debug APK
./gradlew :app:testDebugUnitTest # JVM unit tests (JUnit5 + Truth + Turbine)
./gradlew lintDebug # Android lint (CI runs this on every push)
CI (.gitea/workflows/ci.yaml) runs lint → unit tests → debug build on every
push, so run these locally before opening a PR. Keep CI green.
Where to put code
| Layer | Lives in | Rule of thumb |
|---|---|---|
| Pure logic (models, filtering, sorting, form validation, date maths) | domain/ |
No Android imports — must be JVM-unit-testable. |
| Provider access | data/tasks/ |
The only place that knows about the provider. New provider work goes through TasksDataSource. |
| Reminders, prefs, DI, demo data | data/reminders/, data/prefs/, data/di/, data/demo/ |
|
| Screens | ui/<area>/ |
One ViewModel + immutable UiState per area; Compose for the screen. |
Code style & conventions
- Kotlin, 4-space indent, LF line endings, final newline, no trailing
whitespace — all enforced by
.editorconfig(2-space for yaml/toml/json/md). Match the surrounding code. - Material 3 Expressive for all UI: use
MaterialExpressiveTheme, the colour-scheme tokens (never hardcoded colours), and canonical M3 components (e.g.ListItemfor rows). Consult thematerial-3skill before designing a new screen or component. - Prefer the domain layer for anything testable; keep
AndroidTasksDataSourcethe only Android-coupled data implementation so the rest stays JVM-testable.
Tests
- New domain logic (mappers, filters, sorting, forms, value mapping) must
come with JVM unit tests under
app/src/test/. The data source is the JVM-testable seam — mock or fake it rather than reaching for instrumentation. - Add an instrumented test only when a path genuinely needs a real
ContentResolver.
Commits & PRs
- Write focused commits with clear messages (the existing history uses short,
scoped subjects like
UI: .../M1 ...). - Update
CHANGELOG.mdunder[Unreleased]for any user-visible change — its sections feed the release notes and F-Droid "What's New" (seedocs/RELEASING.md). - If your change shifts the architecture or completes a milestone, update
docs/ARCHITECTURE.md/docs/ROADMAP.mdin the same PR. - Don't bump
versionName/versionCodeby hand — the git tag drives those at release time.
Scope
Floret stays true to its thesis: a front-end over open task backends (CalDAV / iCalendar / DecSync via the OpenTasks provider). Proprietary backends (Google Tasks, Microsoft To Do) are out of scope by design — they'd mean owning a sync stack. v1 targets the OpenTasks contract (OpenTasks + tasks.org); jtx's richer contract is a possible later addition.
License
By contributing you agree your contributions are licensed under the project's MIT License.