feat(app): ask 'how did it do?' when a harvest is noted (schema v13)

The note growers most wish they had two seasons later — did it do well,
was it worth keeping — never had a home. New append-only GardenOutcomes
table (lotId, season year, three-face rating, free note); the question
appears ONCE, inline, right after recording a harvest in the batch story,
and is skippable without a trace. The answer shows as one more story
line. Rides backups/sync like every mutable table (JSON codec, LWW
import, HLC clock absorb). Migration v12→v13 guarded + verified from
every historical version.
This commit is contained in:
vjrj 2026-07-18 12:18:25 +02:00
parent 92fd84590b
commit bb5b3d4a43
20 changed files with 6030 additions and 15 deletions

View file

@ -79,6 +79,80 @@ void main() {
await disposeTree(tester);
});
testWidgets('harvesting asks "how did it do?" once, a face tap saves it', (
tester,
) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Maize');
final lotId = await repo.addLot(varietyId: id);
await tester.pumpWidget(
wrapDetail(
repository: repo,
varietyId: id,
species: newTestSpeciesRepository(db),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(Key('lot.history.$lotId')));
await tester.pumpAndSettle();
// Sowing does NOT ask; harvesting does.
await tester.tap(find.byKey(const Key('history.sowToday')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
await tester.tap(find.byKey(const Key('history.harvestToday')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('history.outcomeQuestion')), findsOneWidget);
await tester.enterText(
find.byKey(const Key('history.outcomeNote')),
'true to type',
);
await tester.tap(find.byKey(const Key('history.outcomeGood')));
await tester.pumpAndSettle();
// Question gone, answer saved, story shows it.
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
final outcomes = await db.select(db.gardenOutcomes).get();
expect(outcomes.single.rating, GardenOutcomeRating.good);
expect(outcomes.single.notes, 'true to type');
expect(outcomes.single.year, DateTime.now().year);
expect(find.text('It did well'), findsOneWidget);
await disposeTree(tester);
});
testWidgets('the harvest question can be dismissed without a trace', (
tester,
) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Maize');
final lotId = await repo.addLot(varietyId: id);
await tester.pumpWidget(
wrapDetail(
repository: repo,
varietyId: id,
species: newTestSpeciesRepository(db),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(Key('lot.history.$lotId')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('history.harvestToday')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('history.outcomeDismiss')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
expect(await db.select(db.gardenOutcomes).get(), isEmpty);
await disposeTree(tester);
});
testWidgets('a plant lot offers harvest but not sowing', (tester) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Rosemary');