critical priority low complexity database pending database specialist Tier 1

Acceptance Criteria

A Drift table class CachedContactsTable is defined with columns: id (TextColumn, primary key), name (TextColumn, not null), organisation (TextColumn, nullable), chapterAffiliation (TextColumn, nullable), role (TextColumn, nullable), syncedAt (DateTimeColumn, not null)
A Drift table class CachedNotesTable is defined with columns: id (TextColumn, primary key), contactId (TextColumn, not null, references CachedContactsTable.id), content (TextColumn, not null), updatedAt (DateTimeColumn, not null)
A Drift index is declared on CachedContactsTable for the name column to support fast LIKE queries
A Drift index is declared on CachedContactsTable for the organisation column
A Drift index is declared on CachedNotesTable for the contactId column (foreign key lookup)
The AppDatabase class includes both tables and both indexes in its @DriftDatabase annotation tables list
Running flutter pub run build_runner build generates Drift companion and data classes without errors
The generated schema migration is compatible with the existing app database version (increment schemaVersion and provide migration step)

Technical Requirements

frameworks
Flutter
Drift (drift: ^2.x)
data models
CachedContactsTable
CachedNotesTable
ContactSearchResult
performance requirements
Indexed LIKE queries on name and organisation columns must return results in under 100ms for a cache of up to 10,000 contacts on a mid-range Android device
security requirements
The local SQLite database must be stored in the app's private data directory (getApplicationDocumentsDirectory) β€” not on external storage
Do not store sensitive personal data (e.g., health information) in the cache tables β€” only contact directory fields

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define Drift table classes in lib/features/contact_search/data/local/tables/. Keep the table definitions in separate files (cached_contacts_table.dart, cached_notes_table.dart) and include them in the AppDatabase part file. For the name and organisation LIKE queries, Drift's LIKE operator is available via (table.name.like('%$query%')) in a CustomExpression or via Drift's expression API. Note that SQLite LIKE is case-insensitive for ASCII characters but NOT for Unicode β€” for Norwegian names (Γ¦, ΓΈ, Γ₯), use LOWER() on both sides of the comparison or consider using the unicode61 tokenizer if full-text search is added later.

The syncedAt column on CachedContactsTable enables cache invalidation: the sync service can purge rows older than a configurable TTL.

Testing Requirements

Write unit tests using an in-memory Drift database (NativeDatabase.memory()) to verify: (1) inserting a CachedContact row and querying it back returns the same data, (2) inserting a CachedNote with a valid contactId succeeds, (3) inserting a CachedNote with a non-existent contactId fails (foreign key constraint β€” enable PRAGMA foreign_keys = ON in the test setup), (4) a LIKE query on name returns matching rows and excludes non-matching rows. Use flutter_test for all tests. Assert that schema migration from version N to N+1 does not throw.

Epic Risks (3)
high impact medium prob security

Supabase RLS policies may not correctly scope ilike search results to the authenticated user's organisation and chapter, causing data leakage across organisations or empty result sets for valid queries.

Mitigation & Contingency

Mitigation: Reuse and extend existing RLS query builder patterns from the contact-list-management feature. Write integration tests against a seeded multi-organisation test database to verify cross-org isolation before merging.

Contingency: If RLS scoping is insufficient, add an explicit organisation_id filter in the Dart query builder layer as a defence-in-depth measure while the Supabase policy is corrected.

medium impact medium prob integration

Adding new Drift tables for the contact cache may conflict with existing migrations or schema versions in the contact-list-management feature if both features cache the same contacts table, causing migration failures on user devices.

Mitigation & Contingency

Mitigation: Audit existing Drift schema versions from contact-list-management before writing new migrations. Reuse existing cache tables if the schema already covers required fields; only add missing fields via ALTER or new version.

Contingency: If schema conflict occurs, consolidate into a single shared cache table owned by contact-list-management and expose a DAO interface to the search feature, avoiding duplicated schema ownership.

medium impact medium prob scope

The offline cache may surface significantly stale contact data if sync has not run recently, leading coordinators to act on outdated information (wrong phone numbers, changed assignments).

Mitigation & Contingency

Mitigation: Store and surface the last-sync timestamp prominently in the UI layer. Trigger a background cache refresh on app foreground when connectivity is detected.

Contingency: If staleness becomes a reported UX issue, implement a maximum-age threshold that shows a warning banner when the cache is older than a configurable limit (e.g. 24 hours).