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.
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.
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.
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.
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.
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.
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.
- 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.
- 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.
- 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.
- 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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
The list showed only initials even when a variety had a photo (quick-add stores
one as an encrypted BLOB). Now the avatar shows the first photo, falling back to
the initial — matching the "foto/inicial" list spec.
- VarietyListItem carries the first photo bytes; watchInventory loads them in a
single query per emission.
- List tile renders a MemoryImage avatar when present.
Tests: repository (photo bytes surfaced, null when none) and a widget test
(avatar replaces the initial). Full suite: 37 passing, 0 skipped.
Record germination tests on a lot and surface the result.
- GerminationEntry model with a derived rate (germinated / sample). VarietyLot
carries its tests (most-recent first) and exposes latestGerminationRate.
- VarietyRepository.addGerminationTest; watchVariety now also re-emits on
germination-test changes, so the detail refreshes live.
- Detail UI: each lot shows a germination % badge and a grass action that opens
a sheet listing past tests and adding a new one (germinated + sample size).
i18n strings (ES/EN).
Tests: derived-rate + reactivity at the repository, and a widget test for the
record-test → badge flow. Full suite: 35 passing, 0 skipped.
Add a small curated catalog of Iberian horticultural species and let a variety
be linked to it from the edit sheet.
- assets/catalog/species.json: 14 species with botanical family and ES/EN
common names (wikidata_qid/gbif_key deferred to the varilla enrichment).
- SpeciesRepository: idempotent seedBundled (is_bundled rows, keyed by
scientific name) + search by scientific/common name with a locale-best label.
Seeded on startup from DI.
- VarietyRepository.linkSpecies: sets species_id and prefills category from the
species' family when empty (never overwrites an existing category).
VarietyDetail now carries the scientific name.
- Edit sheet gains a live species-search field; the detail view shows the
scientific name (italic). i18n strings added (ES/EN).
Tests: catalog parse, idempotent seeding, search by scientific/common name,
linkSpecies prefill semantics, and a widget test for the autocomplete → link →
scientific-name-shown flow. Full suite: 32 passing, 0 skipped.
Tapping an inventory item now opens its detail (the /variety/:id route was a
placeholder). Read + edit one variety end-to-end:
- Detail view: photo, category, "also known as" names, notes and lots, driven
by a reactive VarietyDetailCubit.
- Edit core fields (label/category/notes, LWW) via a bottom sheet.
- Add a lot (harvest year + quantity) — the list refreshes reactively.
- Soft-delete (tombstone) with a confirm dialog.
Repository:
- watchVariety(id): merges Drift table-change streams (variety, lots, names,
attachments) via StreamGroup and reloads the full detail on any change, so a
lot added to a *different* table still refreshes the view.
- updateVariety (LWW), addLot, softDeleteVariety.
i18n: detail/edit/addLot strings (ES/EN); switched slang to {param} braces
interpolation (translator- and Weblate-friendly).
Tests: repository (reactive watchVariety, update, soft-delete) and widget tests
(render, edit updates title, add-lot appears, delete shows not-found). Full
suite green: 23 passing, 1 skipped (SQLCipher-only encryption guard).
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.