high priority medium complexity integration pending fullstack developer Tier 8

Acceptance Criteria

Tapping 'Dismiss' shows an optional brief reason selection dialog (e.g., 'Already handled', 'Not relevant', 'Will follow up later') before confirming dismissal
On confirmation, the Prompt History Repository method markPromptDismissed(promptId, reason, dismissedAt) is called with correct parameters
dismissedAt is always the device's current UTC timestamp at the moment of tap — not a server timestamp to avoid latency
The bottom sheet closes immediately after the repository call resolves successfully (optimistic close)
If the repository call fails, a SnackBar error message is shown and the sheet remains open so the coordinator can retry
The dismissed prompt no longer appears in the scenario prompt notification list for the duration of the cooldown window (verified by querying the list after dismissal)
Cooldown window filtering logic is enforced server-side via Supabase query/RLS — client does not implement cooldown logic independently
The dismiss action is guarded by a coordinator role check — non-coordinator users cannot access the dismiss button (button hidden or disabled)
Loading indicator shown on the Dismiss button while the repository call is in flight — button disabled during loading to prevent double-tap
Dismiss reason is stored in the Prompt History record and visible in any future admin audit view
If the user swipes the sheet down without tapping Dismiss, no dismissal is recorded — swipe-to-close is a non-committing action

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
assignment
performance requirements
Dismiss repository call must complete within 3 seconds — show timeout error if exceeded
Sheet close animation must not be blocked by the async repository call — use optimistic close pattern
security requirements
Coordinator role must be validated server-side via Supabase RLS on the prompt history table — client-side role check is UX only, not a security boundary
Prompt ID must be a valid UUID from the server — reject any client-constructed IDs
Dismiss reason must be selected from a predefined enum — reject free-text to prevent injection
ui components
Reason selection dialog (showDialog with radio list or ListTile options)
AppButton with loading state (isLoading: true while in flight)
SnackBar for error feedback via ScaffoldMessenger
Role-aware widget that hides/disables Dismiss for non-coordinators

Execution Context

Execution Tier
Tier 8

Tier 8 - 48 tasks

Can start after Tier 7 completes

Implementation Notes

Use a StateNotifier or AsyncNotifier to manage the dismiss flow state (idle, loading, success, error) and expose it to the sheet widget via Riverpod. The reason selection dialog should use an enum DismissReason with values like alreadyHandled, notRelevant, willFollowUpLater — never accept raw strings from the UI. For the optimistic close pattern: pop the sheet immediately on button tap, then await the repository call in the background; if it fails, show a SnackBar from the parent screen (pass a GlobalKey or use the root scaffold). For role guard: read the current user's role from an existing auth provider (do not re-fetch from Supabase) and set the button's enabled state accordingly.

The cooldown filtering must be in the Supabase query (WHERE dismissed_at IS NULL OR dismissed_at < NOW() - INTERVAL '...'). Coordinate with the backend task that defines the prompt_history table schema to confirm column names before writing the repository method.

Testing Requirements

Write integration tests and widget tests using flutter_test. Integration test 1: Full dismiss flow — tap Dismiss → select reason → confirm → verify markPromptDismissed called with correct promptId, reason, and a UTC timestamp. Integration test 2: After dismissal, prompt notification list provider no longer returns the dismissed prompt. Widget test 1: Dismiss button shows loading indicator while repository call in flight.

Widget test 2: SnackBar error shown when repository returns error, sheet stays open. Widget test 3: Dismiss button absent/disabled for non-coordinator role. Widget test 4: Swiping sheet down without tapping Dismiss does NOT call markPromptDismissed. Mock the PromptHistoryRepository in all tests — do not hit Supabase.

Aim for 85%+ branch coverage on the dismiss flow.

Epic Risks (2)
high impact medium prob technical

If the scheduler runs concurrently (e.g., two overlapping cron invocations due to edge function retry), duplicate prompts could be dispatched before the first run's history records are committed, breaking the deduplication guarantee.

Mitigation & Contingency

Mitigation: Use a Postgres advisory lock or unique constraint on (user_id, scenario_id, activity_ref) in the prompt history table to make concurrent writes idempotent; design the scheduler to check history inside a transaction.

Contingency: If concurrency issues persist in production, add a distributed lock via Supabase Edge Function concurrency limit (max_instances=1) for the evaluation function as a hard guard.

medium impact medium prob scope

Coordinators may find scenario configuration unclear if trigger conditions are expressed as raw JSON or technical terminology, leading to misconfiguration and irrelevant prompts being sent to peer mentors.

Mitigation & Contingency

Mitigation: Design the ScenarioConfigurationScreen to display human-readable descriptions of each template's trigger condition (e.g., 'Send 3 days after first contact if wellbeing concern was flagged') rather than raw rule properties; validate with an HLF coordinator in a design review before implementation.

Contingency: If coordinators still misconfigure rules after launch, add a preview mode that shows a simulated prompt based on a test activity before the rule is enabled.