fix(app): stop one malformed item dropping the whole conversation decode
ActionItem.fromJson throws FormatException: Invalid field: due_at when the server sends an unparseable or empty due_at, and the actionItems loop in Structured.fromJson had no guard around it. The exception escapes the whole factory, so one bad timestamp drops the entire conversation: title, overview, events, and every valid action item next to it. The events loop had the same gap.
Two paths throw TypeError rather than FormatException, so both need catching. ActionItem.fromJson passes json['deleted'] straight into a bool, and Event.fromJson builds legacy integer timestamp events by hand from raw duration, created and title, skipping the generated reader. A deleted of "yes" failed with type 'String' is not a subtype of type 'bool', a string duration with the same error for int, and each took the conversation down the same way. Both loops now skip an entry that raises either.
The sections loop directly above already skips a bad entry for this reason, and its comment says to skip "the way the actionItems and events loops below do". Those two loops did not do that. They do now.
This does not change what counts as a valid item. contracts/parity/wire_action_item.json pins Dart to strict_decode with parses: false for "" and "not-a-date", and that is still what happens: the bad item is rejected, only now on its own. test/parity/parity_contracts_test.dart passes unchanged at 35 cases. Whether Dart should converge on the tolerant decode Windows uses is the open question in divergence 4 of contracts/parity/README.md, and this PR deliberately does not answer it.
Verified by running, from app/:
$ flutter test test/unit/structured_decode_blast_radius_test.dart test/parity/parity_contracts_test.dart
00:08 +42 ~5: All tests passed!
$ bash test.sh
03:59 +1885 ~5: All tests passed!
$ bash scripts/analyze_ratchet.sh
Analyzer ratchet passed.
$ make preflight (repo root)
PR preflight passed: 14 checks
The 42 are the 7 blast radius tests plus the 35 parity cases. Two of the seven pin unchanged behaviour: a well formed payload still decodes every item, and plain string action items still work.
Failure-Class: FC-batch-fault-domain-head-of-line-poison