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.
This commit is contained in:
vjrj 2026-07-09 22:44:12 +02:00
parent ba87bf2719
commit fde0ea6eab
14 changed files with 484 additions and 46 deletions

View file

@ -373,6 +373,56 @@ void main() {
await disposeTree(tester);
});
testWidgets(
'a linked species with identifiers shows verified reference links',
(tester) async {
final repo = newTestRepository(db);
final species = newTestSpeciesRepository(db);
await species.seedBundled(const [
SpeciesSeed(
scientificName: 'Solanum lycopersicum',
gbifKey: 2930137,
wikidataQid: 'Q23501',
),
]);
final match = (await species.search('Solanum')).single;
final id = await repo.addQuickVariety(label: 'Tomato');
await repo.linkSpecies(id, match.id);
await tester.pumpWidget(
wrapDetail(repository: repo, varietyId: id, species: species),
);
await tester.pumpAndSettle();
// The "Learn more" section renders one chip per available source.
expect(find.text('Learn more'), findsOneWidget);
expect(find.byKey(const Key('ref.gbif')), findsOneWidget);
expect(find.byKey(const Key('ref.wikipedia')), findsOneWidget);
expect(find.byKey(const Key('ref.wikispecies')), findsOneWidget);
await disposeTree(tester);
},
);
testWidgets('a variety with no species shows no reference section', (
tester,
) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Mystery seed');
await tester.pumpWidget(
wrapDetail(
repository: repo,
varietyId: id,
species: newTestSpeciesRepository(db),
),
);
await tester.pumpAndSettle();
expect(find.text('Learn more'), findsNothing);
expect(find.byKey(const Key('ref.gbif')), findsNothing);
await disposeTree(tester);
});
testWidgets('plant lots show the Plants chip and hide germination', (
tester,
) async {