From 82c3e1d605e299e1309964a6b3fa4cb96c65c06e Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 11 Jun 2026 22:35:03 +0200 Subject: [PATCH] docs: architecture tour, docs index, showcase README; ci: Gitea release per tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation pass after the 2.0 milestone: - docs/ARCHITECTURE.md — principles (provider as single source of truth, observer-driven UI, JVM-first tests, no network), layer + reminder mermaid diagrams, navigation (overlay/held-key, no nav lib), and the provider lessons (recurring-write invariants, conflict snapshots) - docs/README.md — map of what documentation lives where, incl. the convention that superpowers/ plans are historical artifacts while .planning/ stays current - README.md — showcase layout (centered header, badges, screenshot gallery from the fastlane assets, grouped features, install/build/ architecture/roadmap sections); renders on Gitea - .planning/{PROJECT,REQUIREMENTS,STATE}.md unstaled: read-only-V1 talk removed, V1/V2 checklists marked shipped, state points at v3 + the Locations & People go/no-go release.yaml gains a gitea-release job: on every tag push it extracts the tag's CHANGELOG section and creates a Gitea release with it as the notes. No APK assets — distribution stays with the F-Droid repo. Idempotent (skips an existing release), gated on the test job only so notes appear even when the F-Droid upload hiccups. Co-Authored-By: Claude Fable 5 --- .gitea/workflows/release.yaml | 67 +++++++++++++++- .planning/PROJECT.md | 22 ++--- .planning/REQUIREMENTS.md | 52 ++++++------ .planning/STATE.md | 33 +++++--- README.md | 122 ++++++++++++++++++++-------- docs/ARCHITECTURE.md | 147 ++++++++++++++++++++++++++++++++++ docs/README.md | 20 +++++ 7 files changed, 383 insertions(+), 80 deletions(-) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/README.md diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 9852026..de573c4 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -1,4 +1,4 @@ -name: Build and Release to F-Droid +name: Release — F-Droid repo + Gitea release on: push: @@ -222,3 +222,68 @@ jobs: -mkdir dev/fdroid/repo SFTP sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/. "$USER@$HOST:dev/fdroid/" + + # A Gitea release per tag, carrying the tag's CHANGELOG section as its + # notes. Deliberately no APK assets — distribution stays with the F-Droid + # repo; the release is the human-readable record. Gated on the tests-only + # ci job (not the deploy) so notes appear even if the F-Droid upload has + # an infrastructure hiccup. + gitea-release: + needs: ci + if: startsWith(github.ref, 'refs/tags/') + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract changelog section for this tag + run: | + set -e + TAG="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}" + VERSION="${TAG#v}" + # Everything between "## []" and the next "## [" heading. + awk -v ver="$VERSION" ' + $0 ~ "^## \\[" ver "\\]" { flag = 1; next } + /^## \[/ { flag = 0 } + flag' CHANGELOG.md > release-notes.md + # Trim leading blank lines. + sed -i -e '/./,$!d' release-notes.md + if [ ! -s release-notes.md ]; then + echo "_No changelog entry for ${VERSION} — see CHANGELOG.md._" > release-notes.md + fi + echo "--- release notes ---" + cat release-notes.md + + - name: Create Gitea release + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + run: | + set -e + TAG="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}" + # Re-runs must not fail on an already-published release. + STATUS=$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG") + if [ "$STATUS" = "200" ]; then + echo "Release for $TAG already exists — skipping." + exit 0 + fi + python3 - "$TAG" <<'PY' > payload.json + import json, sys + print(json.dumps({ + "tag_name": sys.argv[1], + "name": sys.argv[1], + "body": open("release-notes.md").read(), + "draft": False, + "prerelease": False, + })) + PY + CODE=$(curl -s -o response.json -w '%{http_code}' -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d @payload.json "$API/releases") + cat response.json + if [ "$CODE" != "201" ]; then + echo "Release creation failed with HTTP $CODE" + exit 1 + fi diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index 4b500c0..04ce9d3 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -2,11 +2,12 @@ ## What This Is -A modern Material 3 Expressive Android calendar app, read-only V1. Lives -entirely on top of Android's `CalendarContract` — any calendar synced to the -device (CalDAV via DAVx5, Google, local, WebCal, …) shows up automatically. -The differentiator is visual: real Material 3 Expressive design that no -existing FOSS calendar app delivers. +A modern Material 3 Expressive Android calendar app. Lives entirely on top +of Android's `CalendarContract` — any calendar synced to the device (CalDAV +via DAVx5, Google, local, WebCal, …) shows up automatically; creating, +editing, and deleting writes straight back, and reminders are delivered by +the app itself (Etar model). The differentiator is visual: real Material 3 +Expressive design that no existing FOSS calendar app delivers. ## Core Value @@ -15,8 +16,10 @@ re-inventing the calendar sync stack — leave that to DAVx5 and the system. ## Current Milestone -**v0.1 — Foundation & CI:** Buildable Android project scaffold with theme, -icon, i18n, Hilt, DataStore, green CI. +Milestones 1 (read, v1.0) and 2 (write support, v1.1–v2.0.0 incl. reminder +delivery) are **complete** — v2.0.0 shipped 2026-06-11. Next is v3.0 +(power-user features) plus an undecided "Locations & People" idea backlog; +see `ROADMAP.md`. ## Stack @@ -26,9 +29,8 @@ Expressive 1.5.0-alpha21 (alpha is intentional — Expressive APIs only live in the 1.5 alpha line). Hilt 2.59.2, DataStore. Gradle Kotlin DSL with Version Catalog. AGP 9.1.1, Gradle 9.5.1. JVM target 17. -Read-only V1, write support V2. - -Android-only (minSdk 29, targetSdk 36). No iOS. +Android-only (minSdk 29, targetSdk 36). No iOS. No `INTERNET` permission — +any feature that would need one is an explicit product decision first. ## Naming diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index dd008ed..a6b970b 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -2,39 +2,43 @@ See full design spec: `docs/superpowers/specs/2026-06-08-calendar-app-design.md` -## V1 Scope (Variant "B") +## V1 Scope (Variant "B") — shipped in full (v1.0.0, 2026-06-11) -### Validated (shipped) -- Foundation & CI infrastructure — v0.1.0 (2026-06-08) - -### Active (V1) - -- [x] Foundation & CI infrastructure +- [x] Foundation & CI infrastructure — v0.1.0 (2026-06-08) - [x] Data Layer over `CalendarContract` - [x] Permission flow (`READ_CALENDAR`) -- [ ] Month view (S1) -- [ ] Week view (S2) -- [ ] Day view (S3) -- [ ] Event Detail Sheet (S4) -- [ ] Multi-Calendar Filter (M3) +- [x] Month view (S1) +- [x] Week view (S2) +- [x] Day view (S3) +- [x] Event Detail Sheet (S4) — became a full screen, plus full event read (v0.6) +- [x] Multi-Calendar Filter (M3) - [x] Today button (M2) — shipped v0.5; Jump-to-Date **cut from scope** -- [ ] View-Switcher (M1) -- [ ] Settings screen (M4) -- [ ] Empty / no-permission / no-calendars states -- [ ] German + English localization -- [ ] Loading/Failure/Success states per screen (architectural pattern) +- [x] View-Switcher (M1) +- [x] Settings screen (M4) +- [x] Empty / no-permission / no-calendars states +- [x] German + English localization +- [x] Loading/Failure/Success states per screen (architectural pattern) -### Out of Scope (V2+) +## V2 Scope — write support, shipped in full (v2.0.0, 2026-06-11) + +- [x] Write foundation: `WRITE_CALENDAR`, read-only-calendar detection, delete (v1.1) +- [x] Create event: form, FAB, last-used calendar (v1.2; polish v1.2.1) +- [x] Edit event: shared form, scoped recurring writes, recurrence picker (v1.3) +- [x] Reminder notifications (v1.4) — **reversal of the original + "system handles reminders" assumption:** Calendula targets + sole-calendar-app users, so it posts reminder notifications itself + (Etar model), incl. `POST_NOTIFICATIONS` onboarding +- [x] Conflict dialog on save + store polish (v2.0) +- Quick-add — **cut from scope** (the prefilled form covers it) +- Calendar switching while editing — moved to v3 backlog + +### Out of Scope (V3+) -- Event create / edit / delete (V2) - Home-screen widget - Full-text search -- Quick-add -- ~~Custom notifications/reminders (system already handles these)~~ — - **reversed:** Calendula targets sole-calendar-app users, so no other app - posts reminder notifications. We post them ourselves (Etar model). Planned - for v1.4 — see `ROADMAP.md`. - Tablet/foldable-specific layouts +- Locations & People ideas (contact picker, OSM autocomplete) — see + `ROADMAP.md` idea backlog, undecided - iOS support (Android-only by design) ## Constraints diff --git a/.planning/STATE.md b/.planning/STATE.md index b88a347..4772408 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,13 +4,10 @@ ## Status -**Milestone:** v2.0 — Write support (milestone 2, in progress) -**Phase:** v1.3.0 (edit event) shipped 2026-06-11 after four on-device -review rounds (BYDAY toggles, scoped recurring writes, scope-at-save flip, -stale-instances split bugfix). Milestone 2 runs in four slices -(`docs/superpowers/plans/2026-06-11-03-write-support.md`); v2.0 (quick-add, -conflict dialog, polish) is the remaining slice, v1.4 (reminder -notifications) comes first. +**Milestone:** 2 (write support) **complete** — v2.0.0 shipped 2026-06-11. +**Phase:** between milestones. Next: v3.0 (power-user features) and the +go/no-go on the "Locations & People" idea backlog (`ROADMAP.md`). Docs +pass done (ARCHITECTURE.md, README overhaul, planning docs refreshed). ## Progress @@ -62,10 +59,22 @@ notifications) comes first. recurring saves park in `SaveUiState.AwaitingScope`, a changed rule drops the "only this event" option +- [x] v1.4 reminder notifications (shipped 2026-06-11) — exported + `EVENT_REMINDER` receiver → `CalendarAlerts` (SCHEDULED & due) → + dedicated channel, tap opens detail (singleTop deep link); best-effort + FIRED marking; one-time onboarding step requesting `POST_NOTIFICATIONS` + with duplicate-reminders warning; Settings mirror. Provider only fires + `METHOD_ALERT` rows (AOSP-verified), so email reminders never reach us + +- [x] v2.0 conflict dialog + store polish (shipped 2026-06-11 as v2.0.0) — + `EditSnapshot` compare on save (overwrite/discard; deleted → close), + quick-add cut, calendar-switch → v3 backlog; F-Droid/README copy + refreshed, fastlane screenshots DE+EN captured on-device + ## Next -1. v1.4 — reminder notifications (essential for sole-app use): `EVENT_REMINDER` - receiver + notification channel, `POST_NOTIFICATIONS`, onboarding step with - default-on toggle + duplicate-reminder warning (Etar model) -2. v2.0 — quick-add sheet, conflict dialog, polish pass, milestone release -3. Monitor the F-Droid build/publish for v1.1.0 – v1.3.0 +1. Decide the "Locations & People" go/no-go (INTERNET permission question) + — see `ROADMAP.md` idea backlog +2. v3.0 scoping: widget, full-text search, tablet layouts, ICS import, + calendar-move +3. Monitor the F-Droid build/publish for the v1.4.0 / v2.0.0 tags diff --git a/README.md b/README.md index 98ad074..8935189 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,103 @@ -# Calendula +
-A modern Material 3 Expressive calendar app for Android. +Calendula icon -Calendula is named after the flower of the same name, whose name comes from -the Latin *kalendae* — the first day of the month — the same root as the -word "calendar". Calendula reads from Android's built-in `CalendarContract`, -so any calendar source synced to your device (CalDAV via DAVx5, Google, -local, WebCal subscriptions, ...) is shown. +

