Add local cache layer to BufdirPreviewRepository
epic-bufdir-report-preview-foundation-task-003 — Extend BufdirPreviewRepository with an offline-capable local cache using Hive or shared_preferences so that after the first successful load the last known report data is available without a network round-trip. Implement cache invalidation tied to period selection changes and a configurable TTL. Ensure the stream emits cached data immediately while a background refresh is in flight.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Introduce a BufdirCacheEntry wrapper class serialisable to JSON: { 'data': BufdirReportData.toJson(), 'cachedAt': ISO8601 string, 'periodId': String, 'orgId': String }. Cache key format: 'bufdir_preview_${orgId}_${periodId}'. Use Hive over shared_preferences if the data volume per entry exceeds 1 KB — Hive handles larger values more efficiently and supports type adapters. For the stale-while-revalidate pattern: use a StreamController that first adds the cached AsyncData, then triggers the network call and adds updated AsyncData or AsyncError when it resolves.
Abstract the storage layer behind a BufdirCacheStore interface so tests can inject an in-memory implementation without Hive/shared_preferences. Register the encryption key retrieval in a separate SecureStorageService to keep the repository focused on data concerns.
Testing Requirements
Unit tests using flutter_test and mocktail: (1) fresh cache miss triggers network fetch and stores result, (2) cache hit within TTL emits cached data and triggers background refresh, (3) background refresh on cache hit emits updated data after completion, (4) expired cache triggers network fetch (not cache), (5) period change invalidates old cache entry, (6) org switch clears all cache entries, (7) offline with valid cache emits AsyncData, (8) offline with no cache emits AsyncError with NetworkUnavailableException. Mock the storage adapter (Hive box or shared_preferences) using an in-memory fake. Mock connectivity_plus with a FakeConnectivity. Use fakeAsync to advance TTL without real time delays.
The preview repository depends on aggregated data produced by the Bufdir Data Aggregation feature. If the aggregation RPC schema or Supabase view columns change during parallel development, the repository's typed Dart models will break, causing compile errors or runtime null-dereference failures.
Mitigation & Contingency
Mitigation: Define a shared Dart interface (abstract class) for the aggregated data contract early and have both features code against it. Use Supabase typed generated clients so schema mismatches surface at code generation time rather than runtime.
Contingency: If the aggregation schema changes after the repository is complete, run `supabase gen types dart` immediately, update the repository model, and run repository unit tests before unblocking UI development. Keep a mock data fixture so UI work can continue during the fix.
The BufdirReportStructureMapper must map internal activity category IDs to the exact label strings used on the official Bufdir reporting form. If the mapping is incomplete or uses outdated labels, coordinators will see mismatches when cross-referencing the preview with the paper form, potentially leading to incorrect submissions.
Mitigation & Contingency
Mitigation: Obtain the current Bufdir reporting form PDF directly from Bufdir (Norse Digital Products has an existing Bufdir dialogue). Extract all field labels and section names into a static constants file reviewed by at least one coordinator from NHF or HLF before implementation begins.
Contingency: If incorrect labels are discovered during UAT on TestFlight, update the static constants file and redeploy. Because the mapper is a pure Dart class with no database storage, corrections require no migration — only a new build.