high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

LabelledNavigationBar widget accepts: items (List<LabelledNavItem>), currentIndex (int), onTap (ValueChanged<int>)
Each LabelledNavItem has: icon (IconData), activeIcon (IconData?), labelKey (String for labels provider lookup), semanticsLabel (String?)
Every tab renders both an icon and a visible text label — no configuration allows icon-only tabs
Maximum 5 items enforced: passing more than 5 items throws AssertionError in debug mode
Each tab touch target is ≥ 48dp × 48dp (enforced via SizedBox/InkWell minimum, not just visual size)
Selected tab announces 'selected' state to screen readers via Semantics(selected: true)
Tab label text is fetched from the OrgLabelsProvider using labelKey — falls back to a humanised version of labelKey if not found
Active tab uses design token colour for active icon and label; inactive tabs use design token colour for inactive state
WCAG 2.2 AA contrast ≥ 3:1 for active icon and label against navigation bar background
Widget does not use Flutter's built-in NavigationBar or BottomNavigationBar — implements custom layout for full control
Golden tests pass for 3-tab and 5-tab configurations in both light and dark themes
Widget is fully accessible via keyboard/switch access — tab order follows left-to-right item order

Technical Requirements

frameworks
Flutter
Riverpod
data models
LabelledNavItem
OrgLabelsProvider
performance requirements
Bar renders in a single frame with no async label lookups at paint time
Label provider lookup cached per session — no repeated async calls per frame
ui components
LabelledNavigationBar
LabelledNavItem
NavTab (private)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Build as a StatelessWidget consuming OrgLabelsProvider via Riverpod (useProvider or ConsumerWidget). Do NOT subclass BottomNavigationBar — use a custom Row of InkWell-wrapped Column(icon + Text) widgets inside a SafeArea-aware Container. Wrap each tab in a SizedBox with constraints: BoxConstraints(minWidth: 48, minHeight: 48) to guarantee touch target size regardless of content. Use Semantics(label: item.semanticsLabel ??

resolvedLabel, selected: currentIndex == i, onTap: ...) per tab. For label resolution, call the provider synchronously using a cached map populated on first access — do not trigger async rebuilds during paint. The bar background colour and shadow should use design tokens (surfaceNav, shadowNav). Ensure the widget works correctly inside StatefulShellRoute from go_router (the existing app navigation architecture) by accepting external currentIndex and onTap instead of managing state internally.

Testing Requirements

Widget tests with flutter_test. Golden tests: 3-tab bar (tab 0 selected), 5-tab bar (tab 4 selected), dark theme variant. Semantics tests: verify each tab has Semantics(selected: bool, label: String). Touch target test: use tester.getRect() to verify hit area ≥ 48×48 logical pixels.

Test label provider override: mock OrgLabelsProvider to return custom labels and verify rendered text. Test AssertionError for 6 items. Test onTap callback fires with correct index. Accessibility: use SemanticsChecker or manual semantics tree traversal to confirm no unlabelled tappable nodes.

No e2e tests needed.

Component
Labelled Navigation Bar
ui low
Epic Risks (4)
high impact medium prob technical

The error message registry and help content registry both depend on bundled JSON assets loaded at startup. If asset loading fails silently (e.g. malformed JSON, missing pubspec asset declaration), the entire plain-language layer falls back to empty strings or raw error codes, breaking the accessibility guarantee app-wide.

Mitigation & Contingency

Mitigation: Implement eager validation of both assets during app initialisation with an assertion failure in debug mode and a structured error log in release mode. Add integration tests that verify asset loading in the Flutter test harness on every CI run.

Contingency: Ship a hardcoded minimum-viable fallback message set directly in Dart code so the app always has at least a safe generic message, preventing a blank or code-only error surface.

medium impact medium prob dependency

The AccessibilityDesignTokenEnforcer relies on dart_code_metrics custom lint rules. If the lint toolchain is not already configured in the project's CI pipeline, integrating a new linting plugin may cause unexpected build failures or require significant CI configuration work beyond the estimated scope.

Mitigation & Contingency

Mitigation: Audit the existing dart_code_metrics configuration in the project before starting implementation. Scope the lint rules to a separate Dart package that can be integrated incrementally, starting with the most critical rule (hard-coded colors) and adding others in subsequent iterations.

Contingency: Fall back to Flutter test-level assertions (using the cognitive-accessibility-audit utility) to catch violations in CI if the lint plugin integration is delayed, preserving enforcement coverage without blocking the epic.

medium impact low prob technical

WizardDraftRepository must choose between shared_preferences and Hive for local persistence. Choosing the wrong store for the data volume (e.g. shared_preferences for complex nested wizard state) can lead to serialisation bugs or performance degradation, particularly on lower-end Android devices used by some NHF members.

Mitigation & Contingency

Mitigation: Define a clean repository interface first and implement shared_preferences as the initial backend. Profile serialisation round-trip time with a realistic wizard state payload (≈10 fields) before committing to either store.

Contingency: Swap the persistence backend behind the repository interface without touching wizard UI code, which is possible precisely because the repository abstraction isolates the storage detail.

medium impact high prob scope

The AccessibilityDesignTokenEnforcer scope could expand significantly if a large portion of existing widgets use hard-coded values. Discovering widespread violations during this epic would force either a major refactor or a decision to exclude legacy components, potentially reducing the enforcer's coverage and value.

Mitigation & Contingency

Mitigation: Run a preliminary audit of existing widgets using a simple grep for hard-coded hex colors and raw pixel values before implementation begins. Use the results to set a realistic remediation boundary for this epic and log all out-of-scope violations as tracked tech-debt items.

Contingency: Scope the enforcer to new and modified components only (via file-path filters in dart_code_metrics config), shipping a partial but immediately valuable coverage rather than blocking the epic on full-codebase remediation.