high priority low complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

When the calculationResult changes and a new total is computed, SemanticsService.announce() is called with a message in the form: 'Reimbursement total updated: kr {amount}'
Announcement fires within one render cycle of the BLoC state update (i.e., inside the BlocListener callback or equivalent post-frame callback)
Announcement uses TextDirection.ltr
No announcement is made on the initial render (widget mount) — only on subsequent changes
Announcement text is in Norwegian (Bokmål): e.g., 'Refusjonstotal oppdatert: kr {amount}'
If the same total amount is emitted twice consecutively, announcement fires only once (deduplication)
The implementation pattern matches any existing live-region announcement helper already used in the app

Technical Requirements

frameworks
Flutter
flutter_test
apis
SemanticsService.announce(String, TextDirection)
data models
CalculationResult (totalAmount double)
performance requirements
Announcement must not block the UI thread — SemanticsService.announce is async by nature and acceptable
security requirements
Announced amount must be formatted identically to the displayed amount to avoid discrepancy for screen reader users
ui components
BlocListener<ExpenseSelectionBLoC, ExpenseSelectionState> for triggering announcements
SemanticsService.announce call in listener body

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Use a BlocListener alongside the existing BlocBuilder — do not merge them into a single BlocConsumer unless the codebase already uses that pattern, as it complicates testability. In the listener: compare previous.calculationResult?.totalAmount != current.calculationResult?.totalAmount before calling SemanticsService.announce() to implement deduplication. The announcement text must be in Norwegian Bokmål as this is a Norwegian-market product used by accessibility-dependent users (Blindeforbundet users in particular). Format the amount using the same NumberFormat instance as the display widget to guarantee consistency.

Wrap the SemanticsService call in a WidgetsBinding.instance.addPostFrameCallback if needed to ensure the visual update has completed before the audio announcement.

Testing Requirements

Widget tests using flutter_test with SemanticsController enabled (tester.ensureSemantics()). Cover: (1) no announcement on initial widget mount, (2) announcement fires with correct Norwegian text when total changes from null to a value, (3) announcement fires when total changes from one amount to another, (4) announcement does NOT fire when an unrelated BLoC state field changes (buildWhen deduplication), (5) announcement is deduplicated when the same total is emitted twice. Use a spy/mock on SemanticsService or verify via tester.getSemantics if the framework supports it.

Component
Expense Calculation Preview
ui low
Epic Risks (2)
medium impact medium prob technical

If the expense calculation preview subscribes to the full BLoC state stream, every unrelated state property change (e.g. a loading flag toggle) triggers a widget rebuild. With complex card animations for the disabled-state transition, this could cause frame drops on low-end Android devices used by some peer mentors.

Mitigation & Contingency

Mitigation: Use select() on the Riverpod provider to subscribe only to the specific state slice each widget needs; write a performance test asserting rebuild count on a rapid sequence of toggle events.

Contingency: If jank is detected in device testing, replace animated disabled-state transitions with instant opacity changes and defer animation polish to a follow-up sprint.

medium impact low prob integration

The disabled card state requires a specific contrast-safe colour combination that communicates unavailability without relying solely on colour (WCAG 1.4.1). If the current design token palette does not include a disabled-state token with sufficient contrast for text on the disabled background, the widget will either fail WCAG AA or require a last-minute design token addition that could break other components.

Mitigation & Contingency

Mitigation: Audit the existing design token manifest for disabled-state tokens at the start of the epic; if missing, raise with the design lead and add a contrast-validated token before widget implementation begins.

Contingency: If no design review is available, use the established --color-text-disabled and --color-surface-disabled tokens with an added strikethrough or lock icon to satisfy WCAG 1.4.1 non-colour requirement, and document the deviation for design review.