high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Tapping the banner header toggles the issue list panel open/closed with an AnimatedSize slide-down animation (duration 250ms, Curves.easeInOut)
Panel shows a row for each BufdirValidationIssue, displaying sectionTitle and issueMessage
Filter toggle row has two FilterChip widgets: 'Errors' (with errorCount badge) and 'Warnings' (with warningCount badge)
When only 'Errors' filter is active, only issues with severity == BufdirValidationSeverity.error are shown
When only 'Warnings' filter is active, only issues with severity == BufdirValidationSeverity.warning are shown
When both filters are active (default), all issues are shown
When both filters are deactivated, all issues are shown (treat as 'show all' fallback)
Tapping an issue row invokes BufdirFocusManager.scrollToField(fieldIdentifier) — no direct widget access from banner
Panel is constrained to max-height 40% of screen height with internal scrolling via ListView
Each issue row has a Semantics label combining sectionTitle and issueMessage for screen reader traversal
Filter chip state is local (useState-equivalent via StateProvider) and does not persist across banner close/open cycles

Technical Requirements

frameworks
Flutter
Riverpod
data models
bufdir_column_schema
bufdir_export_audit_log
performance requirements
Issue list uses ListView.builder for O(1) widget construction regardless of issue count
Filter operation is synchronous derived state — no async calls on filter toggle
security requirements
Issue messages must be HTML-escaped before display — no raw server error strings rendered as rich text
fieldIdentifier used for focus navigation must be validated against known field keys before use
ui components
BufdirValidationSummaryBanner (extended)
BufdirIssueListPanel (extracted sub-widget)
BufdirIssueFilterRow (extracted sub-widget)
BufdirIssueRow (extracted sub-widget)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Extract BufdirIssueListPanel as a separate StatefulWidget to isolate filter state from the banner's animation state — avoids unnecessary parent rebuilds on filter toggle. Use a Riverpod StateProvider> for filter state so it can be overridden in tests. BufdirFocusManager should be injected as a Riverpod provider (not a static class) to enable test mocking. The panel's max-height constraint should use MediaQuery.of(context).size.height * 0.4 with a LayoutBuilder fallback.

Avoid nested Scrollables without physics: NeverScrollableScrollPhysics on the inner list if the panel height is unconstrained.

Testing Requirements

Widget tests: (1) panel is not visible on initial render; (2) tapping banner header shows panel; (3) tapping again hides panel; (4) with filter 'Errors' only active, warning-severity rows are not present in widget tree; (5) with filter 'Warnings' only active, error-severity rows are not present; (6) tapping an issue row calls a mock BufdirFocusManager.scrollToField with correct fieldIdentifier; (7) panel list is scrollable when issue count exceeds visible height; (8) golden test for expanded panel with mixed issues. Use a mock/fake BufdirFocusManager injected via Riverpod override.

Component
Validation Summary Banner
ui low
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.