Wire calculation preview to BLoC stream with 300ms SLA
epic-expense-type-selection-user-interface-task-009 — Subscribe the ExpenseCalculationPreview widget to the ExpenseSelectionBLoC stream and re-render whenever the calculationResult field changes. Verify with integration tests that the preview updates within 300 ms of every selection change event. Use StreamBuilder and avoid local state to keep the widget stateless.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Use BlocBuilder rather than StreamBuilder directly to remain consistent with BLoC conventions already established in the codebase. Add buildWhen: (previous, current) => previous.calculationResult != current.calculationResult to the BlocBuilder to prevent rebuilds when other fields change (e.g., loading state for a different operation). The 300ms SLA is a product requirement from HLF's detail requirements — enforce it in the integration test using fake async timing, not real timers. If calculation is async (Supabase query or local computation), ensure the BLoC emits an intermediate state with the previous result preserved, not null, to avoid a blank flash on every keypress.
Dispose the BLoC at the parent screen level, not inside this widget.
Testing Requirements
Integration tests using flutter_test with a fake/mock ExpenseSelectionBLoC. Cover: (1) initial render shows placeholder when BLoC state has null calculationResult, (2) dispatching a selection event causes preview to update within 300ms — use tester.pump(Duration(milliseconds: 300)) and assert updated state, (3) multiple rapid selection changes result in final correct state (no stale intermediate state displayed), (4) widget disposes BLoC subscription without errors when popped from navigation stack, (5) buildWhen verified by counting build calls — emitting an unrelated state field change must not trigger a rebuild.
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.
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.