Commit graph

315 commits

Author SHA1 Message Date
0e6ef13d00 fix(ui): pin edit-sheet Save button as a fixed footer
The variety edit sheet grew with v8 fields (organic, needs-reproduction,
crop calendar) and its Save button, sitting at the bottom of the
SingleChildScrollView, dropped below the fold — the species-linking
widget test could not reach it (tap landed off-screen, species never
linked). Move the fields into a Flexible scroll area and pin the
Cancel/Save row as a fixed footer so Save is always visible and tappable.

Full suite green (233 tests).
2026-07-10 02:19:07 +02:00
d433748ab5 spike(block2): de-risk web of trust — the last big unknown
Adds TrustTransport as the third interface on the shared NostrConnection,
completing the social-layer happy path in the spike:

- WebOfTrust: pure, Flutter-free Duniter membership rule (N certs from members +
  within distance D of bootstrap referents), iterated to a fixpoint. The
  commons_core-worthy piece; TDD'd (threshold, distance cutoff, transitive
  growth, expired/revoked/self exclusion).
- NostrTrustTransport: certifications as a custom addressable kind (30777,
  keyed by issuer+subject) — certify renews, revoke replaces, certs expire.
- Trust filters spam (network-trust §2): seeds certify Alice; Eve stays
  uncertified; Bob discovers both offers and annotates authors — Alice known,
  Eve unknown — all over ONE shared connection.

Findings updated: WoT risk High -> Medium (policy/bootstrap, not feasibility);
overall scope Medium-High -> Medium — all three transport contracts now proven.
30 tests green, analyzer clean, Block 1 untouched.
2026-07-10 02:17:35 +02:00
8a81aaf442 spike(block2): de-risk NIP-17 messaging + validate shared-connection shape
Extends the Block 2 spike to attack the highest-risk unbuilt piece (private
messaging) and to prove the Q2 architecture recommendation with running code:

- NostrConnection: one shared socket+key+sign+REQ/EOSE lifecycle. Refactored
  NostrOfferTransport onto it; added NostrMessageTransport — two thin contracts
  on ONE connection (the 'one connection, three interfaces' shape).
- NIP-44 v2 encryption (secp256k1 ECDH -> ChaCha20 + HMAC, length-hiding pad).
- NIP-17/NIP-59 gift-wrap: rumor(14) -> seal(13) -> wrap(1059, ephemeral key).
  Alice->Bob DM round-trips through the relay: encrypted, sender authenticated,
  and metadata-private (relay/eavesdropper sees only ciphertext, an ephemeral
  author and the recipient p-tag — Alice stays hidden).

Findings updated: NIP-17 risk drops Medium-High -> Medium (hardening, not
feasibility); next de-risking target is the web of trust. NIP-44 not yet
vector-verified (noted). 20 tests green, analyzer clean, Block 1 untouched.
2026-07-10 02:11:54 +02:00
733c118c3b chore(i18n): track generated strings.g.dart
The slang aggregator fell out of tracking through merge resolutions while its
per-locale siblings (strings_en/es/pt.g.dart) and database.g.dart stayed
tracked, so it kept showing as an untracked file on main. Track it too for
consistency with the repo's commit-generated-code convention.
2026-07-10 02:09:01 +02:00
2ea8d67cb4 fix(sync): re-seed HLC clock from stored rows so local edits win LWW
The repository clock started fresh at Hlc.zero on every launch and was
never re-seeded from the database. After a restart, if the device wall
clock lagged behind stamps already stored (e.g. rows imported from a
peer whose clock ran ahead), a local edit was stamped *older* than the
row it edited and silently lost last-writer-wins — so lot changes made
on one device did not update when the backup was imported on another.

Re-seed the clock lazily from the newest stored `updatedAt` before the
first local stamp of the session, mirroring what importInventory already
does with the incoming max. _stamp() is now async.

