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

@ -153,6 +153,23 @@ class InventoryJsonCodec {
'notes': c.notes,
},
],
'gardenOutcomes': [
for (final o in snapshot.gardenOutcomes)
{
..._syncMeta(
id: o.id,
createdAt: o.createdAt,
updatedAt: o.updatedAt,
lastAuthor: o.lastAuthor,
schemaRowVersion: o.schemaRowVersion,
isDeleted: o.isDeleted,
),
'lotId': o.lotId,
'year': o.year,
'rating': o.rating?.name,
'notes': o.notes,
},
],
'movements': [
for (final m in snapshot.movements)
{
@ -417,6 +434,20 @@ class InventoryJsonCodec {
notes: m['notes'] as String?,
);
}),
gardenOutcomes: _rows(root, 'gardenOutcomes', (m) {
return GardenOutcome(
id: _string(m, 'id'),
createdAt: _int(m, 'createdAt'),
updatedAt: _string(m, 'updatedAt'),
lastAuthor: _string(m, 'lastAuthor'),
isDeleted: _bool(m, 'isDeleted'),
schemaRowVersion: _int(m, 'schemaRowVersion', fallback: 1),
lotId: _string(m, 'lotId'),
year: m['year'] as int?,
rating: _enumOrNull(GardenOutcomeRating.values, m['rating']),
notes: m['notes'] as String?,
);
}),
movements: _rows(root, 'movements', (m) {
final type = _enumOrNull(MovementType.values, m['type']);
if (type == null) return null; // no safe default drop row

View file

@ -27,6 +27,7 @@ class InventorySnapshot {
this.externalLinks = const [],
this.germinationTests = const [],
this.conditionChecks = const [],
this.gardenOutcomes = const [],
this.movements = const [],
this.parties = const [],
this.attachments = const [],
@ -41,6 +42,7 @@ class InventorySnapshot {
final List<ExternalLink> externalLinks;
final List<GerminationTest> germinationTests;
final List<ConditionCheck> conditionChecks;
final List<GardenOutcome> gardenOutcomes;
final List<Movement> movements;
final List<Party> parties;
final List<Attachment> attachments;