critical priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Navigating to the post-session report route while the linked activity has status 'active' or 'in_progress' redirects to (or overlays) a blocking screen — the report form is never rendered in the widget tree
The guard is implemented as a GoRouter redirect or a route-level guard, not as a conditional inside the report screen widget — no deep-link or programmatic navigation bypasses it
The blocking UI displays: the activity name, a clear message explaining why the report is unavailable (e.g. 'The session must end before you can fill in the report'), and an action button to navigate back or to the activity detail screen
The blocking UI is semantically accessible: screen reader announces the blocking reason without the user needing to navigate the full page; action button has a descriptive label
When the session ends (status changes to 'completed' or 'ended'), re-navigating to the report route succeeds without requiring an app restart
The activity session state is fetched exactly once per navigation attempt — no polling; the result is not cached across navigations (fresh check each time)
Loading state: while the session status is being fetched, a skeleton or spinner is shown in the blocking guard — the report form is not shown during this period
Error state: if the session status fetch fails (network error), the guard defaults to blocking with an error message and a retry action — it does NOT allow access to the report on error
Unit test: guard correctly blocks when `activityStatus == 'active'` and allows through when `activityStatus == 'completed'`
Widget test: blocking screen renders with correct text and the back button navigates to the expected route
WCAG 2.2 AA: blocking screen meets 4.5:1 contrast ratio for all text; focus order is logical

Technical Requirements

frameworks
Flutter
BLoC (or Riverpod) for session state
GoRouter (route-level redirect guard)
apis
Activity repository — getActivityStatus(activityId) → ActivityStatus
Supabase (via activity repository)
data models
ActivityStatus enum (active, in_progress, completed, ended, cancelled)
Activity (id, status, title)
performance requirements
Session status fetch completes in < 2 seconds on a standard mobile connection; guard shows spinner immediately on navigation
Guard evaluation adds < 50 ms overhead to route transitions on cached data
security requirements
Guard must be enforced at the GoRouter redirect layer — a user who manually constructs a deep link to the report screen is still blocked
Do not expose the activity_id in error messages visible to non-owning users
ui components
SessionLockGuard (route redirect function or guard widget)
BlockingScreen widget (message, activity name, back/retry button)
LoadingGuard skeleton/spinner

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement the guard as a GoRouter `redirect` callback on the report route, not inside the screen widget. The callback should be async (GoRouter supports async redirects via a `FutureOr` return). Inject the activity repository into the guard via a Riverpod provider or BLoC so it is testable. Be careful with GoRouter's `refreshListenable` — if the activity status provider is a `ChangeNotifier` or `Stream`, hook it up so the router re-evaluates the guard when status changes, enabling seamless unblocking when a session ends.

The blocking screen should use the app's standard design tokens (colors, typography, spacing) from the existing token system — do not hardcode values. Ensure the back button calls `context.go(...)` rather than `Navigator.pop()` to stay within GoRouter's navigation stack.

Testing Requirements

Write unit tests for the guard logic function in isolation (mock activity repository, test all ActivityStatus enum values). Write widget tests for the BlockingScreen widget (verify text content, button labels, and tap navigation). Write a GoRouter integration test using `GoRouter.optionURLReflectsImperativeAPIs` and a test router setup to verify the redirect fires correctly. Cover: blocked state, allowed state, loading state, and error/retry state.

Use `flutter_test` with `WidgetTester` and a mock activity repository.

Component
Post-Session Report Screen
ui high
Epic Risks (2)
medium impact high prob technical

End-to-end integration tests that span Flutter UI → Supabase → RLS → storage are inherently flaky in CI due to network timing, test database state, and Supabase cold-start latency. Flaky tests erode confidence and slow the release pipeline.

Mitigation & Contingency

Mitigation: Use a dedicated Supabase test project with seeded org and user fixtures. Wrap all E2E tests in retry logic with a fixed seed and tear-down hooks. Keep E2E tests in a separate test suite that runs on-demand rather than on every PR, with unit and widget tests as the primary CI gate.

Contingency: If E2E tests remain unreliable, replace the Supabase calls in integration tests with a verified fake (in-memory repository implementations) and promote the real Supabase tests to a nightly scheduled run rather than blocking PR merges.

high impact medium prob security

Health status, course interest, and assistive device fields contain personal health data. If any logger, analytics event, or crash reporter captures field values — through automated error serialisation or developer-added debug logs — the feature could violate GDPR and Blindeforbundet's data processor agreement.

Mitigation & Contingency

Mitigation: Audit all log statements in the report feature's code paths before the epic is marked done. Apply a PII-safe logging wrapper that strips field values from any serialised form state before it reaches the logger. Add a CI lint rule that flags direct logger calls within report-related files.

Contingency: If PII is found in logs post-launch, immediately disable the affected logging call and rotate any credentials that were exposed. Notify the data protection officer and document the incident per GDPR Article 33 requirements.