Tests: add a lot-level LWW re-import test (the variety case was covered,
the lot case was not) and a restart-regression test that reproduces the
lagging-wall-clock scenario.
2026-07-10 02:06:01 +02:00
46ba85dad0 Merge branch 'claude/cool-swanson-81a747': expand species catalog to ~1200 edible species
# Conflicts:
#	apps/app_seeds/assets/catalog/species.json
#	apps/app_seeds/lib/data/species_repository.dart
2026-07-10 02:04:51 +02:00
959fa2551c feat(species): expand catalog to ~1200 edible species from Wikidata
Grow the bundled species catalog from 14 hand-curated entries to ~1200
edible/cultivated species, internationalized in 13 languages (es, en, fr,
de, it, pt, ca, gl, eu, ar, zh, ja, ru — Latin + Arabic RTL + CJK + Cyrillic).

- Add a reproducible generator (tool/gen_species_catalog.dart) that queries
  Wikidata (CC0, no attribution burden) in two phases, filters out
  non-vernacular noise (author citations, ranks, initials) and applies a
  relevance floor, then merges hand-curated, authoritative core-crop data
  (tool/curated_overrides.json: names, family, viability_years). GBIF is used
  only as an identifier. The generated species.json (v3) is committed.
- Carry wikidata_qid and gbif_key through the parse/seed pipeline; the columns
  already existed, so no DB migration.
- Rewrite seedBundled to one read + one batch (was a SELECT per species on
  every startup — a real cost at ~1200 rows) and keep it idempotent with
  backfill of the new reference fields.
- Move species search filtering to SQL (LIKE) so a large catalog is not pulled
  into memory on every keystroke.
- Cover the generator transform, the generated asset, and the new fields with
  tests.
2026-07-10 02:00:47 +02:00
32ddaf1714 spike(block2): de-risk social layer — key derivation, OfferTransport, NIP-99 round-trip
Throwaway research spike (spike/ branch, outside pub workspace, no prod deps
touched). Validates the open Block 2 decisions before committing a funded round:

1. Deterministic one-way secp256k1 (Nostr) key derived from the Ğ1 root seed
   via HKDF — user still backs up ONE thing. Reproducible + signs (BIP340).
2. OfferTransport abstraction over Nostr NIP-99 (kind 30402); Offer stays
   inventory/location-agnostic, geohash coarsened on the wire (tested).
3. publish->discover-by-geohash proven end-to-end against an in-process
   hermetic mini-relay (~34ms round-trip).

Findings + risks + recommendation in docs/design/spike-block2-findings.md.
Block 1 suite untouched. 14 tests green, analyzer clean.
2026-07-10 01:57:25 +02:00
6eb1517ffb Merge branch 'main' into claude/trusting-shannon-ff64dd
# Conflicts:
#	apps/app_seeds/lib/i18n/strings.g.dart
2026-07-09 23:56:45 +02:00
4acf227e30 chore(release): beta packaging — signing, changelog, store metadata, scale test
- Android release signing reads a gitignored key.properties, falling back
  to debug keys so contributors can still build --release.
- CHANGELOG.md (human, newest-first) + Fastlane/F-Droid metadata (en/es)
  with descriptions derived from docs/intro.md.
- docs/release.md: the by-hand build + manual smoke checklist (no CI yet).
- inventory_scale_test guards the list query against N+1 at 3000 varieties.
2026-07-09 23:10:01 +02:00
5d2b41a110 i18n: RTL smoke tests, Portuguese locale and a translation guide
- Add rtl_smoke_test covering inventory, detail (lot sheet) and settings
  under Directionality.rtl, with an Arabic label and vernacular name.
- Ship pt.i18n.json (Portuguese) as proof the locale pipeline scales beyond
  es/en; wire its language tile in Settings.
- docs/i18n.md documents the Weblate-ready flow for adding locales.
2026-07-09 23:06:49 +02:00
fde0ea6eab feat(species): derived verified reference links (GBIF/Wikipedia/Wikispecies)
Add a read-only "Learn more" section on the variety detail that surfaces
verified external references for the linked species, built deterministically
from bundled identifiers — no titles bundled, no network, offline-first:

