high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

When coordinator taps Proceed, logWarningAcknowledged(warningId, coordinatorId, timestamp) is called exactly once with correct arguments before the dialog closes
When coordinator taps Cancel, logWarningDismissed() is called exactly once before the dialog closes
The coordinatorId used in the log call matches the currently authenticated user's ID from the session
The timestamp used in the log call is a UTC DateTime generated at the moment of button tap, not a stale pre-computed value
The DialogResult.proceed value is returned to the parent widget after a successful Proceed log call
The DialogResult.cancelled value is returned to the parent widget after a Cancel log call
If logWarningAcknowledged throws an exception, the dialog shows an inline error message and does NOT close — the coordinator can retry
Audit log calls are non-blocking from the UI perspective: a loading indicator is shown on the Proceed button during the async log call
The Supabase audit log table receives a correctly structured row for each decision
Unit tests mock DuplicateWarningEventLogger and verify call arguments and call count

Technical Requirements

frameworks
Flutter
BLoC
apis
DuplicateWarningEventLogger.logWarningAcknowledged(warningId, coordinatorId, timestamp)
DuplicateWarningEventLogger.logWarningDismissed(warningId)
Supabase (via logger repository)
data models
DuplicateActivityWarning
AuditLogEntry
DialogResult
performance requirements
Audit log write must complete within 3 seconds on standard network; show loading state if it takes longer than 300ms
security requirements
coordinatorId must be sourced from the authenticated session token — never from UI state or dialog parameters
Audit log entries must be immutable once written — no update or delete operations permitted on log rows
Log calls must use server-side timestamp (database NOW()) as the authoritative record, with client timestamp as metadata
ui components
DuplicateActivityWarningDialog (updated with async Proceed handler)
AppButton (Proceed — with loading state support)
InlineErrorMessage (for log failure feedback)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Inject DuplicateWarningEventLogger into the dialog via constructor parameter (not service locator) to keep the widget testable. Make the Proceed button's onPressed an async handler: set a local isLoading state variable (using StatefulWidget or a simple ValueNotifier) to show a loading indicator, then await the log call, then pop with the result. Use a try/catch around the log call to catch Supabase or network errors and surface them as an inline error string below the buttons — do not use a SnackBar as it may not be accessible from within the modal focus trap. Source coordinatorId from a repository/auth service injected at the call site — the parent widget should pass it in as part of DuplicateActivityWarning or as a separate constructor argument.

Keep the logger interface thin: logWarningAcknowledged and logWarningDismissed should both return Future and throw on failure so the dialog can handle errors consistently.

Testing Requirements

Write unit tests using flutter_test and mockito (or mocktail): (1) mock DuplicateWarningEventLogger and verify logWarningAcknowledged is called with correct warningId, coordinatorId, and a recent UTC timestamp when Proceed is tapped, (2) verify logWarningDismissed is called when Cancel is tapped, (3) verify DialogResult.proceed is returned on successful acknowledge, (4) verify DialogResult.cancelled is returned on cancel, (5) simulate logWarningAcknowledged throwing an exception and assert the dialog remains open with an error message displayed. Test the loading state by using a completer to delay the mock and assert the Proceed button shows a loading indicator during the async call.

Component
Duplicate Activity Warning Dialog
ui medium
Epic Risks (3)
high impact medium prob technical

The Duplicate Activity Warning Dialog must announce itself to VoiceOver and TalkBack immediately on appearance. Flutter's default modal semantics do not guarantee focus shift to the dialog on all platform versions, risking silent appearance for screen reader users — a WCAG 2.2 failure.

Mitigation & Contingency

Mitigation: Wrap the dialog content in a Semantics node with liveRegion: true and explicitly request focus via FocusScope.of(context).requestFocus() on the dialog's primary action button in the post-frame callback. Test on physical iOS (VoiceOver) and Android (TalkBack) devices, not only simulators.

Contingency: If automatic focus fails on a specific platform version, add a platform-specific fallback using SemanticsService.announce() to force a live region announcement of the dialog's headline text.

medium impact medium prob technical

The Chapter Membership Cubit tracks pending changes before commit to support the two-step add/confirm flow. If the user navigates away mid-edit or the app is backgrounded, uncommitted pending state could be replayed incorrectly on return, causing phantom affiliation additions or removals.

Mitigation & Contingency

Mitigation: Design the Cubit to hold pending changes only in transient in-memory state with no persistence. On any navigation-away event, emit a reset state that discards pending changes. Prevent accidental navigation during an active edit by showing a discard-changes confirmation dialog.

Contingency: If state desync is reported in production, add an explicit state reconciliation step in the Cubit's onResume handler that re-fetches the authoritative affiliation list from the repository and resets all pending state before re-rendering.

medium impact high prob technical

The Chapter Assignment Editor's searchable chapter list must load quickly. If the organisation has hundreds of chapters (NHF has 1,400 local chapters) and the full list is fetched on dialog open, the editor will be slow to display and the search will be sluggish.

Mitigation & Contingency

Mitigation: Scope the chapter list to only chapters within the coordinator's administrative scope (not all 1,400), leveraging the existing hierarchy access scope service. Implement server-side search with a minimum 2-character threshold and debounce to avoid excessive Supabase queries.

Contingency: If the scoped list is still too large, add local caching of the chapter list with a 15-minute TTL and an explicit refresh button, ensuring the editor is always responsive even on poor network conditions.