critical priority high complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

All five tab items have explicit semantic labels: 'Home', 'Contacts', 'Add activity', 'Work', 'Notifications'
The currently selected tab exposes Semantics.selected = true; all other tabs expose Semantics.selected = false
Each tab item has a semantic hint string describing its function (e.g., hint: 'Navigate to your activity feed and overview')
VoiceOver on iOS announces the selected state as 'selected' when focusing the active tab (e.g., 'Home, selected, tab 1 of 5')
TalkBack on Android announces the selected state consistently
The 'Add activity' center tab has an additional semantic tooltip indicating it opens a creation menu, not a navigation destination
Tab items satisfy WCAG 2.2 AA criterion 2.4.6 (Headings and Labels) — labels are descriptive
Tab items satisfy WCAG 2.2 AA criterion 4.1.2 (Name, Role, Value) — role is exposed as 'tab', state as selected/not selected
The AccessibleBottomNavigation widget is implemented as an overlay/wrapper and does NOT alter the existing bottom navigation bar's visual layout or routing logic
If an org uses custom labels (organization labels system), the semantic labels update to match the org-specific terminology
Notification badge count (when present) is included in the Notifications tab semantic label (e.g., 'Notifications, 3 unread')

Technical Requirements

frameworks
Flutter Semantics widget with selected property
Flutter Tooltip for hint strings
BLoC/Riverpod for navigation state
StatefulShellRoute (go_router) for tab state
apis
Semantics(selected: bool, label: String, hint: String, onTap: ...)
MergeSemantics or ExcludeSemantics for icon+label combination
Semantics(container: true) to create a bounded semantic node per tab
data models
device_token
accessibility_preferences
performance requirements
Semantics tree update for selected state change must complete within one frame (16ms)
The overlay must not add any additional layout passes — use a Stack with IgnorePointer for pure semantics injection if needed
security requirements
Notification badge count announced in semantic label must not include notification content — only the count
No PII must appear in tab semantic labels or hints
ui components
AccessibleBottomNavigation wrapper widget
TabSemanticsItem (per-tab semantics configuration model)
NotificationBadgeSemanticsProvider (reads unread count for label generation)
OrgLabelResolver for custom terminology per organisation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Wrap each existing BottomNavigationBarItem using a Semantics widget. Use MergeSemantics to combine icon and text into a single semantics node so VoiceOver reads them as one element rather than two. Set Semantics(button: true, selected: isSelected, label: label, hint: hint) on each tab. For the 'Add activity' tab, override the button semantics with Semantics(button: true, label: 'Add activity', hint: 'Opens a menu to create a new activity or event') — it is functionally a button/menu trigger, not a navigation tab.

For notification count: read the unread count from a Riverpod provider that queries the notifications stream; interpolate into the label as 'Notifications, $count unread' when count > 0. The OrgLabelResolver maps tab identifiers to org-specific strings using the existing organisation labels system documented in CLAUDE.md. Important: do not hardcode label strings — store in the localisation arb file so Norwegian translations are supported. The underlying StatefulShellRoute already handles tab state persistence — this widget only wraps the semantics layer.

Testing Requirements

Widget tests: mount AccessibleBottomNavigation with a mock nav state; assert Semantics.selected is true for the active tab and false for all others after each tab switch. Assert all 5 tab labels are present in the semantics tree. Assert hint strings are present. Simulate tab index change and verify selected state updates correctly.

Test with notification badge count of 0, 1, and 99+. Test with an org that overrides the 'Contacts' label to 'Members' and assert the semantic label updates. Accessibility audit: run flutter_test semanticsTester to verify no duplicate labels, no unlabelled interactive elements in the nav bar. Run on physical iOS device with VoiceOver enabled for final validation.

Coverage target: 90%.

Component
Accessible Bottom Navigation
ui high
Epic Risks (3)
high impact high prob integration

Flutter does not natively enforce a focus trap within a bottom sheet or modal dialog in the semantic tree — VoiceOver and TalkBack can navigate outside the sheet to background content. Implementing a reliable focus trap requires overriding the semantic tree, which may conflict with the existing modal helper infrastructure in the app and require changes to shared components beyond this feature's scope.

Mitigation & Contingency

Mitigation: Prototype the focus trap on the first modal sheet implementation before building the remaining sheets. Evaluate Flutter's ExcludeSemantics and BlockSemantics widgets as the trap mechanism, and coordinate with the team owning the shared modal helpers to agree on a non-breaking integration point before writing production code.

Contingency: If a complete semantic focus trap cannot be implemented without breaking existing modal patterns, implement a partial solution using FocusScope with autofocus on the modal's first element and a prominent 'Return to main content' semantic action, documenting the deviation from WCAG 2.4.3 with a scheduled remediation item.

high impact medium prob technical

The activity wizard uses BLoC state management and the UI rebuilds the entire step widget subtree on transition. If the semantic tree is traversed by VoiceOver before the build cycle settles, focus may land on a stale or partially rendered step, causing the wrong step label or progress value to be announced. This is particularly problematic for blind users who cannot visually verify the announcement against the screen.

Mitigation & Contingency

Mitigation: Coordinate ActivityWizardStepSemantics with FocusManagementService (from the core services epic) to delay focus placement until the post-build callback confirms the new step's semantic tree is complete. Write integration tests using the AccessibilityTestHarness that assert the full announcement sequence across all five wizard steps.

Contingency: If post-build focus delay is insufficient due to async BLoC emission timing, add an explicit semantic notification barrier in the wizard cubit that emits a 'step ready' event only after the new widget tree has been marked as built, decoupling the announcement trigger from the raw state transition.

medium impact medium prob scope

Automated WCAG contrast ratio checking on widget tree snapshots may produce false positives for gradient backgrounds, dark-mode overrides, or design token overrides that are resolved at runtime but appear as unresolvable colours at static analysis time. Excessive false positives would erode team trust in the CI gate, leading to suppression rules that also mask real violations.

Mitigation & Contingency

Mitigation: Scope the WCAGComplianceChecker to check only solid-colour backgrounds in the first iteration, explicitly excluding gradients from contrast checks with documented rationale. Design the check output to distinguish 'undetermined' (gradient/unknown) from 'fail' (solid colour below threshold) so the team can take targeted action on genuine failures only.

Contingency: If false positive rates exceed 20% of reported violations during initial CI runs, switch the CI gate from a hard build failure to a warning annotation on the pull request, combined with a mandatory manual review step, until the checker's rule set has been tuned to match actual design token values.