- GBIF taxon page from the backbone key
- Wikipedia via Wikidata Special:GoToLinkedPage (locale-aware, RTL-safe,
  falls back to the Wikidata item when a language lacks the article)
- Wikispecies from the scientific name

Enrich the bundled catalog (species.json v3) with gbif_key + wikidata_qid for
all 14 species, cross-checked (each QID carries GBIF ID P846 = the key).
Schema already had the columns; seedBundled now persists and backfills them.

The grower's own manual links stay but are demoted (progressive disclosure):
the section only shows when links exist and "Add link" is a low-emphasis
button, since hand-adding a link is the rarer power move.

Pure-Dart link builder is unit-tested; repo persistence/backfill and the
detail-screen section are covered too.
2026-07-09 22:44:12 +02:00
d6781870d9 feat(backup): sealed backups + printable recovery sheet
'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.
2026-07-09 22:32:13 +02:00
ba87bf2719 feat(inventory): local sharing — per-lot share terms, filter and printable catalog
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.
2026-07-09 22:19:55 +02:00
e3ec855630 style: dart format reflow (no behavior change) 2026-07-09 22:19:43 +02:00
cf5d302cc1 test(state): cover InventoryCubit and VarietyDetailCubit; refresh block1 status doc 2026-07-09 22:03:36 +02:00
a96d82add9 test(ui): scroll to the save button in the taller edit sheet; fix doc lint 2026-07-09 21:58:43 +02:00
383eb3ba37 ci: manual Windows desktop build job (allow_failure) 2026-07-09 21:48:14 +02:00
3db8ca558c docs: public intro narrative and in-app onboarding copy 2026-07-09 21:48:14 +02:00
1f6a3282c1 docs(agents): codify international-by-design and human-words golden rules 2026-07-09 21:48:14 +02:00
7f9f5c065b fix(inventory): merge draft + list watch into one subscription
InventoryCubit subscribed to watchInventory() and watchDrafts()
separately — two overlapping StreamGroups (both watching attachments)
re-emitted in a tight loop that starved the event loop and hung widget
tests' pumpAndSettle for 10+ minutes (home/inventory/quick-add all
timed out). Replace both with a single watchInventoryView() stream that
loads the catalogued list and the draft tray from one StreamGroup.

Full widget suite no longer hangs: 165 pass. One unrelated failure
remains in the v8 variety edit sheet (Save button pushed off-screen by
new fields).
2026-07-09 21:33:48 +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
12a2ee2d64 test(ui): scroll to the About tile now that Backup pushes it below the fold 2026-07-09 14:49:15 +02:00
d3711e39b0 feat(quick-add): save & add another for rapid manual entry
The quick-add sheet no longer closes on every save: a new 'Save & add
another' action saves the seed, clears the form (keeping the chosen lot
type), refocuses the name field and shows an 'N added' running count, so
a whole shelf can be entered without reopening the sheet each time.
Plain Save still closes it. Adds a dedicated label field with its own
controller, submitAndAddAnother() on the cubit, and cubit + widget tests.
2026-07-09 14:45:48 +02:00
89b61bc9e4 feat(inventory): additive CSV import for bulk digitization
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).
2026-07-09 14:41:28 +02:00
21072633b5 fix(ui): darken seedMuted and app-bar green to meet WCAG AA contrast
seedMuted (hints, card subtitles, search placeholder) was #8A9880 —
2.62:1 on the canvas and 3.05:1 on white, failing AA for the 13px text
it colours. Darkened to #617057: 5.30:1 on white, 4.56:1 on canvas,
4.88:1 on the field fill, same muted green-grey hue.

The new contrast test also caught the app-bar green: white titles on
#3E8C38 were 4.19:1, so it is nudged to #3A8434 (4.64:1) — a barely
visible change.

