critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

When validationState is normal, no icon is rendered and row layout is identical to task-002 output
When validationState is warning, a warning triangle icon (Icons.warning_amber_rounded or equivalent) is shown in the right gutter of the row using BufdirAccessibilityUtils.warningColor token
When validationState is anomaly, a distinct anomaly icon (Icons.error_outline or equivalent) is shown using BufdirAccessibilityUtils.anomalyColor token
Warning and anomaly color tokens each achieve WCAG 2.2 AA contrast ratio of 3:1 against the row background (non-text contrast threshold for icons)
When tooltipText is non-null, a Semantics widget wraps the icon with label set to tooltipText so VoiceOver/TalkBack announces it
When tooltipText is null and state is warning/anomaly, the Semantics label falls back to a localized default string (e.g., 'Warning' or 'Anomaly')
Icon tap/long-press shows a Tooltip widget with tooltipText for sighted users
Icon size meets WCAG 2.2 minimum touch target of 44×44 logical pixels (use GestureDetector or IconButton with padding)
Golden tests updated for warning state and anomaly state at 375px width
No regression: normal state golden test still passes

Technical Requirements

frameworks
Flutter
BufdirAccessibilityUtils (internal)
Flutter Semantics API
data models
BufdirFieldRowData
BufdirValidationState
performance requirements
Icon rendering adds no measurable frame time; use const Icon constructors where possible
security requirements
tooltipText content is user-facing validation message — sanitize to plain text before rendering, no HTML injection
ui components
Icon
Tooltip
Semantics
IconButton
Row
Expanded

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a switch on validationState to decide icon data and color. Pattern: final iconData = switch(data.validationState) { BufdirValidationState.warning => Icons.warning_amber_rounded, BufdirValidationState.anomaly => Icons.error_outline, _ => null }. If iconData is null, render SizedBox.shrink() to avoid layout shift. Wrap the icon in Semantics(label: data.tooltipText ??

defaultLabel, child: Tooltip(message: ..., child: IconButton(...))). Use IconButton with padding: EdgeInsets.all(10) to ensure 44px minimum interactive area even with a 24px icon. Source warningColor and anomalyColor exclusively from BufdirAccessibilityUtils — do not use Colors.yellow or Colors.red directly. Ensure the icon column in the row layout has a fixed width (e.g., 44px) so the two-column label/value layout is not disturbed when no icon is present (use SizedBox(width: 44, child: iconWidget)).

Testing Requirements

Extend test/features/bufdir/widgets/bufdir_field_row_widget_test.dart with: (1) warning state renders Icons.warning_amber_rounded (find.byIcon); (2) anomaly state renders anomaly icon; (3) normal state has no icon (find.byType(Icon) returns zero widgets); (4) Semantics label equals tooltipText when provided (use tester.getSemantics); (5) Semantics label equals localized default when tooltipText is null; (6) long-press on icon in warning state shows Tooltip with correct message; (7) touch target is at least 44px (tester.getSize on IconButton). Update golden tests for all three states. Run accessibility checks using flutter_test SemanticsController to verify screen reader labels are present.

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.