critical priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

BufdirSectionHeadingLevel enum exists with values h2 and h3
BufdirValidationSummary is an @immutable class with fields: int errorCount, int warningCount — both non-nullable and defaulting to 0
BufdirReportSectionData is an @immutable class with fields: String sectionId, String title, BufdirSectionHeadingLevel headingLevel, List<BufdirFieldRowData> fields, BufdirValidationSummary validationSummary
BufdirReportSectionData implements Equatable comparing all fields including the fields list
BufdirReportSectionData has a copyWith method
BufdirReportSectionData has fromJson/toJson for Supabase serialization with nested BufdirFieldRowData list and BufdirValidationSummary
A factory constructor BufdirReportSectionData.fromFields(...) computes validationSummary automatically from the fields list (counts warning and anomaly states)
Unit tests pass: equality, copyWith, JSON round-trip, fromFields correctly counts errors and warnings
File placed at lib/features/bufdir/models/bufdir_report_section_data.dart — pure Dart, no Flutter imports

Technical Requirements

frameworks
Dart
equatable
data models
BufdirReportSectionData
BufdirValidationSummary
BufdirSectionHeadingLevel
BufdirFieldRowData
performance requirements
fromFields factory iterates the fields list once — O(n) computation
security requirements
title field is plain text only — do not allow HTML markup in section titles

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The fromFields factory should compute validationSummary by iterating fields: errorCount = fields.where((f) => f.validationState == BufdirValidationState.anomaly).length; warningCount = fields.where((f) => f.validationState == BufdirValidationState.warning).length. This is the canonical source for the validation badge displayed in BufdirReportSectionWidget — BufdirPreviewService should use this factory when constructing sections so counts are always consistent with actual field states. Use UnmodifiableListView for the fields list in the model to enforce immutability. When implementing Equatable, include listEquals-compatible props: override get props => [sectionId, title, headingLevel, fields, validationSummary].

This model is a direct dependency for task-005.

Testing Requirements

Write unit tests in test/features/bufdir/models/bufdir_report_section_data_test.dart. Cover: (1) fromFields with zero warnings produces validationSummary(0,0); (2) fromFields with 2 warnings and 1 anomaly produces validationSummary(1,2) — note anomaly maps to errorCount; (3) equality for two sections with same fields list; (4) copyWith replaces only specified fields; (5) JSON round-trip preserves all nested BufdirFieldRowData items and their validationState; (6) headingLevel serializes to/from 'h2'/'h3' strings. No widget tests needed.

Component
Bufdir Report Section 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.