high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget accepts: iconData (IconData), formattedValue (String), unitLabel (String), captionText (String?), displayMode (BenefitMetricDisplayMode.compact | .expanded)
In compact mode: icon + value + unit displayed in a single horizontal row; caption hidden
In expanded mode: icon above, value large and prominent, unit below value, caption text at bottom in muted style
Numeric value formatted with thousand separators (e.g., 1 234 hours) matching Norwegian locale (nb_NO)
A merged Semantics node wraps the entire tile with a combined label such as '42 volunteer hours — time you gave to others' combining value, unit, and caption for screen readers
Individual child text nodes are excluded from the Semantics tree (excludeSemantics: true on children) to avoid duplicate announcements
Icon has no independent Semantics label since it is decorative within the merged node
Widget is purely presentational — no BLoC or provider dependency; all data passed via constructor
Colors and typography sourced exclusively from design tokens; no inline style overrides
Dark mode renders all text and icon colors correctly via theme token lookup
Widget does not clip content when text scale factor is 2.0x — layout adapts gracefully
Caption text is optional; when null, no vertical space is reserved for it in either display mode

Technical Requirements

frameworks
Flutter
performance requirements
Stateless widget — zero rebuild cost beyond what the parent triggers
No image assets loaded; icons sourced from material/cupertino icon font to avoid asset decode overhead
ui components
BenefitMetricTile (StatelessWidget)
BenefitMetricDisplayMode (enum: compact, expanded)
Semantics wrapper (mergeAllDescendantsIntoThisNode: true)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define BenefitMetricDisplayMode as an enum in the same file. Use a single Semantics widget at the root with mergeAllDescendantsIntoThisNode: false and a manually constructed label string that concatenates value + unit + caption to give screen readers a natural single announcement. Mark all child Text and Icon widgets with ExcludeSemantics to prevent double-reading. For locale-aware number formatting use the intl package's NumberFormat.decimalPattern('nb') — confirm the intl package is already in pubspec.yaml.

Keep the widget in a dedicated file under lib/features/benefit_calculator/widgets/ following the project's feature-first folder convention.

Testing Requirements

Widget tests (flutter_test): render in compact and expanded modes, verify layout structure via finder queries. Golden snapshot tests at light/dark mode and 1.0x/2.0x text scale for both display modes. Semantics tree test: verify merged Semantics node contains all three text parts; verify children are excluded. Test null caption path produces no overflow or exception.

Target coverage ≥ 90%.

Component
Benefit Metric Tile
ui low
Epic Risks (3)
high impact medium prob technical

The RepaintBoundary PNG capture approach for sharing the results card may produce blurry or oversized images on high-DPI devices, or may silently fail on certain Android OEM configurations that restrict off-screen rendering. A failed share would break one of the core use cases (recruitment tool).

Mitigation & Contingency

Mitigation: Implement the capture using the established screenshot-capture-utility pattern already present in the Wrapped summary feature (component 542-screenshot-capture-utility). Test on a range of iOS and Android devices including Samsung and Huawei OEM builds during development. Set explicit pixel ratio (3.0) when calling toImage() to guarantee resolution.

Contingency: If image capture fails on a platform, the BenefitShareService falls back to sharing the plain-text summary only, with a user-facing message explaining the image could not be generated. This ensures the share flow never fully blocks.

high impact medium prob technical

Implementing full WCAG 2.2 AA compliance for the results card and metric tiles — including live regions, focus management, and semantic labels that read naturally in Norwegian — requires deep familiarity with Flutter semantics APIs. Gaps may only surface during screen reader testing on physical devices, late in the sprint.

Mitigation & Contingency

Mitigation: Use the existing semantics-wrapper-widget (606) and live-region-announcer (608) components from the accessibility feature rather than implementing custom semantics. Assign screen reader testing on a physical iPhone with VoiceOver as a mandatory acceptance gate, not an afterthought. Write widget tests using Flutter's AccessibilityGuideline matchers early in development.

Contingency: If screen reader issues are found late, the pure semantic markup approach (no Canvas numbers, all Semantics wrappers) limits the blast radius to label text corrections. Escalate to the accessibility feature team for a pairing session to resolve complex focus management issues.

medium impact low prob integration

The BenefitResultsCard must match the Wrapped design language used in the annual summary feature. If design tokens or widget patterns are inconsistent between the two features, the results card will look out of place and undermine the intended emotional impact for sharing.

Mitigation & Contingency

Mitigation: Review the existing Wrapped summary screen (529-wrapped-summary-screen) and stat card widget (530-stat-card-widget) implementations before building the results card. Reuse design tokens from the existing design-token-theme (200) system. Involve the designer in a review of the results card mock-up against the Wrapped language before implementation begins.

Contingency: If design parity issues are discovered post-implementation, isolate visual adjustments to the BenefitResultsCard widget. The feature's business logic and accessibility compliance are unaffected by visual polish changes.