high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

ExpenseSummaryCard renders with all five elements visible: type badge, amount, date, status chip, and receipt thumbnail indicator
Status chip displays distinct colours for each state: pending (amber), auto_approved (green), rejected (red) — all passing WCAG 2.2 AA contrast ratio ≥ 4.5:1 against the card background
Receipt thumbnail indicator is visible when receipt_url is non-null; hidden (not just invisible) when null to preserve tap-target space
Amount is formatted as Norwegian locale currency (e.g. '1 250,00 kr') using the project's NumberFormat utility
Date is formatted as 'dd.MM.yyyy' consistent with the rest of the app
Widget accepts an `ExpenseClaim` model and an optional `onTap` callback; if onTap is null the card is non-interactive and has no ink splash
Widget is stateless; all data comes through constructor parameters
Widget exposes a `key` parameter and is usable in ListView.builder without layout jank
All tappable surfaces have a minimum touch target of 48×48 dp
VoiceOver/TalkBack reads the card as a single semantic unit: '{type}, {amount}, {date}, {status}'
Widget renders correctly at text-scale factors 1.0, 1.5, and 2.0 without overflow or clipping
Golden tests pass for all three status states and for null vs. non-null receipt indicator

Technical Requirements

frameworks
Flutter
flutter_test (golden tests)
data models
ExpenseClaim (expense_claims table model)
performance requirements
Widget must be const-constructable when onTap is null
No async operations or futures inside the widget; all data pre-resolved by caller
Rebuilds only when ExpenseClaim reference changes (use == or Equatable on the model)
security requirements
Receipt thumbnail URL must not be rendered as a raw <img> or cached to disk without Supabase signed URL — use the existing signed URL infrastructure from task-006
Amount and claimant name must not be logged to console in production builds
ui components
ExpenseSummaryCard (new)
StatusChip (reuse or create sub-widget)
ReceiptThumbnailIndicator (icon-based, not a network image in this card)
AppButton (existing — do not use for this card's tap, use InkWell/GestureDetector on the card itself)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Place widget at `lib/features/expense/widgets/expense_summary_card.dart`. Import `ExpenseClaim` from the data layer defined in task-004; do not duplicate the model. For status chip colours use design tokens (e.g. `AppColors.statusPending`, `AppColors.statusApproved`, `AppColors.statusRejected`) — add tokens if missing rather than hardcoding hex values.

Use `Semantics(label: ..., child: ...)` wrapping the entire card, with `excludeSemantics: true` on child elements, so screen readers read one clean string. For the receipt indicator, use a camera icon with `Visibility` widget controlled by `claim.receiptUrl != null` — avoid `Opacity(opacity: 0)` which keeps the widget in the tree invisibly. Apply `Flexible` or `Expanded` within the row layout to prevent overflow at large text scales. The type badge should use the organisation's label system (dynamic terminology per org) — pass the label string in, do not resolve it inside the widget.

Testing Requirements

Widget tests (flutter_test): render each status state, verify chip colour token, verify VoiceOver semantics label, verify onTap callback fires, verify null onTap disables interaction. Golden tests: capture PNG for pending/auto_approved/rejected states at 1.0 and 2.0 text scale. Accessibility audit: run Flutter's SemanticsController in tests to assert the merged semantics node. No integration test required for this isolated widget, but include it in the attestation queue screen integration test in task-011.

Component
Expense Summary Card
ui low
Epic Risks (3)
high impact medium prob security

Row-level security policies for expense claims must correctly scope data to organisation, role (peer mentor sees own claims only, coordinator sees org-wide queue), and claim status. Incorrect RLS can expose claims cross-organisation or prevent coordinators from accessing the attestation queue.

Mitigation & Contingency

Mitigation: Define RLS policies in code-reviewed migration files. Write integration tests that attempt cross-org reads with different JWT roles and assert access denial. Review with a second engineer before merging migrations.

Contingency: If RLS is misconfigured post-deployment, disable the affected policy temporarily and apply a hotfix migration within the same release window. No claim data is exposed publicly due to Supabase project-level auth requirement.

medium impact medium prob technical

The auto-approval Edge Function is triggered server-side on expense insert. Cold-start latency or Edge Function failures can block the submission response and degrade UX, especially on mobile networks.

Mitigation & Contingency

Mitigation: Implement the auto-approval Edge Function client with a timeout and graceful fallback: if no result is received within 5 seconds, treat the claim as 'pending' and poll for the status update via Supabase Realtime. Keep the Edge Function warm with a periodic ping.

Contingency: If Edge Function reliability is unacceptable, move auto-approval evaluation to a database trigger or Postgres function as an interim measure, accepting that threshold configuration changes require a migration rather than a settings update.

medium impact low prob scope

The expense type catalogue and threshold configuration are cached locally for offline use. If an organisation updates their catalogue exclusion rules or thresholds while a peer mentor is offline, the local cache may allow submissions that violate the new policy.

Mitigation & Contingency

Mitigation: Cache entries include a TTL (24 hours). On connectivity restore, refresh cache before allowing new submissions. Server-side validation in the Edge Function and save functions provides a second enforcement layer.

Contingency: If a stale-cache submission passes client validation but fails server validation, surface a clear error message explaining that the expense type rules have been updated and prompt the user to review their selection with the refreshed catalogue.