Commit graph

8 commits

Author SHA1 Message Date
cfa1053842 feat(offers): asking price on lots, published with sale offers (schema v11)
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
2026-07-11 08:00:08 +02:00
6de039d518 feat(sales): local sales/purchase ledger (any currency)
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.
2026-07-11 02:33:42 +02:00
81094f25a8 feat(plantare): reproduction-commitment data layer (schema v9)
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.
2026-07-11 02:01:25 +02:00
6809dc6143 feat(inventory): photo-first drafts + on-device OCR (digitization R2+R4)
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.
2026-07-09 21:23:46 +02:00
3954c62aa6 feat(db): lot presentation + attachment sort order (schema v4→v5)
Expand LotType from {seed, plant} to six append-only forms (seed,
seedling, plant, tree, bulb, cutting) and add an optional Presentation
attribute (pot/tray/plug/bareRoot/rootBall) kept separate from quantity
so 'how many' and 'in what packaging' never conflate.

Add attachments.sortOrder so the cover photo is the lowest-ordered one,
enabling reordering from the full-screen viewer. Expand QuantityKind
with the new plant-aware forms.

Migrations are additive (v1→v5 all covered by migration_test); enum
values are stored by name and appended only, keeping CRDT sync safe.
2026-07-09 11:51:21 +02:00
5d4053157f feat(inventory): month/year harvest date picker (schema v3)
Replace the free-text harvest year with a themed month/year picker:
a tappable field opens a green-styled dialog with month and year grids
(navigable, paginated years), month optional. Lots now read e.g.
"September 2021" or just "Year 2021".

- schema v3: add nullable Lot.harvest_month (1..12) + migration + tests
- repo/cubit propagate harvestMonth; localized month names (en/es) via slang
2026-07-08 12:09:38 +02:00
3942975dba feat(inventory): informal quantity scale (number + unit) + seed/plant lots
Rework quantities the way seeds are actually described — a number of an informal
unit — and let a lot hold seeds or plants (plantel).

Quantity model (commons_core):
- Quantity is now { kind, count?, label } — "3 cobs", "2 packets", "a few".
  Count is optional; uncountable vibes (a few, some, pinch) carry no number.
- QuantityKind reorganised into an ascending scale: vibes → containers
  (teaspoon/spoon/cup/jar/sack) → seed & fruit forms → plant units, each tagged
  countable/not. Stored by name; count reuses the existing numeric column.

Lot type (schema v2):
- Lots gain a `type` (seed | plant); germination stays meaningful for seeds.
  schemaVersion 2 with a from1To2 migration (adds the column), drift_schema_v2
  exported, migration test covers v1→v2.

UI:
- New QuantityPicker: a horizontal scale of large seed-glyph icons + a number
  stepper (shown only for countable units), plus a seed/plant SegmentedButton.
  Used in quick-add and add-lot. Lot lines render "3 cobs" / "a few".
- i18n: unit singular/plural (ES/EN, Weblate-friendly), lot-type labels.

Build: slang moved to its CLI (disabled in build_runner to avoid a multi-file
collision); CI runs `dart run slang` before build_runner. 38 tests green,
Linux migrates the existing v1 DB and runs.
2026-07-08 10:51:02 +02:00
040f15a898 feat(block1): inventory walking skeleton + first quick-add slice
Stand up the Tanemaki monorepo and the first end-to-end vertical slice of
Block 1 (offline, encrypted inventory): add a seed → see it in a categorized,
searchable list → it persists → reopen and it's still there.

Architecture (state management like G1nkgo, adapted to Tane's reality):
- flutter_bloc (Cubit-first), but the encrypted Drift DB is the single source
  of truth; cubits stream from repositories (no hydrated_bloc/Hive, which would
  write plaintext at rest).
- get_it composition root; go_router; slang i18n (ES/EN, Weblate-friendly JSON).

Workspace & core:
- pub workspace: packages/commons_core (pure Dart) + apps/app_seeds (Flutter).
- commons_core primitives: UUIDv7 IdGen, Hybrid Logical Clock, Quantity value
  type (+ plant-aware QuantityKind), IdentityService root-seed stub.

Data & security:
- Drift schemaVersion=1 with all 10 Block-1 tables + common CRDT columns
  (HLC updated_at, last_author, tombstones); Movement append-only.
- SQLCipher via an injectable executor that refuses to open a plaintext DB;
  DB key + root seed in the OS keystore (separate secrets).
- Exported drift_schema_v1.json + migration scaffold.

Tests (near-TDD; nothing merges without tests):
- commons_core units (24), Drift migration test, "no plaintext at rest"
  security guard (runs where SQLCipher is present, skips otherwise),
  repository, widget, full quick-add flow, file-reopen persistence, and a
  no-hardcoded-strings i18n guard. GitLab CI: format + analyze + test + coverage.

Follow-on Block-1 stories (item detail/edit, species catalog, germination UI)
and all of Block 2 remain out of scope.
2026-07-07 15:16:14 +02:00