critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

BufdirValidationState enum exists with exactly three values: normal, warning, anomaly
BufdirFieldRowData is a @immutable Dart class with fields: String fieldId, String label, String value, BufdirValidationState validationState, String? tooltipText
BufdirFieldRowData implements Equatable (or overrides == and hashCode) so two instances with identical field values compare as equal
BufdirFieldRowData has a copyWith method for all fields
BufdirFieldRowData has a fromJson factory constructor and a toJson method for Supabase serialization
All fields except tooltipText are non-nullable
Unit tests pass: equality, copyWith mutation, JSON round-trip for all three validation states
File is placed at lib/features/bufdir/models/bufdir_field_row_data.dart
No dependency on Flutter widgets — this is a pure Dart model file

Technical Requirements

frameworks
Dart
equatable (pub.dev package)
data models
BufdirFieldRowData
BufdirValidationState
performance requirements
Model instantiation must be O(1) — no async operations in constructor
security requirements
tooltipText may contain user-facing validation messages; ensure no raw HTML is stored in this field — plain text only

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the equatable package for == and hashCode to avoid boilerplate errors. The validationState field should serialize to/from a lowercase string in JSON ('normal', 'warning', 'anomaly') matching what BufdirPreviewService returns from Supabase. Prefer a static Map for fromJson validation state parsing rather than relying on enum.name to guard against future rename refactors. Keep tooltipText nullable — the normal state never has a tooltip.

Place the enum definition in the same file as the model for colocation. This model is a foundational dependency for tasks 002, 003, 004, and 005 — ensure the API is stable before those tasks begin.

Testing Requirements

Write unit tests in test/features/bufdir/models/bufdir_field_row_data_test.dart. Cover: (1) equality for two identical instances; (2) inequality when any single field differs; (3) copyWith preserves unchanged fields; (4) fromJson/toJson round-trip for validationState='normal', 'warning', 'anomaly'; (5) null tooltipText serializes to null in JSON and deserializes correctly. Use flutter_test package. No widget tests needed — pure Dart model.

Component
Bufdir Field Row Widget
ui medium
Epic Risks (2)
high impact medium prob technical

Implementing the tap-to-scroll-and-focus behavior from the validation banner to a specific field row in a long scrollable list is complex in Flutter. If focus management is incorrectly implemented, VoiceOver users who navigate to the banner and select an issue will not be moved to the relevant field row, breaking the accessibility workflow and violating WCAG 2.4.3 (Focus Order).

Mitigation & Contingency

Mitigation: Use BufdirAccessibilityUtils focus management utilities (built in the foundation epic) with explicit GlobalKey-based scroll anchors on each field row. Test with a real iOS device running VoiceOver during widget development, not only in the Flutter accessibility inspector.

Contingency: If programmatic scroll-to-focus cannot be reliably achieved before the TestFlight deadline, fall back to a navigation approach where tapping a banner issue opens a modal detail sheet for that field row rather than scrolling in place, and file a follow-up ticket for the inline scroll implementation.

medium impact low prob technical

The validation summary banner must reactively update its issue count as underlying aggregated data changes (e.g., if the coordinator has navigated away and data was refreshed). If the banner's Riverpod provider is not correctly scoped, it may display stale issue counts or fail to disappear when all issues are resolved, eroding coordinator trust in the validation system.

Mitigation & Contingency

Mitigation: Drive the banner exclusively from the same Riverpod provider that powers the full preview model — do not maintain a separate local state for issue counts. Write a widget test that simulates a data refresh mid-review and asserts the banner updates within one frame.

Contingency: If stale state reaches production, add a manual refresh button to the banner as a short-term workaround while the provider scoping is corrected in the next release cycle.