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 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
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 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.
Reusing the backup snapshot for sync had two bugs that would bite real use:
1. Deletions didn't propagate. exportInventory() filters isDeleted=false
(user backups are tombstone-free by design) and the JSON codec dropped
isDeleted (omitted on encode, hardcoded false on decode) — so deleting a
variety on one device never reached the others. Now the codec round-trips
isDeleted (emitted only when true, so backups stay byte-identical and it's
backward compatible), and a new exportForSync() includes tombstones. The
importer already merges them LWW-correctly.
2. Photos bloated events. The snapshot embeds photo bytes as base64, so a
real inventory would blow past relay event-size limits and sync would
silently fail. exportForSync() omits attachments entirely (photos stay
device-local; media sync is a separate, deferred concern).
buildSnapshot() now uses exportForSync(); backups keep using exportInventory
(photos in, tombstones out) unchanged.
Tests: a deletion replicates as a tombstone; the sync snapshot carries no
photo bytes while a sealed backup still does.
Add multi-select to the inventory and a "Print labels" flow that renders a
PDF sheet of labels (small stickers or large cards), each carrying the seed's
common name, variety, scientific name, year/origin and a tanemaki://seed QR
that holds the seed's data offline (future scan-to-import).
- SeedLabelCodec: compact, versioned tanemaki://seed URI (encode/decode)
- LabelSheetService: pure-Dart PDF grid (pdf + barcode + DejaVu), RTL-aware
- VarietyRepository.labelRows: one row per lot, locale-picked common name
- InventoryCubit selection mode; contextual app bar + label print sheet
- i18n en/es/pt (printLabels.*)
Tests: codec, PDF service (both formats + RTL), labelRows, selection cubit,
inventory widget selection.
Lower the bulk-digitization cliff with two more routes on top of the
already-landed CSV import and "save and add another":
- Photo-first drafts (capture now, catalogue later): burst-capture
photos (camera or multi-gallery) into unnamed draft varieties, shown
in a "to catalogue" tray, hidden from the main list until named.
Adds Variety.isDraft (schema), addDraftVariety/watchDrafts/nameDraft,
the triage sheet and the inventory banner.
- On-device OCR label suggestion (Tesseract, offline, no Google): a
"Suggest name from photo" button in the naming dialog behind a
LabelTextExtractor interface (Tesseract on Android/iOS, no-op
elsewhere). Reads the largest print via hOCR bounding boxes, drops
boilerplate/low-confidence noise, preprocesses (grayscale, contrast,
upscale) and sweeps rotations (0-315 deg) so tilted packets still
read. Bundles tessdata_fast eng+spa; validated on-device against real
packets. The photo is written to a temp file deleted immediately in a
finally block (the plugin needs a path) - a bounded, documented
exception to no-plaintext-at-rest.
This commit also carries the co-developed schema evolution v5 to v8 that
shares these files (organic flag, species viability years, crop
calendar, lot provenance/abundance/preservation format, condition
checks) plus their exports/migrations and i18n.
Tests: CSV/draft/OCR unit + widget + migration green in isolation.
Note: the full widget suite currently hangs (>10 min) - under investigation.
Import a spreadsheet list (the same 21-column schema the CSV export
already produces) as new varieties/lots. Tolerant reader: columns
matched by header name in any order, only variety_label required,
unknown enums fall back to a safe default, rows sharing variety_id
collapse into one variety with several lots. Species re-linked by
scientific name. Additive by design (fresh ids + HLC), so it never
overwrites existing rows; JSON stays the canonical id-reconciling import.
Adds a hand-rolled RFC 4180 parser (no new dependency), an importCsv
path in VarietyRepository/ExportImportService, an 'Import from CSV'
tile in the backup section, and en/es strings.
Note: i18n .g.dart also carries pre-existing About/intro strings from
the working tree (regenerated by slang).
Interchange export/import for Phase 1 (data-model §7):
- JSON: canonical, versioned envelope (formatVersion 1) with all sync
metadata verbatim, photos embedded as base64, tombstones excluded.
Species are re-resolved on import by scientific name (catalog ids are
per-install). Reader tolerates unknown fields/enum values (§5.2) and
rejects newer format versions with a clear error.
- CSV: export-only spreadsheet flatten, one row per lot, RFC 4180
escaping, never any photo bytes.
- Import merges by UUIDv7 id in one transaction: insert-if-unknown
preserving original stamps, last-writer-wins by packed HLC for known
mutable rows, append-only movements; afterwards the local clock
receiveEvent()s the newest imported stamp so it never runs behind.
- Settings gains a Backup section (export CSV/JSON, import JSON with
confirmation); file dialogs behind a FileService interface backed by
file_picker (MIT).
- Tests: codec round-trip and tolerance, reconciler LWW, repository
export→import round-trip (fresh DB, idempotent re-import, newer-local
wins, clock monotonicity, species re-resolution), backup widget flows.