Bumps to 0.1.1+3 (versionCode 3). Adds CHANGELOG [0.1.1], fastlane
changelogs (en-US, es-ES) for versionCode 3, and a 0.1.1 build entry in
the F-Droid recipe (commit v0.1.1, CurrentVersion/Code 3).
Removes the geolocator dependency, which transitively embedded
com.google.android.gms.* (play-services-location) — proprietary classes
that F-Droid's scanner rejects. The optional coarse-location button now
talks to Android's own LocationManager over a small platform channel
(org.comunes.tane/coarse_location): permission prompt, single coarse fix
with a 12s timeout, last-known fallback, null on any failure. Behavior
is unchanged and the CoarseLocationProvider interface is preserved, so
the UI, i18n and widget tests need no changes.
Also sets dependenciesInfo.includeInApk/Bundle = false so the AGP
dependency-metadata block (also flagged by F-Droid) is not embedded.
Neither change affects the Google Play build.
Format: repo predates Dart 3.11's tall formatter (~128 files would need a
dedicated reformat commit); disable --set-exit-if-changed for now, keep
flutter analyze as the real gate.
Test: 'drawer Settings opens the settings screen' asserted find.text('Español'),
which only holds when the resolved locale is Spanish. Under AppLocale.en (as
set in the test) the tile reads 'English', so it failed under CI's non-Spanish
host locale. Assert by the language selector's Key + icon instead (locale-robust).
Noto Sans wordmark on brand green (#2F7D34), white sprout, and the official
value tagline ('keep and share traditional seeds' / 'guarda y comparte...').
Generated by tool/gen_feature_graphic.py for en-US and es-ES.
- fastlane: Appfile/Fastfile/Gemfile with deploy_play lane (AAB -> Play internal track)
- CI: tag-gated build:android (signed AAB/APK from CI secrets) + deploy:play jobs
- F-Droid: fdroiddata build recipe at docs/fdroid/org.comunes.tane.yml
- Play compliance: Data Safety / content-rating answer sheet (docs/legal/internal)
- docs/release.md: automated tag-triggered flow, dedicated tane-upload keystore
- pubspec: description now mentions the market, not just the inventory
The Cancel / Save-and-add-another / Save actions were a fixed Row that
could not shrink, so on narrow phones and long locales (es/pt/de/fr) the
buttons overflowed the right edge (the clipped 'Guardar'). Replace it with
an OverflowBar that wraps onto stacked, end-aligned lines when they don't
fit; move the added-count onto its own line above.
Guard it in small_screen_overflow_test.dart across the long locales.
Beta-tester feedback triage. The data model already had LotType.seedling as
first class; the gaps were copy and UX.
- Seedlings/plantones: broaden landing hero (EN/ES), in-app legal notice, and
all legal texts (site + docs masters) from seeds-only to "seeds and
seedlings", with a live-plant phytosanitary/transport caveat. Seeds stay the
hero — targeted, not a blanket rename.
- Backups: settings line now states the 7-day cadence explicitly; cadence lives
in AutoBackupService.backupInterval as the single source for text and schedule.
- Community servers: replace the manual wss:// text box with a checklist of the
known servers (defaults visible/toggleable) plus an "add server" affordance
with basic validation. No jargon in the UI.
i18n across en/es/pt/fr/de/ast (ja falls back). Tests updated + a new
server-picker widget test.
- Hugo site (site/): EN/ES intro + legal (generated from docs/legal), an
ES/EN About page from docs/{que-es-tane,what-is-tane}.md, seed-green theme,
responsive header, store-badge placeholders, multi-stage Hugo->nginx image.
- Golden screenshot harness (test/screenshots/): en,es,fr,de,pt,ja + RTL demo,
real fonts via DejaVu Sans + FontManifest; skip-by-default tag so CI stays
green. collect_screenshots.sh feeds site/ and fastlane phoneScreenshots.
- Drop editorial notes from the public explainer.
The propose sheet used a free-text seed name, unlinked from the
inventory. Now the seed is picked from your catalogued varieties so the
Plantaré is always tied to a real Variety (tap-through to its detail, and
it shows in that seed's commitments):
- Seed field is an Autocomplete over the inventory; on send the typed name
resolves to the matching Variety, or creates a quick one if it's new —
so varietyId is never null. Sheet takes the VarietyRepository; chat
passes it.
- New repo one-shot `varietyLabels()` (id + label, no joins) feeds the
picker. Deliberately a Future, not `watchInventory()` — a transient
sheet must not hold a live Drift subscription (that hung the widget test
~36 min). Tests use it too and now run in ~2s with bounded pumps + a
tall surface, no pumpAndSettle.
Tests: a new name creates & links a Variety; a matching name links the
existing one without duplicating. ui/services/data plantare suites green.
The viral loop, made usable end-to-end:
- Propose sheet (plantare_propose_sheet.dart): from a chat with a peer
(their key already in hand), pick your side, name the seed, choose what
comes back (similar · non-GMO organic / work hours / other) and an
optional return-by, then sign and send. Reached from the chat overflow
menu; confirms "waiting for them to sign".
- Plantares screen: incoming proposals show Accept & sign / Decline right
on the tile; every bilateral row wears a handshake badge — awaiting
signature, signed by both, or declined. Accept falls back to a gentle
"offline" nudge if the proposal isn't in hand yet.
- i18n: full en + es for the bilateral flow; other locales fall back to
base per slang config.
Wire-up only touches human copy (no jargon) and stays behind the
optional PlantareService, so inventory-only builds are unaffected.
Tests: propose sheet drives a real sign+send; the ledger renders the
signed/awaiting badges. Fake SocialSession in your_people test gains the
new transport getter. Widget tests targeted + timed. All green.
The app-layer driver that turns local intent into a signed proposal,
counter-signs or declines incoming ones, and reconciles every move into
the local ledger. Cryptography and wire format stay in commons_core;
this owns persistence and orchestration.
- propose(): builds + self-signs a PlantarePledge, records the local row
(remoteState=proposed, my stub) and sends it. Direction maps to
debtor/creditor: iReturn = I received and owe, owedToMe = I gave.
- accept(): counter-signs the exact in-hand proposal (verified) and sends
the doubly-signed copy back, closing the row.
- decline(): notifies the proposer and marks the row declined.
- ingest(): verifies stubs before storing — a bad-signature proposal or a
half-signed "accept" is dropped; proposals not addressed to me are
ignored. Pending proposals held in memory keyed by pledge id (relay
redelivers on reconnect), so accept signs the verified payload.
Wired into DI + Bootstrap alongside the inbox/sync listeners and torn
down/rebuilt on identity switch. SocialSession now exposes the transport.
Tests: full propose→accept close, decline, and the three drop paths with
a records-only transport + real repos. services/data green.
Adds the deferred bilateral columns to Plantares (plantare-bilateral.md
§"Schema delta"), all nullable/defaulted so v1 local rows coexist
untouched:
- pledgeId (the shared id both parties key their row by), debtorKey /
creditorKey, debtorSignature / creditorSignature, movementId (the
shared hand-over Movement for the provenance DAG), remoteState
(proposed/accepted/declined — the handshake, distinct from status),
returnKind (similar/workHours/other, default similar) + workHours.
Repo: createPlantare grows optional bilateral params; plantareByPledgeId
looks a row up by the shared id; applyPlantareSignatures counter-stamps
an incoming accept; setPlantareRemoteState records a decline. The
JSON codec carries every new field so they ride backups.
Versioned migration is guarded/idempotent like the others; schema v12
dumped, test helper regenerated, migration test covers v1..v11 → v12.
db + data + services 295/295 green.
Bundle Noto Sans Arabic + Noto Sans JP (SIL OFL 1.1) as per-glyph fallbacks
for both the UI text theme and every generated PDF, so RTL (Arabic) and CJK
render on all platforms — including desktop, where the system font may lack
them — instead of tofu. A shared pdf_fonts.dart centralizes the PDF theme and
the three label/catalog/recovery services reuse it.
Add Japanese (ja) as the CJK reference locale — fittingly, the app's own name
is Japanese (種, tane, 'seed'). Core strings translated; the rest fall back to
English via slang. Wired into supportedLocales (automatic) and the settings
picker.
Fix three physical Alignment.centerLeft -> AlignmentDirectional.centerStart in
variety_detail_screen, and make month-abbreviation and avatar-initial
truncation grapheme-safe (.characters) so CJK text is never cut mid-character.
Tests: CJK/Arabic PDF glyph render, ja locale resolution + English fallback.
The chat-bubble mirroring flagged by the audit was verified correct (already
respects Directionality) and left unchanged.
The flat list of 6 languages plus 'system' dominated Settings. Replace it
with a single tile showing the current language, opening a bottom-sheet
picker (all languages + 'System language'). Resolve LocaleStore defensively
so the screen renders in DI-less widget tests.
Full fr/de translations of the app (625 keys each), wired into the
Settings language picker and the small-screen overflow test. Material
ships fr/de localizations, so chrome resolves natively.
The Plantaré screen stored madeOn/dueBy/varietyId but showed none of them,
so it felt half-built. Surface what's already there (no schema change):
- watchPlantareViews() joins each commitment with its variety label
- tile shows the made-on date, a "Return by {date}" line (amber + "overdue"
when an open promise is past due, via new seedWarning colour), the variety
name, and taps through to /variety/:id
- add sheet gains an optional return-by date picker (createPlantare already
took dueBy)
- variety detail shows a read-only commitments section (hidden when empty,
no add button — honours the single hand-over door)
- i18n en/es/pt/ast; tests for the join, dueBy round-trip, tile, and section
Also specs the deferred two-sided record in docs/design/plantare-bilateral.md
(Nostr pubkey counterparty, PlantareTransport propose->accept->counter-sign,
deferred key/signature/movement columns, provenance DAG, reputation anchor),
with a faithful transcription of the paper Plantaré v0.4 (BAH-Semillero 2009,
CC-BY-SA). Cross-linked from data-model §2.7 and sharing-model §6.
On short screens (e.g. landscape) the fixed-height trust and rating
strips plus the composer overflowed the body once the keyboard shrank
it. Wrap the two secondary strips in HideWhenKeyboardOpen so they yield
their vertical space while typing and reappear when the keyboard closes.
Adds a Favorites feature: a heart on the offer detail saves another
person's listing to an encrypted, per-identity SavedOffersStore
(keystore JSON snapshot, no plaintext at rest). A new /favorites screen
(wired from the drawer) lists saved offers offline-first and, when a
relay is reachable, flags ones that are gone as "no longer available".
i18n en/es/pt/ast; store + screen + detail-heart tests.
Drop the DiceBear generated avatar (and dicebear_core/dicebear_styles/
flutter_svg): the default is again a Material coloured-initial disc from
the pubkey. Picking a photo now goes through a square crop step
(crop_your_image — pure Flutter, all platforms incl. desktop, Apache-2.0,
only pulls the image package we already had) before the 24 KB thumbnail.
The picker now offers only a photo; with none, the generated DiceBear
avatar is used. Removes the seedks-ttf illustration row (and the now-dead
avatarGlyphNames/avatarGlyphToken). tane:seed:<glyph> values set before
this still render (avatarGlyphChar kept) for backward compatibility.
Replace the flutter_boring_avatars fallback with a friendly DiceBear
"thumbs" face, generated offline (dicebear_core) and deterministically
from the pubkey, rendered as SVG (flutter_svg). The thumbs style is
CC0 1.0 (public domain) — no attribution, AGPL-clean — unlike Multiavatar
whose custom licence restricts use. Style parsed once and per-pubkey SVG
memoized; the CC0 <metadata> block is stripped to keep flutter_svg quiet.
People without a photo or seed illustration now get a deterministic
pattern avatar drawn from their pubkey (flutter_boring_avatars, MIT),
in an earthy seed-tone palette, replacing the coloured-initial disc.
Same pubkey renders the same picture on every device with nothing shared.
Fastlane title.txt + full_description.txt (en-US, es-ES) now lead with
Tane and keep tanemaki (種まき) as etymology; CI comment and the Material
mockups updated too.
Public name is now Tane (種, "seed"); Tanemaki (種まき, "to scatter seeds")
is kept only as etymology in the About screen and onboarding.
App not yet distributed, so format tokens rename cleanly with no migration:
backup extension .tanemaki→.tane, QR scheme tanemaki://→tane://, file
prefixes tane-*. Website link and species-catalog User-Agent →
tane.comunes.org. Android label capitalised to "Tane".
Identity/crypto constants already use org.comunes.tane (derivation, HKDF
backup, sync namespace, TANE1 recovery tag) — left untouched, so keys,
backups and sync are unaffected. Tests updated; analyze + full suite green.
The 'giving all with payment and promise' widget test awaited
repo.watchSales().first / watchPlantares().first in its body. A Drift
stream's .first never completes under the widget-test fake-async clock
(pumpAndSettle has already returned, so dart_test.yaml's per-test timeout
can't bound it), so the test hung to the framework's 10-min hard cap.
- Read the recorded sale/plantare with one-shot db.select(...).get()
queries instead (same filter, resolves fine under fake-async) — the
suite now passes in ~1s.
- Add a pure-Dart guard test that scans test/ui/ and fails loudly if any
awaited watchX().first reappears, so this stops being recurrent.
Pinning it beside the horizontal scroll squeezed the chips into a narrow
strip and clipped them under the edge fade (e.g. 'Comparto' cut off by
the scroll arrow). Now the chips get the full width and 'clear filters'
sits on its own line below, shown only when a filter is active.
Six reported UX issues:
- Inventory 'clear filters' is now pinned outside the horizontal scroll,
always visible — you no longer have to scroll past every chip to find it.
- Empty result now reads 'No seeds match your filters' (not 'no seeds
yet') when a SEARCH or the 'this month' filter hid everything — the
'filtered' flag was missing query + sowThisMonthOnly. Guarded by a test.
- Profile avatar saves + publishes the instant you pick it, so it no
longer looks saved when it wasn't (the Save button sits below the fold).
- Market offer detail shows the author's real avatar (photo/illustration)
instead of a generic person icon — resolves + caches their kind:0 picture.
- 'Wishlist' renamed to Favourites/Favoritos (en/es/pt/ast).
- The drawer 'following' item now opens the web-of-trust 'your people'
screen when the social layer is on (was a dead item).
analyze clean; inventory/market/home + overflow suites green.
Follow-up to the avatar feature: peers' published photos/illustrations
now actually render where people appear.
- CachedAvatar: a PeerAvatar that resolves the person's picture from the
ProfileCache (FutureBuilder), falling back to the coloured-initial disc.
For list/row sites — one avatar each.
- Chat bubbles: the peer's and my own avatar are resolved once into state
(_peerPicture/_selfPicture — from the cache / ProfileStore, freshened
from the peer's published profile) and threaded to each bubble, so many
bubbles of two people don't each hit the cache.
- Chat list + your-people rows: swapped the static person-icon disc for
CachedAvatar, so photos/illustrations show there too.
- Tests: CachedAvatar render modes (cached illustration / initial / no
cache). analyze clean; chat + your-people suites green.
Market offers show no author avatar today (would be a new element, not a
swap) — left out of scope.
Profiles had only a coloured-initial disc. Now each person can set an
avatar — a real photo OR one of our own seed illustrations (so
pseudonymity stays the default; no photo required).
- ui/avatar.dart: the one-string value scheme carried in the kind:0
'picture' — a 'data:' photo thumbnail, a 'tane:seed:<glyph>' token, or
empty. The Nostr ProfileTransport already published 'picture'.
- Photos ride inline as a tiny thumbnail (reuses offerThumbnailDataUri,
24 KB cap) — no media server, like offer photos.
- avatar_edit.dart: pick/take a photo, choose a seed illustration, or
remove; ProfileStore stores it; ProfileScreen shows a big editable
avatar and publishes it.
- PeerAvatar renders photo / illustration / initial fallback.
- ProfileCache gains picture/setPicture; the inbox caches peers' avatars
alongside their names.
- i18n avatar block (en/es/pt/ast). Tests: value scheme + PeerAvatar
render modes, store + cache round-trips.
Follow-up: render peers' avatars in chat/market/your-people (thread the
cached picture into PeerAvatar at each call site).
From the owner's TODO.org review of the shipped features:
- Crop calendar now says the months are YOUR notes: a hint line in the
editor (cropCalendar.editorHint) and a caption under the 'this month'
screen (calendar.selfNote) — it read as if the app knew when to sow.
- Dropped the 'how much I have' (abundance) input + display: it competed
with the lot quantity as a second 'how much'. Removed from the lot sheet
and the lot summary; the DB column + backup/CSV/catalog plumbing stay
dormant (no destructive migration). offer_status carries 'do I share'.
- Seed-saving guidance moved into a collapsed expander (detail.seedSaving)
— reference facts about the species shouldn't sit open among the
grower's own records; opt-in depth, per progressive disclosure.
- Condition checks already live behind the collapsed 'advanced' expander
(seed lots only) — left as the discreet power-user surface.
Tests: dropped the obsolete abundance→share nudge test; the seed-saving
section test now expands before asserting. analyze clean; suites green.