From 4acf227e30166dcb543e476eb0fb1be4688cf51c Mon Sep 17 00:00:00 2001 From: vjrj Date: Thu, 9 Jul 2026 23:10:01 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20beta=20packaging=20=E2=80=94?= =?UTF-8?q?=20signing,=20changelog,=20store=20metadata,=20scale=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Android release signing reads a gitignored key.properties, falling back to debug keys so contributors can still build --release. - CHANGELOG.md (human, newest-first) + Fastlane/F-Droid metadata (en/es) with descriptions derived from docs/intro.md. - docs/release.md: the by-hand build + manual smoke checklist (no CI yet). - inventory_scale_test guards the list query against N+1 at 3000 varieties. --- CHANGELOG.md | 32 +++++++++ apps/app_seeds/android/app/build.gradle.kts | 37 ++++++++-- .../metadata/android/en-US/changelogs/1.txt | 6 ++ .../android/en-US/full_description.txt | 27 +++++++ .../android/en-US/short_description.txt | 1 + .../fastlane/metadata/android/en-US/title.txt | 1 + .../metadata/android/es-ES/changelogs/1.txt | 6 ++ .../android/es-ES/full_description.txt | 27 +++++++ .../android/es-ES/short_description.txt | 1 + .../fastlane/metadata/android/es-ES/title.txt | 1 + .../test/data/inventory_scale_test.dart | 54 ++++++++++++++ docs/release.md | 71 +++++++++++++++++++ 12 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 apps/app_seeds/fastlane/metadata/android/en-US/changelogs/1.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/en-US/full_description.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/en-US/short_description.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/en-US/title.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/es-ES/changelogs/1.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/es-ES/full_description.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/es-ES/short_description.txt create mode 100644 apps/app_seeds/fastlane/metadata/android/es-ES/title.txt create mode 100644 apps/app_seeds/test/data/inventory_scale_test.dart create mode 100644 docs/release.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e2226e1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to Tanemaki are recorded here. Human-readable, newest +first. Dates are ISO (YYYY-MM-DD). + +## [Unreleased] — Block 1 beta prep + +Block 1 is the offline, encrypted seed inventory — useful with zero network. + +### Added +- **Local sharing.** Mark a batch as to give away, to swap or for sale (gift + and swap first, sale never the default). A "what I share" filter and a + printable catalog (a paper bridge for fairs) surface it. +- **Sealed backups & recovery.** "Save a backup" writes a single encrypted + file; restoring on your own device is silent, and a copy from another + identity opens with the printed recovery code — which also brings your + identity back. A printable recovery sheet (QR + code) lives in Settings. +- **Full editing.** Lots and vernacular names can be edited and removed, not + just added. +- **Portuguese** interface, alongside Spanish and English; right-to-left + layouts verified. + +### Changed +- "How much I have" is answered by the coarse abundance scale (plenty to + share … running low), which now nudges toward sharing when you have plenty. + A precise gram/seed-count field is deferred until network offers need it. + +### Notes +- Everything is local-first and encrypted at rest (SQLCipher). No account, no + server, no tracking. +- The social layer (network offers, messaging, web of trust) is Block 2 and + intentionally not part of this release. diff --git a/apps/app_seeds/android/app/build.gradle.kts b/apps/app_seeds/android/app/build.gradle.kts index 9718a0a..e785816 100644 --- a/apps/app_seeds/android/app/build.gradle.kts +++ b/apps/app_seeds/android/app/build.gradle.kts @@ -1,3 +1,6 @@ +import java.util.Properties +import java.io.FileInputStream + plugins { id("com.android.application") id("kotlin-android") @@ -5,6 +8,17 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +// Release signing is opt-in: drop a `key.properties` (gitignored) next to this +// module's android/ dir to sign release builds with your own keystore. Without +// it, release builds fall back to debug keys so contributors can still run +// `flutter run --release`. See docs/release.md. +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +val hasReleaseKeystore = keystorePropertiesFile.exists() +if (hasReleaseKeystore) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + android { namespace = "org.comunes.tane" compileSdk = flutter.compileSdkVersion @@ -20,7 +34,6 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "org.comunes.tane" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. @@ -30,11 +43,27 @@ android { versionName = flutter.versionName } + signingConfigs { + if (hasReleaseKeystore) { + create("release") { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = keystoreProperties["storeFile"]?.let { file(it) } + storePassword = keystoreProperties["storePassword"] as String + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + // Sign with the real release keystore when key.properties is present, + // otherwise fall back to debug keys so `flutter run --release` works + // for contributors without the signing material. + signingConfig = if (hasReleaseKeystore) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } } } } diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/changelogs/1.txt b/apps/app_seeds/fastlane/metadata/android/en-US/changelogs/1.txt new file mode 100644 index 0000000..358d332 --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/en-US/changelogs/1.txt @@ -0,0 +1,6 @@ +First public beta of Block 1: the offline, encrypted seed inventory. + +- Local sharing (give away / swap / sell) with a printable catalog. +- Encrypted backups and a printable recovery sheet. +- Edit and delete lots and vernacular names. +- Portuguese added; right-to-left layouts verified. diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/full_description.txt b/apps/app_seeds/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000..856980c --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,27 @@ +Tanemaki (種まき, "to sow seeds") is a local-first, decentralized app for +keeping and sharing traditional seeds. + +Every traditional seed is a letter written by thousands of generations, passed +from hand to hand. Tanemaki helps people and seed-saver collectives keep a +friendly inventory of their bank, decide what they offer, and share it locally +— without a central intermediary that could control, censor, or be fined for +it. + +MANAGE YOUR BANK +• Add a seed with just a photo and a name — no Latin required. +• Note the year, how much you have, where it came from, germination tests. +• Search and filter; snap a photo now and name it later. + +YOURS, AND ONLY YOURS +• Works fully offline. No account, no server, no trackers. +• Everything is encrypted at rest on your device. +• Save an encrypted backup and keep a printed recovery sheet — restore your + whole bank on a new device. + +SHARE, THE WAY IT'S ALWAYS BEEN DONE +• Mark what you have spare to give away, swap or sell. +• Print a catalog of what you share to take to a seed fair. + +Tanemaki is free software (AGPL-3.0). No ads, no commissions, no business model +— it exists to support traditional varieties and push back against the seed +monopoly. diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/short_description.txt b/apps/app_seeds/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000..8570f7c --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Your seed bank in your pocket — offline, encrypted, no account. diff --git a/apps/app_seeds/fastlane/metadata/android/en-US/title.txt b/apps/app_seeds/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 0000000..772f5e1 --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Tanemaki diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/changelogs/1.txt b/apps/app_seeds/fastlane/metadata/android/es-ES/changelogs/1.txt new file mode 100644 index 0000000..705accc --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/es-ES/changelogs/1.txt @@ -0,0 +1,6 @@ +Primera beta pública del Bloque 1: el inventario de semillas sin conexión y cifrado. + +- Compartir localmente (regalar / intercambiar / vender) con catálogo imprimible. +- Copias cifradas y hoja de recuperación imprimible. +- Editar y borrar lotes y nombres vernáculos. +- Añadido portugués; disposiciones de derecha a izquierda verificadas. diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/full_description.txt b/apps/app_seeds/fastlane/metadata/android/es-ES/full_description.txt new file mode 100644 index 0000000..00934ad --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/es-ES/full_description.txt @@ -0,0 +1,27 @@ +Tanemaki (種まき, "sembrar semillas") es una app local-first y descentralizada +para guardar y compartir semillas tradicionales. + +Cada semilla tradicional es una carta escrita por miles de generaciones, que +pasa de mano en mano. Tanemaki ayuda a personas y colectivos de guardianas de +semillas a llevar un inventario amable de su banco, decidir qué ofrecen y +compartirlo localmente, sin un intermediario central que pueda controlarlo, +censurarlo o ser multado por ello. + +GESTIONA TU BANCO +• Añade una semilla con solo una foto y un nombre; sin latín. +• Anota el año, cuánta tienes, de dónde viene, pruebas de germinación. +• Busca y filtra; haz una foto ahora y ponle nombre después. + +TUYO, Y SOLO TUYO +• Funciona totalmente sin conexión. Sin cuenta, sin servidor, sin rastreadores. +• Todo va cifrado en tu dispositivo. +• Guarda una copia cifrada y ten una hoja de recuperación impresa: recupera + todo tu banco en un dispositivo nuevo. + +COMPARTIR, COMO SE HA HECHO SIEMPRE +• Marca lo que te sobra para regalar, intercambiar o vender. +• Imprime un catálogo de lo que compartes para llevar a una feria de semillas. + +Tanemaki es software libre (AGPL-3.0). Sin anuncios, sin comisiones, sin modelo +de negocio: existe para apoyar las variedades tradicionales y plantar cara al +monopolio de las semillas. diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/short_description.txt b/apps/app_seeds/fastlane/metadata/android/es-ES/short_description.txt new file mode 100644 index 0000000..5c2261e --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/es-ES/short_description.txt @@ -0,0 +1 @@ +Tu banco de semillas en el bolsillo: sin conexión, cifrado, sin cuenta. diff --git a/apps/app_seeds/fastlane/metadata/android/es-ES/title.txt b/apps/app_seeds/fastlane/metadata/android/es-ES/title.txt new file mode 100644 index 0000000..772f5e1 --- /dev/null +++ b/apps/app_seeds/fastlane/metadata/android/es-ES/title.txt @@ -0,0 +1 @@ +Tanemaki diff --git a/apps/app_seeds/test/data/inventory_scale_test.dart b/apps/app_seeds/test/data/inventory_scale_test.dart new file mode 100644 index 0000000..d6928df --- /dev/null +++ b/apps/app_seeds/test/data/inventory_scale_test.dart @@ -0,0 +1,54 @@ +import 'package:commons_core/commons_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tane/data/variety_repository.dart'; +import 'package:tane/db/database.dart'; +import 'package:tane/db/enums.dart'; + +import '../support/test_support.dart'; + +/// A real seed bank can hold thousands of accessions. This guards that the +/// list query stays a handful of batched queries (not N+1) and returns in a +/// human blink even at that scale — the pre-beta performance check. +void main() { + late AppDatabase db; + late VarietyRepository repo; + + setUp(() { + db = newTestDatabase(); + repo = newTestRepository(db); + }); + tearDown(() => db.close()); + + test('the inventory view loads 3000 varieties quickly', () async { + const count = 3000; + for (var i = 0; i < count; i++) { + final id = await repo.addQuickVariety( + label: 'Variety $i', + category: i.isEven ? 'Poaceae' : 'Fabaceae', + ); + // Every few varieties, add a lot so the batched lot/type/share queries + // do real work. + if (i % 5 == 0) { + await repo.addLot( + varietyId: id, + harvestYear: 2020 + (i % 5), + quantity: const Quantity(kind: QuantityKind.handful), + offerStatus: i % 25 == 0 ? OfferStatus.shared : OfferStatus.private, + ); + } + } + + final sw = Stopwatch()..start(); + final view = await repo.watchInventoryView().first; + sw.stop(); + + expect(view.items, hasLength(count)); + // Generous ceiling for CI; typical local runs are well under 500ms. The + // point is to catch an accidental N+1 regression, not to micro-benchmark. + expect( + sw.elapsedMilliseconds, + lessThan(3000), + reason: 'inventory view took ${sw.elapsedMilliseconds}ms for $count rows', + ); + }); +} diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..e275ed1 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,71 @@ +# Releasing Tanemaki (manual, no CI yet) + +Block 1 ships as a self-contained Android app (desktop builds also work). There +is no release pipeline yet — this page is the by-hand checklist. + +## Versioning + +- Semantic version in [`apps/app_seeds/pubspec.yaml`](../apps/app_seeds/pubspec.yaml) + as `MAJOR.MINOR.PATCH+BUILD` (e.g. `0.1.0+1`). Flutter maps `+BUILD` to + Android `versionCode` and the rest to `versionName`. +- Record every user-visible change in [`../CHANGELOG.md`](../CHANGELOG.md) and + add a matching per-locale note under + `apps/app_seeds/fastlane/metadata/android//changelogs/.txt`. + +## Android signing (one-time) + +Release builds sign with your own keystore when a **gitignored** +`apps/app_seeds/android/key.properties` exists; without it they fall back to +debug keys (so contributors can still `flutter run --release`). + +1. Create a keystore (keep it and its passwords safe — losing it means you can + never update the app under the same identity): + ```bash + keytool -genkey -v -keystore ~/tane-release.jks \ + -keyalg RSA -keysize 4096 -validity 10000 -alias tane + ``` +2. Create `apps/app_seeds/android/key.properties` (never commit it): + ```properties + storeFile=/home/you/tane-release.jks + storePassword=… + keyAlias=tane + keyPassword=… + ``` + +## Build the beta by hand + +```bash +cd apps/app_seeds +dart run slang # i18n (if strings changed) +dart run build_runner build --delete-conflicting-outputs # Drift (if schema changed) +flutter test # must be green +flutter build apk --release # or: flutter build appbundle --release +``` + +The APK lands at +`apps/app_seeds/build/app/outputs/flutter-apk/app-release.apk`. + +## Store metadata + +F-Droid / Fastlane-style metadata lives under +`apps/app_seeds/fastlane/metadata/android/`. Descriptions are derived from +[`intro.md`](intro.md); icon and splash are generated by +`flutter_launcher_icons` / `flutter_native_splash` (see `pubspec.yaml`). + +## Manual smoke test before shipping + +Automated tests cover behaviour; a release build still gets a human pass on a +real device (the one place we allow manual testing — it validates the packaged +artifact, it does not replace tests): + +1. Cold start → intro carousel → home. +2. Quick-add a seed with a photo; it appears in the list. +3. Mark a lot to share; the "I share" filter and printable catalog appear. +4. Save a backup, then restore it (and test the recovery sheet + code on a + second install). +5. Switch language (es / en / pt); check an RTL locale if the device offers one. + +## Not in this release + +CI, Play Store automation, web builds (SQLCipher is unavailable on web), and +the whole social layer (Block 2).