test/ui/theme_contrast_test.dart pins the WCAG relative-luminance math
and asserts every text/background pair the palette actually uses.
2026-07-09 12:50:26 +02:00
2812c99280 feat(inventory): CSV/JSON export and JSON import with LWW reconciliation
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.
2026-07-09 12:46:53 +02:00
136ed701a7 fix(linux): set GTK window icon from bundled app icon 2026-07-09 12:39:29 +02:00
c9e93f7e27 feat(ui): About in drawer; higher-contrast app icon & splash
Add an "About" entry to the navigation drawer (below Settings) so the
About screen is reachable without opening Settings first.

Recolour the launcher icon and native splash to a white sprout on brand
green (#2F7D34) — the previous dark-green-on-light-green had almost no
contrast. Regenerate Android, iOS and web icons and the light/dark splash.
2026-07-09 12:18:37 +02:00
9558115c1e feat(inventory): filter by category and lot form + a11y polish
Add filter chips above the inventory list, one per category in use and
one per lot form actually held; results apply search ∧ category ∧ form.
An active filter that empties the list shows a distinct 'no matches'
state and a clear-filters button. VarietyListItem now carries the set of
lot forms it holds (one aggregate query), and the stream re-emits on lot
changes.

Accessibility: category headers use the text theme so they honour the
system font scale; the list avatar is excluded from semantics (the tile
title already announces the name); the edit action uses the green action
colour (≥3:1 on the canvas, up from 2.62:1). Add category/form/clear
filter tests and a tap-target-size guideline test.
2026-07-09 12:00:34 +02:00
42c16c0e3f feat(ui): Material 3 redesign, cover-photo viewer, About screen
Apply a Material 3 seed-green palette and rounded components across the
home, inventory, quick-add, quantity picker and variety-detail screens.
The full-screen photo viewer can now set any photo as the cover (via
attachment sort order) and delete after confirmation. Lot forms and
packaging surface as choice chips.

Extract About out of Settings into its own screen (app version via
package_info_plus, website link). Add es/en strings for the new lot
types, presentation, home tagline and About. Update Seedees v2 mockups.
2026-07-09 11:51:59 +02:00
f5c36f2369 chore(app): launcher icons, native splash, and web platform
Add flutter_launcher_icons + flutter_native_splash config (green sprout
on the #E7F1E0 canvas, light & dark) and regenerate Android/iOS assets.
Enable the web platform (favicon, manifest, PWA icons). Add
package_info_plus for the version string shown on the About screen.
2026-07-09 11:51:38 +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
06aed2a586 fix(ui): enable mouse/trackpad drag scrolling on desktop
Flutter disables pointer-drag on scrollables/PageViews on desktop by default,
so the small photo carousel (and lists) couldn't be swiped with a mouse. Add an
app-wide ScrollBehavior that includes mouse/trackpad/stylus drag devices.

54 tests green; Linux runs.
2026-07-08 14:18:10 +02:00
c789cd1027 feat(photos): camera-or-gallery picker + swipeable full-screen viewer
- Adding a photo (quick-add and detail) now offers a Camera / Gallery chooser
  (shared pickPhoto helper); camera errors on desktop degrade gracefully.
- Full-screen viewer: pinch-to-zoom pans only while zoomed, so a plain
  horizontal swipe pages between photos again.

54 tests green; Linux runs.
2026-07-08 14:15:13 +02:00
4b209d6752 feat(shell): real sprout logo on home + basic Settings screen
- Bundle the real sprout logo (docs/logo.png → assets/logo.png) and show it on
  the home menu instead of a Material icon.
- Settings screen: switch language (Español / English / system) via slang, plus
  an About tile (logo, app name, one-line description). Drawer "Settings" is now
  active and routes to /settings.

Tests: drawer Settings opens the screen and shows the language options. 54 green.
2026-07-08 14:05:12 +02:00
46ef2c61cd fix(inventory): tappable photo dots + non-truncated quantity scale
- Photo carousel dots are now tappable (jump to that photo) with a larger hit
  area, so you can browse all images even when swiping the small thumbnail is
  awkward.
- The quantity unit scale is a Wrap instead of a horizontal scroll, so every
  unit stays visible instead of being cut off.
2026-07-08 13:59:21 +02:00
a69047326e feat(inventory): seed/plant clarity + full-screen photo viewer
- Each lot shows a Seeds/Plantel chip (green vs teal, with icon) so mixed
  seed-and-plant varieties read clearly at a glance.
- Germination is hidden on plant lots (it only applies to seeds).
- Tapping a photo in the detail carousel opens a full-screen, swipeable,
  pinch-to-zoom viewer.

Tests: plant lot shows the Plants chip and hides germination. 53 green; Linux runs.
2026-07-08 13:52:56 +02:00
b23f4f1c77 feat(inventory): external links on the variety detail
Add Wikipedia/forum-style URLs to a variety:
- Repository: addExternalLink/removeExternalLink (soft delete); watchVariety
  loads links and re-emits on external_links changes; ExternalLinkItem model
  (title preferred over URL for display).
- Cubit: addExternalLink/removeExternalLink.
- Detail: a "Links" section with tappable link chips (open via url_launcher,
  http(s):// prepended if missing), delete per chip, and an "Add link" dialog
  (URL + optional title).

Tests: repo add/remove + widget add/remove. 52 green; Linux runs.
2026-07-08 13:38:31 +02:00
02a9d5bf39 feat(shell): home menu + navigation drawer (mockups 02/03)
The app now opens on a main menu instead of straight into the list:
- HomeScreen (mockup 02): sprout logo over a faint seed-glyph watermark, with
  big "Your inventory" (→ /inventory) and "Open market" (Block 2, "coming soon")
  destinations.
- AppDrawer (mockup 03): Inventory is live; the social items (market, profile,
  chat, wishlist, following, settings) are shown disabled with a "coming soon"
  tag so the roadmap is visible. Drawer is available on home and inventory.
- Routing: / → home, /inventory → list, /variety/:id → detail.

Tests: home shows the menu + navigates; drawer opens + navigates. quick-add flow
updated to go home → inventory. 50 green; Linux runs.
2026-07-08 12:22:42 +02:00
17073dd560 feat(inventory): photo gallery + reactive avatars (+ harvest month/year)
Photos:
- Repository addPhoto/removePhoto; VarietyDetail.photos (list); loads all photos.
- Detail: a 140×140 photo carousel with page dots, delete-current, and add-photo
  (gallery); empty state shows an add-photo button.
- watchInventory now re-emits on attachment + species changes (StreamGroup), so
  a photo added on the detail screen refreshes the inventory avatar immediately
  (fixes avatars not updating).

Also carries the concurrent harvest-date work: lots gain an optional harvest
month (schemaVersion 3 + from2To3 migration, drift_schema_v3 exported), a
month/year harvest picker, and localized month names.

48 tests green (add/remove photos, avatar reactivity, v1→v3 migration); Linux runs.
2026-07-08 12:12:51 +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
9e5c82184b feat(inventory): editable vernacular (common) names
The "also known as" names were read-only; now they can be added and removed:
- Repository: addVernacularName + removeVernacularName (soft delete);
  VarietyDetail.vernacularNames carries ids (new VernacularName model).
- Cubit: addVernacularName / removeVernacularName.
- Detail: deletable name chips + an "Add name" chip opening a small dialog;
  the section is always visible so names can be added when empty.

Tests: repo add/remove + widget add/remove. 44 green.
2026-07-08 11:20:36 +02:00
f8d4d9a778 feat(inventory): edit and delete existing lots
Lots could only be created; now they can be edited and removed:
- Repository: updateLot (LWW) + softDeleteLot (tombstone).
- Cubit: updateLot / deleteLot.
- The add-lot sheet is now a shared add/edit sheet — tapping a lot opens it
  prefilled (type, year, quantity), with a trash action to delete. The reactive
  detail view refreshes on save/delete.

Tests: repo updateLot/softDeleteLot + widget edit-lot and delete-lot. 42 green.
2026-07-08 11:10:43 +02:00
7e92410728 refine(quantity): drop handful, reorder scale, potted-plant icon, year dropdown
Feedback from the running app:
- Remove "handful" from the scale (redundant with spoon); keep the enum value.
- Reorder so "packet" (sobre) follows "spoon" (cuchara).
- Use the Material Symbols potted-plant for the plant lot type and plant unit
  (adds material_symbols_icons).
- Harvest year is now a dropdown of recent years instead of free text.

38 tests green; Linux build runs with the new icon font bundled.
2026-07-08 11:03:57 +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
48e9d15772 feat(ui): align inventory + detail with mockups (seed icons, green theme)
Visual pass toward docs/mockups (06 inventory, 07 item):
- Bundle the seedks icon font (chars a–k → seed pictograms: jar, packet, spoon…)
  and a SeedGlyph/QuantityKindIcon helper.
- Green app bar + pale-green canvas theme (buildTaneTheme).
- Inventory list: rounded-square photo thumbnail or green initial avatar, bold
  title, scientific-name subtitle, trailing edit pencil, styled category
  headers, rounded search field, seed-glyph empty state.
- Quantity selectors (quick-add + add-lot) show the seed pictogram + word;
  detail lot lines use the kind's glyph.
- Detail header restyled to mockup 07: category link + scientific name on the
  left, photo on the right.

watchInventory now also returns the linked species' scientific name (subtitle).
37 app tests green; seedks font verified in the Linux asset bundle.
2026-07-08 00:14:43 +02:00
e41ec9872b fix(db): load bundled SQLCipher on Linux desktop (stop startup segfault)
The Linux app segfaulted when opening the encrypted DB. Root cause: on Linux we
overrode package:sqlite3 to the SYSTEM libsqlcipher.so, but sqlcipher_flutter_libs
statically links its own SQLCipher into libsqlcipher_flutter_libs_plugin.so and
exports the sqlite3_* symbols globally. Two SQLite/SQLCipher builds loaded in one
process → symbol clash → segfault at sqlite3_open (before PRAGMA key).

Fix: _openLinuxCipher() now prefers the ABI-matched bundled plugin .so
(libsqlcipher_flutter_libs_plugin.so), falling back to the system lib only if the
plugin isn't present (e.g. host test/CI layouts). Android is unaffected (uses the
bundled Android lib as before).

Verified: Linux desktop build runs without crashing; full suite 37/37, and the
"no plaintext at rest" test now runs (not skipped) on the dev host with system
libsqlcipher, confirming real encryption.
2026-07-07 23:45:09 +02:00
c4b80ab4a9 fix(db): drop sqlite3_flutter_libs to stop SQLCipher symbol-clash segfault
drift_flutter pulled sqlite3_flutter_libs, whose bundled plain SQLite exports
the same sqlite3_* symbols as sqlcipher_flutter_libs. With both loaded the app
segfaulted at startup on Linux desktop when opening the encrypted DB (the app
built and the Dart VM started, then crashed natively — "Lost connection").

drift_flutter was unused (we open the DB via our own encrypted executor), so
removing it drops sqlite3_flutter_libs and leaves SQLCipher as the only native
sqlite provider. 37 tests green; Linux build runs without crashing.
2026-07-07 23:21:05 +02:00
5c607c0f65 Merge Block 1: encrypted offline inventory MVP
Inventory walking skeleton + first slice, variety detail/edit, species
catalog + autocomplete, per-lot germination tests, and photo thumbnails.
flutter_bloc over an encrypted Drift (SQLCipher) source of truth; get_it,
go_router, slang (ES/EN). 37 tests green, encryption verified.

Where we are: docs/design/block1-status.md. Pending Block-1 polish:
vernacular-name editing and per-lot edit/delete. Block 2 (social) not started.
2026-07-07 22:16:38 +02:00