Sender-side offline delivery hardening.
- OfferOutbox: a keystore-backed (no plaintext) queue of lot ids to publish
later. Stores ids, not offers, so on flush each offer is rebuilt from the
lot's CURRENT state — an offline edit is respected, a deleted lot drops out.
- flushOutbox(): on reconnect, republishes queued lots and clears them (no-op
offline; drops ids whose lot is gone).
- Market screen: sharing offline parks the lots ('we'll share these when you're
connected'); opening the market online auto-flushes the queue first.
- Wired via DI + TaneApp(outbox); i18n en/es/pt.
Tests: 4 outbox + 3 flush + market UI, 20 green. Analyzer clean for new code.
A silent safety net against in-app data loss or DB corruption, with zero
friction. Driven off the app lifecycle (startup + when hidden), it writes a
sealed .tanemaki copy into the app's private storage once a week, keeping the
newest 3 (rotating). Reuses the manual backup's encryption via the extracted
ExportImportService.buildSealedBackup(); never throws, so a failed copy can't
crash startup or backgrounding. Not registered on web (no file storage).
Settings shows a quiet reassurance line with the date of the last automatic
copy (localised via intl), hidden where no automatic backup exists.
Persistence + wiring the offers screen needs, per the product decisions:
- SocialSettings (keystore-backed, no plaintext): coarse area geohash +
configurable relay URLs, both empty by default (no bundled public relays —
don't leak metadata to third parties). Registered in DI.
- createOffersCubit(): opens a SocialSession on the first configured relay when
online, else a null-transport cubit that degrades gracefully. OffersCubit now
closes the owning session on dispose.
4 settings tests green; offers/social suites still green.
Third slice: bridge Block 1's root seed to the Block 2 transport foundation.
- SocialService: derives the secp256k1 Nostr identity from the SAME root seed
the recovery QR backs up (no extra backup), exposes npub/pubkey, and opens a
SocialSession = one shared connection carrying all three transports.
- injector.dart: registers SocialService in get_it. Local-first — derivation is
cheap + offline; no relay is contacted at startup. Replaces the old
"nodeId slice of the seed is the social pubkey" placeholder; keeps the CRDT
author id distinct (unifying it would rewrite authorship — a later decision).
- Test: deterministic identity from the seed hex, matches commons_core, rejects
malformed hex. 5 tests green; app_seeds analyzes clean (0 errors).
'Save a backup' now writes a sealed .tanemaki file (AES-256-GCM under a
key HKDF-derived from the root seed; format TANEBK v1, open and stable).
Restoring on the same identity is silent; a copy sealed by another
identity asks for the printed recovery code (TANE1 base32, typo-tolerant)
and adopts that seed — recovering your bank recovers you. Legacy plain
exports still restore. Settings gains a recovery sheet (QR + code PDF).
BackupBox/RecoveryCode live in commons_core, pure Dart, TDD.
The local half of the original Fase 2 (no network, no Offer entity yet):
- Lot sheet gains a 'do you share it?' choice (gift/swap first, sale last,
never the default); declaring plenty of abundance reveals it with a nudge.
- Lot lines and list tiles badge shared batches; an 'I share' filter and
a printable PDF catalog (paper bridge for fairs) round out the view.
- Catalog PDF embeds DejaVu (Latin-ext/Cyrillic/Greek); fonts are a
per-locale resource like the OCR packs.
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.