high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

ExpenseCalculationPreview is a StatelessWidget that accepts a nullable CalculationResult parameter
When CalculationResult is null, a zero-state placeholder is rendered (e.g., 'Select expense types to see your reimbursement estimate') — no line items shown
When CalculationResult is non-null, one ReimbursementLineItem row renders per entry in the result's line items list
A bold total row is rendered below the line items, visually separated by a Divider, showing the sum of all line item amounts
The total row uses a bold typography style distinguishable from line item rows
The panel is wrapped in a SingleChildScrollView so it scrolls vertically when more than ~4 line items are present
Zero-state placeholder has a Semantics label: 'No expense types selected. Reimbursement total is zero.'
Panel does not crash or overflow when CalculationResult contains zero line items (empty list case)

Technical Requirements

frameworks
Flutter
flutter_test
data models
CalculationResult (nullable) — contains List<LineItemData> and double totalAmount
LineItemData — name String, amount double
performance requirements
ListView.builder used instead of Column for line items if list length may exceed 10
Panel rebuild time under 5ms for up to 20 line items
security requirements
No financial totals written to logs
ui components
SingleChildScrollView wrapping a Column
ReimbursementLineItem (from task-007) per line item
Divider between line items and total row
Total row: Row with bold Text for label and bold Text for formatted total
Zero-state: Center with Text and Semantics

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define CalculationResult as an immutable Dart class (or use freezed if already adopted in the codebase) with a const constructor. Keep the total amount calculation out of the widget — CalculationResult should carry a pre-computed totalAmount to keep the widget pure. Use a Column inside SingleChildScrollView rather than a shrinkwrap ListView to keep physics consistent with the parent scroll context. Apply a visual weight distinction to the total row using AppTypography.titleSmall or equivalent bold token.

The Divider before the total row should use the design system's divider color token to respect dark/light theming.

Testing Requirements

Widget tests with flutter_test. Cover: (1) null CalculationResult renders placeholder and no line items, (2) CalculationResult with 3 items renders exactly 3 ReimbursementLineItem widgets, (3) total row displays sum matching CalculationResult.totalAmount formatted as NOK, (4) empty line items list (non-null result) renders total row with 'kr 0,00' and no line item rows, (5) panel is scrollable — verify ScrollController or SingleChildScrollView presence. Golden snapshot test for the 3-item state and the zero-state placeholder.

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.