high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

ProxyAuditBadge is a StatelessWidget accepting recordedByUserId (String?) and actorUserId (String?) as named parameters
When recordedByUserId != actorUserId and both are non-null, renders a badge with an icon and the label 'Recorded by coordinator'
When recordedByUserId == actorUserId, renders SizedBox.shrink() (renders nothing, zero size)
When either parameter is null, renders SizedBox.shrink()
Badge background color uses the design system token for a neutral/informational surface (e.g. AppColors.surfaceInfo or equivalent token)
Badge text uses the design system typography token for caption/label size (not hardcoded font size)
Icon is a semantically appropriate symbol (e.g. Icons.supervisor_account or equivalent) rendered at design-system icon size token
Widget is wrapped in Semantics(label: 'This activity was recorded by a coordinator on behalf of the peer mentor') when badge is visible
Badge meets WCAG 2.2 AA contrast ratio (≥ 4.5:1) for text and icon against badge background, verified with Flutter's contrast checker or manual calculation
Widget has a golden test or widget test verifying the rendered output for both visible and hidden states

Technical Requirements

frameworks
Flutter
Dart
performance requirements
Widget build must complete in a single frame with no async operations
Widget must not trigger parent rebuilds — it is purely display-driven by passed parameters
security requirements
Widget must never display raw user IDs in the UI — only the human-readable label
No PII (names, emails) is fetched or displayed by this widget — it is ID-comparison only
ui components
Container / DecoratedBox (badge background with border radius)
Row (icon + label layout)
Icon (supervisor/coordinator icon from design system)
Text (label using design token typography)
Semantics (accessibility wrapper)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place the widget in lib/presentation/widgets/activity/ alongside other activity-related widgets. Use const constructor with all fields to enable widget-level const optimization. For design token access, follow the project's existing pattern (e.g. Theme.of(context).extension() or a static AppColors class).

Do not embed color values as hex literals — every color must come from a design token. The Semantics wrapper should only be applied when the badge is visible, not around the SizedBox.shrink() branch, to avoid announcing invisible elements to screen readers. Reference the WCAG 2.2 AA requirement from the project's workshop documentation (Blindeforbundet, NHF accessibility requirements) as a comment in the widget file.

Testing Requirements

Write flutter_test widget tests: (1) both IDs differ → badge is rendered and 'Recorded by coordinator' text is found in widget tree, (2) both IDs are identical → SizedBox.shrink() rendered, no badge text found, (3) recordedByUserId is null → nothing rendered, (4) actorUserId is null → nothing rendered, (5) Semantics widget is present with correct label when badge is visible, (6) Semantics widget is absent when badge is hidden. Add a golden test capturing the rendered badge appearance for visual regression. Run accessibility audit using flutter_test's SemanticsController to confirm the semantic label is announced correctly.

Component
Proxy Audit Badge Widget
ui low
Epic Risks (2)
high impact medium prob security

Adding recorded_by_user_id to the activities table and writing correct RLS policies is error-prone: overly permissive policies would allow coordinators to record activities under arbitrary user IDs they do not manage, while overly restrictive policies would silently block valid proxy inserts. A policy defect here would either create a security vulnerability or break the entire proxy feature at runtime.

Mitigation & Contingency

Mitigation: Write RLS policies in a local Supabase emulator first. Include policy unit tests using pg_tap or supabase test helpers. Have a second reviewer check the migration SQL before merging. Explicitly test the three cases: coordinator inserting for their own mentors (should succeed), coordinator inserting for another chapter's mentors (should fail), peer mentor inserting for themselves (should succeed as before).

Contingency: If a policy defect is discovered in staging, roll back the migration with a down-migration script. Delay feature release until the policy is corrected and re-verified. Apply a feature flag to keep the proxy entry point hidden from coordinators until the fix is confirmed.

high impact low prob technical

The insert_bulk_activities RPC must behave atomically — a failure on row 7 of 12 must roll back rows 1–6. If Supabase's RPC transaction handling is misconfigured or if network interruptions cause partial acknowledgements, some peer mentors could receive duplicate or missing activity records, directly corrupting Bufdir statistics for the coordinator's chapter.

Mitigation & Contingency

Mitigation: Implement the RPC as a PostgreSQL function with explicit BEGIN/EXCEPTION/END block to guarantee atomicity. Add an integration test that inserts a batch where one row violates a unique constraint and asserts zero rows are committed. Document the transaction semantics in code comments.

Contingency: If atomicity cannot be guaranteed via RPC (e.g., due to Supabase plan limitations), fall back to a sequential insert loop with a compensating DELETE in case of partial failure, and surface a clear error to the coordinator listing which mentors failed and which succeeded.