7.4 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 (NOW): Inventory. Shippable alone, useful with zero network. This is the current target. Block 2 (later): the social leap — offers + messaging + relays + web of trust — big and indivisible; needs its own round (and ideally funding). Do NOT start Block 2 now.
Social layer (for later) uses: Nostr (offers NIP-99, DMs NIP-17), Duniter-style web of trust (Duniter/Ğ1 compatible, ~5 certifications), optional Ğ1 currency (levels: price → wallet deep-link → optional WoT import). Cold-start via real Ğ1 seed groups + fairs. See docs/design/network-trust.md and docs/design/g1-integration.md.
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.