Calendula

-## Features +

A modern Material 3 Expressive calendar for Android.
+Reads, writes, and reminds — on top of the system calendar, with zero network access.

-- Month, Week, and Day views -- Full event details — attendees, reminders, recurrence, availability, and more -- Create, edit, and delete events — recurring events with scoped writes - (only this event / this and all following / whole series) and a simple - recurrence picker -- Reminder notifications, delivered by Calendula itself (tap opens the event) -- Multi-calendar visibility toggle -- Material You Dynamic Color (Android 12+) -- Light/Dark theme follows system -- German + English UI +

+CI +Android 10+ +Kotlin + Compose +Material 3 Expressive +MIT License +

-## Building +

+Week view  +Month view  +Event detail  +Event form  +Reminder onboarding +

-Requires Android SDK 36 and JDK 17. The Gradle wrapper is checked in, so no host Gradle install is needed: +
+ +Calendula is named after the flower whose name — like the word *calendar* — +comes from the Latin *kalendae*, the first day of the month. It lives +entirely on top of Android's `CalendarContract`: any calendar synced to your +device (CalDAV via DAVx5, Google, local, WebCal subscriptions, …) simply +appears, and everything you create or edit syncs back the same way. No own +database, no sync stack reinvented. + +## ✨ Features + +**Calendar** + +- Month, week, and day views with a one-tap view switcher +- Full event details — attendees and their responses, reminders, recurrence + (humanized), availability, visibility, foreign time zones +- Per-calendar visibility toggle, grouped by account + +**Editing** + +- Create, edit, and delete events — including recurring events with scoped + writes: *only this event*, *this and all following*, or *the whole series* +- Recurrence picker with one-tap presets and custom rules (interval, weekday + toggles, end conditions); rules it can't express are preserved verbatim +- Conflict-safe saves: if an event changed elsewhere while you were editing, + Calendula asks instead of silently overwriting +- Read-only calendars (WebCal, birthdays) are detected and respected + +**Reminders** + +- Event reminders delivered by Calendula itself as notifications — + essential when it's your only calendar app, since Android delegates + reminder delivery to calendar apps +- Tap a reminder to land on the event + +**Design & privacy** + +- Real Material 3 Expressive throughout — dynamic color (Android 12+), + expressive motion and shapes, light/dark theme +- German and English UI, per-app language setting +- **Zero telemetry, zero analytics, no internet permission** — your data + never leaves the device + +## 📦 Install + +Calendula ships through a self-hosted F-Droid repository (releases are +built and published automatically from version tags). Alternatively, build +from source — see below. + +## 🛠 Building + +Requires Android SDK 36+ and JDK 17. The Gradle wrapper is checked in: ```bash -# Build debug APK -./gradlew assembleDebug - -# Run unit tests -./gradlew test - -# Run lint -./gradlew lint +./gradlew assembleDebug # debug APK +./gradlew test # JVM unit tests +./gradlew lint # Android lint ``` -If your default JDK is something other than 17, set `JAVA_HOME` explicitly: +If your default JDK is not 17, set `JAVA_HOME` explicitly. -```bash -JAVA_HOME=/path/to/jdk-17 ./gradlew assembleDebug -``` +## 🏗 Architecture -## License +Single-activity Compose app, layered `UI → Repository → DataSource → +CalendarContract`, observer-driven refresh, JVM-first tests. The full tour — +including the recurring-write and reminder pipelines — lives in +[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). + +## 🗺 Roadmap + +Shipped: read (v1.0), write (v1.1–v2.0), reminder delivery (v1.4). +Next up: power-user features — widget, search, tablet layouts. The living +roadmap is in [.planning/ROADMAP.md](.planning/ROADMAP.md), the release +history in [CHANGELOG.md](CHANGELOG.md). + +## 📜 License [MIT](LICENSE) — Jean-Luc Makiola, 2026 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6bd2b04 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,147 @@ +# Architecture + +Calendula is a single-activity Jetpack Compose app layered strictly on top +of Android's calendar provider. This document is the orientation tour: the +principles, the layers, and the three pipelines that are not obvious from +the package list (recurring writes, save conflicts, reminder delivery). + +## Principles + +1. **`CalendarContract` is the single source of truth.** No app database, + no caching layer, no sync code. Reads query the provider; writes go + straight back to it. Sync is DAVx5's / Google's / the system's job. +2. **Observer-driven UI.** A `ContentObserver` on the provider triggers + re-queries; every screen recomposes from fresh provider state. After a + write, nothing is patched by hand — the provider notifies, the views + refresh. This also covers external changes (sync) for free. +3. **JVM-first testing.** Everything between the UI and the + `ContentResolver` is shaped so it runs as a plain JUnit 5 test: pure + domain logic, cursor-free mappers, a `FakeCalendarDataSource` for + repository tests. Instrumented tests are a last resort. +4. **No network.** The app declares no `INTERNET` permission. Anything that + would need one is an explicit, documented product decision first + (see the roadmap's idea backlog). + +## Layers + +```mermaid +flowchart TD + subgraph UI ["ui/ — Compose screens + ViewModels"] + Screens["Month / Week / Day\nDetail / Edit / Settings\nPermission + Reminder onboarding"] + end + subgraph Data ["data/"] + Repo["CalendarRepository\n(interface + impl, Flow-based, io-dispatched)"] + DS["CalendarDataSource\n(interface + AndroidCalendarDataSource)"] + Prefs["SettingsPrefs / CalendarPrefs\n(DataStore)"] + Rem["reminders/\nReminderAlertStore + ReminderNotifier"] + end + Provider[("CalendarContract\n(system calendar provider)")] + + Screens --> Repo + Screens --> Prefs + Repo --> DS + DS --> Provider + Provider -. "ContentObserver tick" .-> Repo + Provider -. "EVENT_REMINDER broadcast" .-> Rem + Rem --> Provider +``` + +- **`domain/`** — pure Kotlin, no Android imports: models + (`EventInstance`, `EventDetail`, `CalendarSource`, …), the `EventForm` + with validation, `SimpleRecurrence` (RRULE parse/render for the picker), + and `EditSnapshot` (conflict detection). All JVM-tested. +- **`data/calendar/`** — the provider seam. `AndroidCalendarDataSource` + owns every `ContentResolver` call; cursor parsing lives in mappers + (`InstanceMapper`, `EventDetailMapper`, `CalendarMapper`) that read + through a `ColumnReader` abstraction so tests feed them plain maps. + `EventWriteMapper` builds dirty-checked update value sets. `TimeBridge` + converts provider epoch millis ↔ `kotlin.time.Instant`. +- **`data/reminders/`** — the notification pipeline (see below). Kept out + of `data/calendar/` because the receiver needs neither the repository + nor its flows. +- **`data/prefs/`** — DataStore-backed settings (theme, week start, form + field defaults, reminders toggle) and small state (last-used calendar). +- **`ui/`** — one package per screen, each with Screen + ViewModel + + UiState. Shared pieces in `ui/common/` (OptionCard — the app's only + sanctioned selection-dialog style —, recurrence humanizer, FAB column, + drawer, transitions). + +## Navigation + +There is no navigation library. `MainActivity` hosts `RootScreen`, which +gates on the calendar permission and the one-time reminder onboarding, then +shows `CalendarHost`. `CalendarHost` holds the active view (month/week/day) +plus overlay state for detail, edit, and settings — full-screen overlays +driven by `AnimatedVisibility` with a *held-key* pattern: the last shown +key stays alive through the slide-out so content never flashes empty. +A tapped reminder notification routes through `MainActivity` (`singleTop` + +`onNewIntent`) as an external detail key that `CalendarHost` consumes +exactly like an event tap. + +## Recurring writes + +The provider's invariants drive the design (learned the hard way, verified +on-device — see plan 03): + +- Recurring rows carry `RRULE` + `DURATION` (no `DTEND`); one-off rows + carry `DTEND`. +- *Only this event* → insert a **modified-occurrence exception** via + `CONTENT_EXCEPTION_URI` (the provider clones the series row, so empty + optionals are written as explicit NULLs). +- *This and following* → **series split**: insert the new event first (if + that fails the original is untouched), then truncate the original's + RRULE with `UNTIL`. +- Truncation updates must send the **complete time-column set** + (`DTSTART`/`DURATION`/`RRULE`/`ALL_DAY`/`EVENT_TIMEZONE`) — the provider + regenerates cached instances only from the values carried by the update + itself; an RRULE-only update leaves stale instances behind. +- `UNTIL` is written as the local end of the previous day expressed in + UTC, so zones ahead of UTC can't leak an extra occurrence. +- All-day events are normalised to UTC midnights with an exclusive end. + +## Save conflicts + +No locking. `openForEdit` keeps an `EditSnapshot` — the prefilled form +*plus the raw Events-row times* (the form derives its times from the tapped +occurrence, so a remotely moved event would otherwise be invisible to it). +Right before writing, the event is re-read and snapshots compared: a +mismatch parks the save in an overwrite/discard dialog; a vanished event +informs and closes. Overwrite still writes only dirty fields, so external +changes to untouched fields survive either way. Fields the form cannot +write (attendees, status, reminder methods) are excluded so sync noise +can't fake a conflict. + +## Reminder delivery + +The provider schedules reminder alarms (for `METHOD_ALERT` rows only) and +broadcasts `EVENT_REMINDER` — but posts no notification; a calendar app +must (the Etar model): + +```mermaid +sequenceDiagram + participant P as CalendarProvider + participant R as EventReminderReceiver + participant S as ReminderAlertStore + participant N as ReminderNotifier + P->>R: EVENT_REMINDER broadcast (manifest receiver, exported) + R->>S: dueAlerts(now) — CalendarAlerts: SCHEDULED, alarmTime ≤ now + S-->>R: due alerts + R->>N: post(alert) — one notification per alert, tag = alert id + R->>S: markFired(ids) — best effort, needs WRITE_CALENDAR +``` + +Posting happens before marking: a crash in between re-posts silently (same +tag + `setOnlyAlertOnce`) rather than losing a reminder. Swiped +notifications never return because `FIRED` rows are never re-queried. +Deliberately absent until real devices prove it necessary: own alarm +scheduling, `BOOT_COMPLETED`, snooze/dismiss actions, battery-exemption +prompts. + +## Testing + +JUnit 5 + Truth + Turbine on the JVM. The seams that make it work: +`CalendarDataSource` is faked (`FakeCalendarDataSource` records writes), +mappers parse `ColumnReader`/plain maps instead of cursors, domain logic +(recurrence, validation, snapshots, write-value building) is pure. CI +(Gitea Actions) runs `lint test assembleDebug` on every push; release tags +additionally build, sign, and publish to the self-hosted F-Droid repo. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3eef8c7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +# Documentation map + +Where to look for what: + +| Document | What it is | +|---|---| +| [`ARCHITECTURE.md`](ARCHITECTURE.md) | Orientation tour: principles, layers, navigation, recurring-write / conflict / reminder pipelines, testing | +| [`../CHANGELOG.md`](../CHANGELOG.md) | Release history (Keep a Changelog, SemVer) | +| [`../.planning/ROADMAP.md`](../.planning/ROADMAP.md) | Living roadmap: shipped milestones, current scope, idea backlog | +| [`../.planning/PROJECT.md`](../.planning/PROJECT.md) | What the project is, stack, naming, infrastructure | +| [`../.planning/REQUIREMENTS.md`](../.planning/REQUIREMENTS.md) | Requirement checklist per milestone | +| [`../.planning/STATE.md`](../.planning/STATE.md) | Snapshot of where development currently stands | +| [`superpowers/specs/`](superpowers/specs/) | The original design spec (2026-06-08) — historical record, not updated | +| [`superpowers/plans/`](superpowers/plans/) | Per-milestone implementation plans with task checklists — historical record of how each slice was built, including provider lessons learned | +| [`../fdroid-metadata/`](../fdroid-metadata/) | F-Droid/fastlane store metadata: descriptions, icon, screenshots (DE + EN) | + +Conventions: plans and specs under `superpowers/` are point-in-time +artifacts of the agentic workflow that built each milestone — they get +status updates but are never rewritten. The `.planning/` files are living +documents and should stay current.