The de-risking spike validated the whole social happy path, so Block 2 moves from "do NOT start" to STARTED. Records the build order (transport foundation in commons_core first, on the vetted pure-Dart nostr lib) and marks open-decisions D.3/D.7 as resolved by the spike.
8.1 KiB
CLAUDE.md — Tanemaki
Context for AI agents (Claude Code) working in this repo. Read this first, then docs/design/open-decisions.md (the live decision log).
What this is
Tanemaki (種まき, "sow/scatter seeds"; short: Tane) — a local-first, decentralized app to manage and share traditional seeds. First app on a generic commons engine. Web: tanemaki.app. Package id: org.comunes.tane. See VISION.md for the human explanation and PLAN.md for the origin analysis (with a "Nota de vigencia" reconciling evolved decisions).
Golden rules (do not violate)
- English for ALL code: identifiers, class/field/column names, comments, commit messages, branch names, and technical specs. No exceptions.
- User-facing strings are NEVER hardcoded — always via i18n. The app is multilingual by design.
- International by design, not Spain-first. Tane targets the whole world, not one country or region. This is not just UI strings: it constrains bundled data and assets too.
- Locale-keyed data, never mono-locale: any catalog/reference data with human-readable names (species common names, vernacular names, units, categories) MUST be a locale-keyed map (
{ "es": [...], "en": [...], "ar": [...] }), extensible to any language — never a single hardcoded language. Starter datasets may ship a small locale set, but the schema, code paths, and copy must never assume es/en only, and comments/notes must not brand the data as "Iberian/Spanish". - RTL is a first-class requirement: layouts, widgets, and tests must work under RTL (Arabic, Hebrew, Persian). Use directional (
start/end) not physical (left/right) insets/alignment; verify withDirectionality. - Scripts & fonts: bundled fonts must cover the scripts we claim to support (Latin + at least RTL/Arabic and CJK as they land); don't hardcode a Latin-only font stack.
- OCR / language models: bundled OCR traineddata and any language resource are pluggable per locale, not fixed to
eng/spa. Match the data pack to the user's locale(s); the current es/en starter set is a seed, not the ceiling. - No locale-specific assumptions in formatting, sorting, dates, numbers, name order — go through
intl/ICU, never ad-hoc.
- Locale-keyed data, never mono-locale: any catalog/reference data with human-readable names (species common names, vernacular names, units, categories) MUST be a locale-keyed map (
- No plaintext at rest, ever — the inventory DB is encrypted (SQLCipher). No plaintext logs/temp/caches. See
docs/design/security-privacy.md. - Local-first: everything works offline, no account, no central server. Online only enriches (degrades gracefully).
- Progressive disclosure: only
Variety.labelis mandatory; every other field optional. Audience is everyone 10–80; simple by default, depth on demand. Never build "two modes". - Human words in the UI, never tech jargon. User-facing copy speaks by intent (what it does for the person), not by mechanism. File formats and internals (CSV, JSON, HLC, CRDT, LWW, UUID…) are implementation details and MUST NOT appear in labels, subtitles, dialogs or messages. E.g. "Save a backup" / "Restore a backup" (not "Export JSON"); "Export to a spreadsheet" (a spreadsheet is a human concept; "CSV" is not). Format-named i18n keys (
exportJson) are fine — they're code; only their values must be human. When a feature is genuinely for power users, reframe it or bury it under progressive disclosure — don't leak the jargon. - License: AGPL-3.0. Keep new deps compatible.
- Tests, near-TDD. Every behavior is covered by automated tests (unit / widget / integration); do NOT rely on manual testing. Write tests first for domain logic (
commons_coreis pure Dart — ideal for TDD). Nothing merges without tests covering the new behavior; CI gates it. Seedocs/design/testing.md. - Discussion/design docs may be in Spanish; code and specs in English.
Stack & structure
- Flutter/Dart, monorepo via pub workspaces. Target Android/iOS/desktop.
- Drift (SQLite) + SQLCipher for the encrypted local DB. Versioned step-by-step migrations, schemas exported to
drift_schemas/, migration tests in CI. Seedocs/design/data-model.md§5. - i18n from day one (e.g.
flutter_localizations+intl/slang).
tane/
pubspec.yaml # pub workspace root
packages/
commons_core/ # identity, Party/Group, TrustEdge, Offer, Pledge, transport (Nostr)+CRDT+sync, geohash discovery. NO seed specifics.
apps/
app_seeds/ # Tanemaki: Variety/Lot/Movement/Species, germination, plant-family units, UI, i18n, commons_ui (embedded for now)
Dependency direction: app_seeds → commons_core, never the reverse. Boundary rules in docs/design/core-domain-boundary.md.
Data model (decided, schemaVersion = 1)
Three levels: Variety (identity/accession) → Lot (a batch you hold: harvest_year, quantity, offer_status, germination) → Movement (append-only event log = history + provenance DAG + Plantare). Plus Species (bundled Wikidata/GBIF catalog), VernacularName, Attachment, ExternalLink, GerminationTest, Party, SeedBank, Offer, Plantare. Every mutable row: client UUIDv7, created_at, HLC updated_at, last_author, is_deleted (soft delete/tombstone), schema_row_version. CRDT: LWW scalars, OR-Set collections, grow-only Movement. Full spec: docs/design/data-model.md.
Decided schema details: Quantity is a shared value type (Lot + Movement); offer_status only on Lot; category free text prefilled from Species.family. Quantity units are plant-aware (cob/pod/ear/head…), not just kitchen units (§2.3.1). A Variety can hold multiple Lots with different years AND units at once.
Identity & crypto (decided)
- One identity = a Duniter/Ğ1-style root seed (does profile, certifications, Ğ1). From it, deterministically derive a secp256k1 subkey for Nostr (messaging/offers) — because Duniter has no messaging. User backs up ONE thing (the seed, as a printable recovery QR).
- At-rest DB key = a separate internal symmetric key in the OS keystore (unlocked by device biometric/PIN); NOT a user password. Backups also encrypted; key travels via the QR. See
docs/design/backup-and-recovery.md.
Phasing — build order
Block 1: Inventory. Shippable alone, useful with zero network. Delivered (beta).
Block 2 (STARTED — the social leap): offers + messaging + relays + web of trust — big and indivisible. The de-risking spike (docs/design/spike-block2-findings.md) validated the whole happy path, so the social round is now open. Build order within Block 2: (1) the transport foundation in commons_core — one NostrConnection + three interfaces (OfferTransport/MessageTransport/TrustTransport) + the pure WebOfTrust rule, on vetted libraries — then (2) offers UI, (3) messaging hardening, (4) WoT parameters + cold-start. It is still large: scope honestly, keep it behind the transport interfaces, and don't let it regress Block 1.
Social layer uses: Nostr (offers NIP-99, DMs NIP-17, via the pure-Dart nostr package), Duniter-style web of trust (Duniter/Ğ1 compatible, ~5 certifications), optional Ğ1 currency (levels: price → wallet deep-link → optional WoT import). Identity: a secp256k1 Nostr key derived (HKDF, domain-separated, one-way) from the Ğ1 root seed — user backs up ONE seed. Cold-start via real Ğ1 seed groups + fairs. See docs/design/network-trust.md, docs/design/g1-integration.md, and the spike findings above.
Where things live
- Live decision log:
docs/design/open-decisions.md— check before deciding anything. - First code steps:
docs/design/first-sprint.md. - Prior art / valuable mockups:
docs/mockups/(inventory, item, search, profile, chat — the UI spec). - Git: bare at
~/repos/tane.git; working clone here. Commit messages in English.