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.
Make Tane primary in README, VISION, CLAUDE, the intros and the
plain-language explainer (renamed que-es-tanemaki.md → que-es-tane.md,
dropping its "provisional name" caveats); keep Tanemaki (種まき) only as
the etymology.
PLAN.md is the origin record: its naming section §5-ter is preserved
verbatim as history under a dated "superseded" note, and the Nota de
vigencia now records Tane + the tane.comunes.org domain.
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).
A short, all-audiences write-up derived from VISION.md: names concepts
like free software / copyleft / decentralised and explains each in plain
words rather than assuming them. States the name is tentative.
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.
Ego-centric trust replaces the global Duniter WoT, plus Wallapop-style
ratings v1 (kind 30778). Conflicts resolved: pubspec assets (kept
seed_saving, dropped trust referents), chat_screen (avatars/no-links
from main + rating strip and simplified TrustCubit from the branch),
strings.g.dart regenerated from merged sources.
- SocialSession grows a ratings transport (fifth interface on the one
shared channel).
- PeerRatingCubit: count, locale-aware average, and circleCount — how
many ratings come from your own circle (reuses the ego-centric trust
rule), the signal that makes strangers' review-stuffing irrelevant.
- Chat: slim rating strip under the trust banner; you can rate only
someone you've talked with (soft anchor until the signed bilateral
exchange form lands). Sheet with 5 stars + optional comment,
pre-filled for editing, with a take-back action.
- Offer detail: author's stars under their name, with 'N from people
you know' when your circle rated them; nothing shown when unrated.
- i18n ratings.* (en/es/pt/ast); tests for cubit and sheet.
Wallapop-style reputation primitive next to trust: Rating (1..5 stars +
optional comment), RatingTransport (rate/retract/ratingsOf/myRatingOf)
and a Nostr implementation on the shared channel. Addressable kind
30778, d = subject: one live rating per (rater, subject) — re-rating
replaces (a single author can never pile up reviews) and retracting
overwrites in place. Client-side dedupe mirrors NIP-01 replacement
order for relays that keep history.
The global membership rule (curated bootstrap referents + sigQty/stepMax
parameters) solved sybil-proof identity for a UBI — a problem this app
doesn't have — and its screen leaked graph jargon (npub roots, parameter
steppers). Trust is now computed from the user's own position only:
you vouch / vouched by people you know (distance <=2) / vouched by N.
- TrustCubit: drop networkMember tier, referents and params; keep the
circle rule and the 365-day vouch expiry (renewable, self-pruning).
- Delete TrustReferents, WotSettings, TrustNetworkScreen and the bundled
referents asset; unwire injector/bootstrap/app/chat.
- New 'Your people' screen (/your-people, from the profile): who you
vouch for (revocable) and who vouches for you, names via ProfileCache.
- i18n: wot.* removed, yourPeople.* added (en/es/pt/ast); trust.member
removed. Kind 30777 events on relays stay fully compatible — only the
interpretation changes.
The tick overlapped the leading icon on selected chips, reading as a
confusing blob. The solid fill already signals 'on', so hide the
checkmark. Also make the selected label reliably white via an explicit
per-chip style: the global ChipTheme's secondaryLabelStyle was not
applied to FilterChips, leaving dark text on green. Plain attribute/
market chips now share a PlainFilterChip widget; swatch and month chips
just drop their tick.
'Seeds changed hands' is now the single entry point on the variety
detail (and per lot row): direction (gave/received), one-tap 'all of it'
(default — the whole-plant/shrub case), optional partial quantity,
counterparty, and reveal-on-tap money and return-promise sections that
spawn the Sale/Plantare rows through recordHandover in one save.
Zero-lot varieties still work (ledger rows only); several lots get a
chip picker. The Sales/Plantares screens keep their own add buttons for
off-app records — the sheets stay, demoted to secondary.
i18n handover block in en/es/pt/ast; widget tests for the flow.
Returns to the original model (data-model §2.8: a closed deal produces a
Movement and, optionally, a Plantare): the given/received Movement plus,
when they came with it, the Sale (informational payment note) and the
Plantare (return promise), all sharing the counterparty. The Movement
carries the promise via the plantare_id column the schema had reserved.
'Gave all' (one tap, the natural path for shrubs/whole plants) moves the
lot's whole quantity, leaves an empty jar (0, same unit), clears
abundance, and returns the lot to private — withdrawing its market offer
on the next publish cycle, price cleared with it.
New enum HandoverDirection; tests in test/data/handover_test.dart.
The data model always meant sale offers to carry an informational price
(data-model §2.8, sharing-model §4.4); with offer state collapsed onto
the Lot, the price lives there. Null amount on a sell lot = 'to be
agreed' — the NIP-99 event simply omits the price tag.
- schema v11: Lots.price_amount/price_currency, guarded migration,
schema dump + generated schema_v11 for the round-trip test
- ShareableLot/VarietyLot carry the price (sell-only in shareableLots);
addLot/updateLot persist it and clear it when the lot leaves sale
- OffersCubit.publishLots forwards it (outbox flush picks it up free)
- lot sheet: price + currency inputs revealed only on 'For sale', with
the €/Ğ1/hours quick-picks extracted to a shared CurrencyQuickPicks
widget (reused by the sale sheet)
- backup JSON round-trips the new columns
- i18n share.price/priceHint in en/es/pt/ast
Record where the guidance comes from (copyright/attribution) and make
clear it is general, not local gospel — kept light:
- seed_saving.json gains a top-level sources list (Seed to Seed / Seed
Savers Exchange / Organic Seed Alliance); parsed into
SeedSavingData.sources, exposed via SeedSavingCatalog.sources.
- The detail section shows a small muted footer: an italic 'advisory —
adapt to your climate' line plus a 'Source: …' credit.
- i18n seedSaving.advisory + sourcePrefix (en/es/pt/ast).
- Tests: asset carries sources; the section renders advisory + credit.
The selected state used a pale tonal container (seedPrimaryContainer)
that sat almost on top of the pale canvas, so 'on' barely read. Selected
chips now fill solid — seedGreen for the plain attribute/market chips and
the swatch's own ink for family/form chips — with white label, icon and
checkmark. Idle chips stay light with dark ink, so the on/off jump is
unmistakable. White-on-ink AA is locked by a new contrast test.
The fade-to-transparent was invisible: the row sits on the same pale
canvas, so masking the edge to transparent just revealed more of the
same colour (and often landed on an inter-chip gap). Replace it with a
surface-coloured scrim the chips slide under, topped by an outward
chevron, shown only on an edge with more content. RTL-mirrored.
A bundled, curated knowledge layer — the differentiator of a serious
seed app. For a variety's crop it shows how the plant reproduces and what
it takes to keep the variety true: life cycle, pollination (self/cross +
insect/wind), isolation distance, how many plants to save from, dry/wet
processing, a difficulty pill, and a short tip — all in human words.
- assets/catalog/seed_saving.json: curated public-domain data (Seed to
Seed / Seed Savers), keyed by botanical FAMILY (defaults) with
per-species/genus OVERRIDES that win — so e.g. Vicia faba is correctly
insect-cross-pollinated despite the selfing Fabaceae default, and maize
is wind-pollinated with a large population. Notes are locale-keyed
(es/en), extensible to any language — a starter set, not a ceiling.
- domain/seed_saving.dart (pure): enums + SeedSavingGuide (merge, note
fallback) + SeedSavingData.guideFor (species → genus → family).
- data/seed_saving_catalog.dart: loads the asset once into an in-memory
singleton (no DB table, no migration); injector loads it at startup.
- VarietyDetail gains (from the linked species — the reliable
key, vs the editable category); detail screen shows a _SeedSavingView
when a guide resolves.
- i18n seedSaving block (labels + enum values + params) in en/es/pt/ast.
- Tests: domain lookup/merge, the real asset's key corrections, and the
detail section shows/hides. 314 suite tests green; analyze clean.
Chips clipped at the viewport edge with no cue that more filters exist.
New reusable EdgeFade widget (ShaderMask + dstIn) fades an edge only
while content remains beyond it, driven by scroll metrics; RTL mirrors
via Directionality. Applied to the inventory filter bar, market filter
bar and calendar month strip.
Local-first chat between people whose web of trust is still forming is a
natural phishing/scam vector, so messages may not contain URLs. A
conservative `containsUrl` rule (explicit scheme, www., or a common-TLD
domain — but not "3.5kg" or "12.30") gates it: the composer warns and
keeps the text for editing, and MessagesCubit.send drops any URL as a
backstop. Incoming text was already non-tappable (plain selectable text).
Feedback: my bubbles were too dark, and avatars showed only a person
glyph, never the profile name. Now both bubbles are light with dark text
(mine a tonal green, the peer's white with a hairline) — the side and the
per-person avatar colour carry the "who", so nothing is heavy to read.
Load my own display name from ProfileStore so my avatar shows my initial
too, not just the peer's; each avatar still falls back to a glyph when no
name is known.
Feedback: avatars only on the left, all the same colour, so it was hard
to tell who said what. In a 1:1 only the peer had an avatar (one colour).
Now each message carries its sender's avatar on their own side — mine
trailing, the peer's leading — each tinted by a deterministic colour from
their pubkey. Two sides, two colours: who-said-what is obvious at a glance.
The drawer was a single Column with a Spacer pinning intro/Settings/About
to the bottom. Once Calendar was added it overflowed a short screen (~94px),
so Settings and About fell off-screen and their navigation tests couldn't
tap them. Put the destinations in a scrolling ListView and keep the header
and the utility footer pinned, so Settings/About stay reachable at any
height. Fixes the about_screen/home_screen drawer navigation tests.
Blind-build polish pass over the recent sales/chips/calendar work:
- Chip label contrast: 7 palette inks (terracotta, ochre, rose, seedling,
plant, bulb, cutting) sat below WCAG AA on their own fill; darkened
(hue preserved) to ≥4.8:1. New category_palette_contrast_test locks it.
- Palette expanded 8 → 14 earthy tones (hues spread around the wheel) so
family colours rarely collide for a typical 6–10 family inventory,
keeping the stable-by-name, cross-screen-consistent scheme. Every new
ink meets AA on its fill.
- Calendar month strip now actually scrolls the selected month into view
(the comment promised it but it was never wired) — late-year months are
no longer left off-screen when the screen opens.
- Minor: dead code removed from the month chip.
dart analyze clean; palette/contrast/calendar/quantity/overflow/inventory
suites green.
Remove the "this month" card from the home menu; the calendar stays
reachable from the drawer and the /calendar route is unchanged. Also
drop the now-unused calendar.homeSubtitle i18n key (en/es/pt/ast) and
regenerate slang.
- Anchor the message list to the bottom (`reverse: true`) so new messages
stay in view instead of landing below the fold.
- Move chat history from the OS keystore (O(n²) JSON blob, silent 200-msg
cap) to a separate encrypted Drift/SQLCipher DB (`ChatDatabase`):
indexed append, uncapped history, dedup as a unique-key invariant. It's
an ephemeral per-device cache, isolated from the inventory schema, its
migrations, and its sync. No data migration (pre-release).
- Add day separators (Today/Yesterday/locale date) and a per-bubble time,
all via ICU (12/24h per locale; Localizations locale maps Asturian →
Spanish for intl date symbols).
- Add peer avatars (deterministic colour from the pubkey + name initial),
surface send failures that were previously silent, and make bubble text
selectable (addresses, links).
- New i18n keys in en/es/pt/ast; tests for grouping, formatting, avatars,
scroll anchoring, storage and send errors.
Docs: docs/design/chat-storage.md + open-decisions.md.
Reuses the existing per-variety crop-calendar masks (sow/transplant/
flowering/fruiting/seed-harvest) — no schema change, no migration — to
answer "qué toca este mes" across the whole inventory. Since the months
are the grower's own record, there's no hemisphere assumption.
- Repo: CalendarEntry + watchCalendar() (varieties with any recorded
phase); VarietyListItem now carries sowMonths.
- Dedicated CalendarScreen (/calendar): a month strip (defaults to the
current month, intl month names) and varieties grouped by phase —
actions (sow/transplant/harvest seed) above informational (flowering/
fruiting), each tinted, tap → variety detail; empty-month state.
- Inventory filter: a 'this month' chip (shown only when some variety
has a calendar) keeps what sows in the current month; folded into the
attributes group + clear-filters.
- Entry points: a live Home card + a drawer item.
- i18n calendar block + menu.calendar (en/es/pt/ast).
- Tests: calendar_test (watchCalendar, list carries sow-months, pure
filter), calendar_screen_test (phase grouping, month switch, empty),
plus the screen added to the small-screen overflow guard.
Four usability passes across the everyday flows:
- Quantity picker: a countable unit (cob/pod/ear…) now defaults to 1 and
the stepper clamps at a minimum of 1 — you can never store 'a cob' with
a blank/zero figure. Vibe units (a few) still carry no number.
- Category & form chips are colour-coded (new category_palette.dart): a
small muted earthy palette maps each botanical family to a STABLE
tonality (fill + readable ink), and each lot form gets its own tone.
Applied to the inventory filter bar (now grouped attributes | forms |
families, separated by a hairline), the list's category headers (a
matching colour dot), and the lot-form selector.
- Sale currency: quick-pick chips (€ · Ğ1 · hours) fill the field in one
tap — Ğ1 offered quietly among familiar options, never imposed — while
free text still takes anything else. New i18n key sale.hours (en/es/pt/ast).
Tests: quantity_picker_test (default-1, clamp, vibe, clear-still-1),
category_palette_test (stable/distinct swatches); overflow guard + the
inventory widget tests stay green.
A sale is a distinct model from a gift or a Plantare (reproduction
commitment): a recorded seed sale or purchase with an optional price in
ANY currency — €, Ğ1, time, or none yet. Mirrors the Plantare feature.
- schema v10: Sales table (SyncColumns), guarded createTable migration,
schema dump + generated schema_v10 for the migration round-trip test
- enum SaleDirection { iSold, iBought }
- VarietyRepository: create/watch/watchForVariety/delete + backup
export/exportForSync/import (LWW-by-HLC, tombstones)
- InventorySnapshot.sales + JSON codec encode/decode (round-trips amount,
currency, counterparty)
- UI: SalesScreen (/sales), sale sheet, drawer entry, variety-detail
action beside 'add Plantare'; money hides a trailing .0
- i18n sale block + menu.sales in en/es/pt/ast
- tests: sales repo test (5) incl. Ğ1/price-less/backup round-trip;
Sales screen added to the small-screen overflow guard
Also harden the overflow guard: swallow the headless engine's image
resource service PNG-decode errors (unrelated to layout) while still
failing on real RenderFlex overflows, so home/about stop reporting
false failures.
The reproduction-commitment (Plantare) UI, local-first and plain-spoken:
- A Plantares screen (drawer entry, /plantares) listing your commitments,
grouped Open / Done, with a big 'mark returned', a menu (let it go / reopen
/ remove) and an empty-state that explains what a Plantare is.
- An add sheet (scrollable): a two-choice 'who reproduces & returns' (I return
/ owed to me), an optional 'with whom', an optional 'what comes back' in the
grower's own words, and a note. No sale, no currency — a promise.
- Reachable from a seed too: an 'Add a commitment' action on the variety
detail, pre-attached to that variety.
- i18n en/es/pt/ast (human words: 'Plantare' + a plain explanation).
Added the Plantares screen to the small-phone overflow guard (18 cases green).
The Plantare — a promise to reproduce seed and return some (data-model §2.7,
the seed-domain Pledge with return_kind=similar). NOT a sale. Local-first
v1: your honest ledger of commitments (from/to a person by name); the
bilateral signed cross-party form is a later social-layer phase.
- New Plantares table (SyncColumns): varietyId, direction (iReturn/owedToMe),
counterparty, owedDescription, madeOn, dueBy, status (open/returned/
forgiven), settledOn, note. schemaVersion 8 -> 9 with a guarded createTable
migration; schema exported + migration-test helper regenerated.
- VarietyRepository: createPlantare / watchPlantares / watchPlantaresForVariety
/ setPlantareStatus / deletePlantare (soft-delete, CRDT-stamped).
- Included in backups + sync: InventorySnapshot + JSON codec (round-trips
isDeleted) + exportInventory/exportForSync/importInventory, so commitments
survive restore and replicate LWW like every other row.
Tests: v1..v8 -> v9 migration + fresh v9; repo create/list/settle/reopen/
delete; and a backup round-trip preserving a commitment. 13 green.
UI (detail section + Plantares screen) follows.
Two save-button sheets wrapped their body in a bare Column (no scroll): the
germination sheet (which grows a list of past tests) and the condition-check
sheet. On a small phone — especially with the keyboard up — the Save button
was pushed off the bottom, unreachable. Both now wrap the body in a
SingleChildScrollView, like the other input sheets already do.
Also extends the small-phone overflow guard to the profile (identity card +
Save), about, intro carousel, and the market/chat screens in their offline
'set up sharing' state. 17 cases green at 320x568 across es/pt/ast.
Adds about, intro and the profile screen (identity card + Save button) to
the 320x568 overflow guard across es/pt/ast. All pass — these screens
already fit small phones; the guard now protects them from regressions.
15 cases green.
Adds test/ui/small_screen_overflow_test.dart: pumps the full screens at a
320x568 (iPhone-SE-class) viewport in the longest-text locales (es/pt/ast);
a RenderFlex overflow fails the test, so a clipped title or save button is
caught. It found — and this fixes — a real bug: the Home menu (a fixed,
non-scrolling Center>Column) clipped ~80px on small phones in every locale.
Home is now wrapped in LayoutBuilder + SingleChildScrollView +
ConstrainedBox(minHeight: maxHeight): centered when it fits, scrolls when it
doesn't, never clipped.
Also makes the test harness faithful: wrapScreen/wrapDetail now map the
locale through materialLocaleFor (ast->es) exactly like the app, so Asturian
screens don't throw on MaterialLocalizations (AppBar tooltips) — that had
masqueraded as an overflow.
Scope notes in the test: full screens only (modal bottom sheets get an
unbounded height in widget tests -> false huge overflow; they already scroll
their body), and the variety-detail screen is skipped (its ~2px hit is on a
DISPOSED/DEFUNCT RenderFlex — a transient during the cubit rebuild, not the
stable layout).
Diagnosed the recurring 'tests hang' pain by measurement: the logic layer
is fast and reliable (dart test commons_core ~1s/82 tests; flutter test
test/services ~11s/124 tests, green), and only WIDGET tests hang — because
pumpAndSettle() waits for a screen to stop scheduling frames and a live
source (Drift/Nostr stream, periodic Timer, connectivity/plugin) never does,
so it sits at the 10-minute default and the run looks stuck.
- apps/app_seeds/dart_test.yaml sets timeout: 90s so such a test FAILS fast
and names itself instead of blocking the whole run.
- CLAUDE.md now spells out the split-by-layer workflow (logic via
dart test/flutter test test/services; widget tests targeted + timed out;
never pumpAndSettle a live screen).