critical priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

BadgeCardWidget is a stateless widget accepting at minimum: badge name (String), isEarned (bool), iconAssetPath (String), and an optional progress (double? between 0.0 and 1.0)
The widget renders the correct asset image (locked or unlocked variant) using BadgeIconAssetManager.resolveIconPath
Badge name is displayed as a text label below or beside the icon
Earned badges render with a visually distinct style (e.g., full colour, accent border) and locked badges render in a muted/greyscale style
When progress is provided and isEarned is false, a LinearProgressIndicator is shown with value equal to the progress parameter
When progress is null or isEarned is true, no LinearProgressIndicator is rendered
The widget wraps its content in a Semantics widget with a label that reads: '{badge name}, {earned/locked}, {progress percentage if applicable}'
VoiceOver and TalkBack correctly read the semantic label in a manual accessibility audit on iOS and Android
The widget renders without overflow at screen widths as narrow as 320px
Widget tests pass for all combinations of isEarned and progress (earned/no-progress, locked/no-progress, locked/with-progress)

Technical Requirements

frameworks
Flutter
data models
Badge
BadgeCriteria
performance requirements
Widget build method must complete in under 16 ms (one frame at 60 fps) on a mid-range device
No expensive computations in build(); asset path resolution via BadgeIconAssetManager must be O(1) lookup
security requirements
Badge name text must be sanitised before display to prevent injection of control characters
ui components
BadgeCardWidget (stateless)
LinearProgressIndicator (Flutter material)
Semantics wrapper
Image.asset or SvgPicture.asset for badge icon

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Keep the widget strictly stateless — all state (earned/locked, progress) is passed in as constructor parameters, enabling the parent BLoC or Riverpod provider to control rendering. Use a Stack to overlay a greyscale ColorFilter over the icon image for locked state rather than maintaining separate greyscale assets. Wrap the LinearProgressIndicator in an ExcludeSemantics widget and express the progress via the parent Semantics label as a percentage string (e.g., '45% complete') so screen readers give a meaningful announcement rather than reading the raw slider value. Follow the project's design token system for spacing and typography — do not hardcode pixel values.

Reference the project's AppButton widget for patterns on how semantic roles are applied in this codebase.

Testing Requirements

Write widget tests (flutter_test) covering: (1) earned badge renders accent styling and no progress bar, (2) locked badge renders muted styling and no progress bar when progress is null, (3) locked badge with progress renders LinearProgressIndicator with correct value, (4) Semantics tree contains expected label string for each state. Use WidgetTester.pump and find.byType/find.bySemanticsLabel. Achieve 90%+ line coverage on the widget file. Additionally, run a golden test for each of the three states to catch unintended visual regressions.

Component
Badge Card Widget
ui low
Epic Risks (2)
high impact medium prob scope

Badge criteria are stored as structured JSON in badge_definitions. If the JSON schema for criteria (threshold counts, streak lengths, training completion flags) is not well-defined upfront, the evaluation service will be built against a moving target, requiring costly migrations and refactors.

Mitigation & Contingency

Mitigation: Define and document the criteria JSON schema in a shared type file before any repository code is written. Review the schema with all three organisations' badge requirements — especially Blindeforbundet's honorar thresholds — and version the JSON schema using a 'criteria_version' field from day one.

Contingency: If the criteria schema must change after services are built, write a Supabase migration to backfill existing rows and add a migration version column. Keep the evaluation service criteria parser isolated behind an interface so only one function needs updating.

medium impact medium prob dependency

Badge icon assets may not yet exist or may fail WCAG 2.2 AA contrast validation (minimum 3:1 for graphical objects) when rendered over design-token backgrounds. Missing or non-compliant icons could block UI epic delivery for Blindeforbundet, for whom screen reader and visual accessibility is non-negotiable.

Mitigation & Contingency

Mitigation: During this epic, implement the contrast-ratio validator in badge-icon-asset-manager and run it as a Flutter test against all candidate icon assets early. Coordinate with the design team to provide WCAG-compliant SVG icons in both locked and unlocked variants before the UI epic begins.

Contingency: If assets are late or fail contrast checks, ship placeholder icons that are guaranteed compliant (solid design-token colour fills with text labels) and swap in final assets post-QA without requiring a code change.