high priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

An in-memory cache stores the fetched report schema for a given organisation_id and serves subsequent reads without a network call until the TTL expires
TTL is configurable per environment (default 15 minutes for production, overridable in tests) and is enforced consistently across hot and cold restarts within the same app session
On TTL expiry the cache transparently re-fetches the schema from the upstream source and updates the in-memory store without throwing an error to callers
A persistent cache layer (e.g. SharedPreferences or local file) stores the last successfully fetched schema so the app can render the report form in offline or degraded-network conditions
Cache invalidation is triggered correctly when a schema-update event is received (e.g. Supabase realtime notification or explicit invalidate() call) and causes the next read to bypass the stale entry
A Riverpod provider (StateNotifierProvider or AsyncNotifierProvider) exposes the cached schema; consumers rebuild only when the schema actually changes
The cache correctly handles multiple concurrent reads during a single in-flight fetch (i.e. does not issue duplicate network requests for the same organisation_id)
All cache operations complete in under 5ms for the in-memory path and under 50ms for the persistent read path on a mid-range device
Unit tests cover: cache hit, cache miss + fetch, TTL expiry + re-fetch, explicit invalidation, concurrent read deduplication, and offline fallback to persistent cache

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase
data models
ReportSchema
OrganisationFieldConfig
performance requirements
In-memory cache read must complete in under 5ms
Persistent cache read must complete in under 50ms
Concurrent reads for the same organisation_id must result in exactly one network request
security requirements
Cached schema data must not contain PII; only structural field definitions and metadata are cached
Persistent cache storage must use Flutter's secure storage or the app sandbox (no external storage) to prevent schema leakage

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use a single ReportSchemaCacheNotifier class backed by a Map where CacheEntry holds the schema payload and an expiry DateTime. Guard all reads with a mutex or Completer to prevent parallel fetch stampedes on cache miss. For persistence, serialize the schema to JSON and write to SharedPreferences keyed by 'report_schema_{organisation_id}'. On app startup, pre-warm the in-memory cache from the persistent store before the first consumer widget builds.

The Riverpod provider should be scoped to the authenticated user's organisation so it is automatically disposed and re-created on org switch. Avoid using Riverpod's autoDispose on the cache provider itself — it should live for the full app session once initialised.

Testing Requirements

Write unit tests using flutter_test with a mocked Supabase client. Test matrix: (1) cache hit returns immediately without calling the network client, (2) cache miss triggers exactly one fetch and caches the result, (3) TTL expiry causes re-fetch on next read, (4) explicit invalidate() causes re-fetch, (5) two simultaneous reads during an in-flight fetch result in one network call, (6) when the network is unavailable the persistent cache is returned, (7) when both caches are empty an appropriate error/loading state is emitted. Aim for 100% branch coverage on the cache logic class.

Component
Report Schema Cache
data low
Epic Risks (2)
medium impact high prob technical

Flutter's speech_to_text package behaviour differs meaningfully between iOS and Android — microphone permission flows, locale availability, background audio session interference, and partial-result timing all vary. Inconsistent behaviour could make voice input unreliable for the primary audience (visually impaired peer mentors on iOS VoiceOver).

Mitigation & Contingency

Mitigation: Test speech-to-text-adapter on physical iOS and Android devices from the start, not just simulators. Write platform-specific test cases for permission flows and locale detection. Design the adapter's public interface to be platform-agnostic so that a native bridge could replace the package if needed.

Contingency: If speech_to_text proves unreliable on a platform, implement a native-speech-api-bridge (already identified in the component catalogue) as a drop-in replacement within the adapter, keeping the external interface unchanged so no UI code needs to change.

medium impact medium prob dependency

The coordinator task queue notification mechanism is not fully specified. If the queue system is owned by another team or uses an external service, way-forward-task-service may block on an undefined integration contract, delaying this epic.

Mitigation & Contingency

Mitigation: Define the task queue notification interface as an abstract Dart interface early in the epic. Implement a stub that writes a flag to the database so coordinator list queries can detect new tasks, deferring the real notification integration to a later epic.

Contingency: If the queue integration remains undefined at implementation time, ship way-forward-task-service with database persistence only and add a TODO-flagged notification hook. Coordinators will still see items on next page load; push notification delivery is deferred.