tane/CLAUDE.md
vjrj 2c42b1022a docs(trust): record the ego-centric pivot and ratings v1
network-trust.md §2 rewritten (ego-centric model, misuse resistance,
honest losses, unburnt Duniter bridge) + new §2.1 for ratings.
open-decisions.md: 2026-07-10 WoT-parameters decision superseded;
two new dated entries (ego-centric trust, ratings v1 with its soft
conversation anchor and the strong-anchor question kept open).
CLAUDE.md Block 2 paragraph updated to match.
2026-07-11 13:17:32 +02:00

9.8 KiB
Raw Blame History

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 with Directionality.
    • 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.
  • 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.label is mandatory; every other field optional. Audience is everyone 1080; 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_core is pure Dart — ideal for TDD). Nothing merges without tests covering the new behavior; CI gates it. See docs/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. See docs/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_seedscommons_core, never the reverse. Boundary rules in docs/design/core-domain-boundary.md.

Prefer workspace-level commands from the repo root — one lockfile, one .dart_tool, both members at once. Verified working:

  • dart pub get (root) — resolves the whole workspace; don't pub get per package.
  • dart analyze (root) — analyzes commons_core and app_seeds together. The default gate.

Testing — split by layer; NEVER run the whole flutter test blind (it's what "hangs"). Diagnosed 2026-07-11: logic tests are fast and reliable; only widget tests hang, because pumpAndSettle() on a screen with a never-settling source (live Drift/Nostr stream, periodic Timer, connectivity/plugin) waits at the 10-min default. The everyday gate:

  • dart test packages/commons_core — pure Dart, ~1s. The TDD workhorse. (dart test can't run app_seeds — it needs flutter.)
  • flutter test test/services — app logic + in-memory DB, no widgets, ~10s, reliable. Put new app logic in plain test() here.
  • Widget tests: run targeted (flutter test test/ui/<file>), always with a timeout so a hang FAILS fast — a global apps/app_seeds/dart_test.yaml sets timeout: 90s; add --timeout 30s when in doubt. Never pumpAndSettle a live screen; use bounded pump(Duration). See the testing-gotchas memory + testing.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 + 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 + N interfaces (OfferTransport/MessageTransport/TrustTransport/RatingTransport) + the pure WebOfTrust engine, on vetted libraries — then (2) offers UI, (3) messaging hardening, (4) trust & reputation polish. 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), ego-centric trust (you vouch for people you've met — kind 30777, one live cert per pair, expiring; your circle = friends-of-friends from YOUR key; no global membership, no bootstrap referents, no user-facing parameters — the global Duniter rule was dropped 2026-07-11 as not internationally viable, see open-decisions.md), Wallapop-style ratings (kind 30778, one live rating per pair, circle-weighted display), optional Ğ1 currency (levels: price → wallet deep-link → optional WoT import remains a possible future enrichment). 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