medium priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

The audit log section renders below the flag list within the admin screen with a clear section header
Each log entry displays: flag display name, previous state (enabled/disabled), new state (enabled/disabled), ISO 8601 timestamp formatted as locale-aware relative time (e.g., '2 hours ago') with full datetime on long-press, and the admin's display name
Initial page loads the most recent 20 entries; a 'Load more' button appends the next page using the cursor returned by the previous response
The 'Load more' button is hidden when no further pages exist (cursor is null)
The 'Load more' button shows a loading spinner while the next page is being fetched
Log entries are strictly read-only: no tap handlers, no selection highlight, no context menus
Empty state renders a descriptive placeholder ('No flag changes recorded yet') when the log has no entries
Error state renders an inline error message with a retry button if the initial load fails
Subsequent page load failures show a snackbar and do not remove already-loaded entries
All text meets WCAG 2.2 AA contrast requirements; state chips (enabled/disabled) use color plus text label, not color alone

Technical Requirements

frameworks
Flutter
Riverpod
apis
FeatureFlagRepository.getAuditLog(cursor: String?, pageSize: int): Future<AuditLogPage>
AuditLogPage model: { entries: List<AuditLogEntry>, nextCursor: String? }
data models
AuditLogEntry (flagId, flagName, previousState, newState, changedAt, changedByAdminName)
AuditLogPage (entries, nextCursor)
performance requirements
Initial page must render within 300ms of screen mount (use cached data if available)
Appending a new page must not cause full list rebuild; use ListView.builder with a growing item count
security requirements
Audit log endpoint must require admin role; unauthorized access returns 403 and triggers navigation to no-access screen
Admin names displayed in log must not expose email addresses or internal user IDs
ui components
AuditLogEntryTile (read-only ListTile variant)
StateBadge widget (colored chip with text for enabled/disabled)
LoadMoreButton with loading/idle states
AuditLogEmptyState widget
AuditLogErrorState widget with retry callback

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a Riverpod AsyncNotifier that holds accumulated entries and the current cursor; appending pages mutates this state immutably. Avoid re-fetching the first page on cursor append—only fetch the delta. Format timestamps with the timeago package or equivalent; store the raw DateTime for accessibility long-press. Wrap the log section in a SliverList inside a CustomScrollView shared with the flag list above it so both sections scroll together naturally.

Do not use a separate inner ScrollView—this causes nested scroll conflicts on mobile. The StateBadge for 'enabled' should use the design token for success-adjacent color (verify against styles.css token set) and 'disabled' should use muted/secondary color, always paired with text.

Testing Requirements

Widget tests using flutter_test and mocked FeatureFlagRepository: (1) first page renders correct number of entries with all required fields visible; (2) 'Load more' button appears when nextCursor is non-null; (3) 'Load more' button is absent when nextCursor is null; (4) tapping 'Load more' appends entries without replacing existing ones; (5) empty state renders when entry list is empty; (6) error state renders when initial load throws; (7) no tap handler is registered on any log entry tile. Also write a golden test to prevent unintended visual regressions on the AuditLogEntryTile.

Component
Feature Flag Admin Screen
ui medium
Epic Risks (3)
high impact low prob security

The feature flag admin screen allows persisting changes to organization_configs. If the role guard is implemented only client-side (checking role state in Riverpod), a user who manipulates their local role state could toggle flags for their organization without proper server-side authorization, potentially exposing features prematurely.

Mitigation & Contingency

Mitigation: Implement server-side authorization for flag update operations using Supabase RLS UPDATE policies that check the user's role in the memberships table. The client-side guard is UX only; the database enforces the actual restriction.

Contingency: If an unauthorized update is detected, audit the RLS policies and add a Supabase Edge Function as an authorization middleware for flag toggle operations, rejecting requests from non-admin role JWTs.

medium impact medium prob scope

Developers on other feature teams may use FeatureGate incorrectly — for example, wrapping business logic rather than UI, or using it before flag initialization completes — leading to features that are visible but non-functional or cause runtime errors when flags are queried in a loading state.

Mitigation & Contingency

Mitigation: Add assert statements in FeatureGate's build method that throw in debug mode if the provider is still in a loading state. Write developer documentation with a clear usage contract: FeatureGate is UI-only; logic gating must use the provider's isEnabled method directly. Include lint examples in the codebase.

Contingency: If misuse is found in code reviews, add a custom Dart lint rule via custom_lint that flags FeatureGate usage outside of the widget tree, and conduct a codebase audit to find existing violations.

medium impact low prob technical

If the audit log is stored in the same organization_configs table without pagination or archival strategy, high-frequency flag changes during pilot testing could produce an unbounded number of rows, degrading query performance on the admin screen.

Mitigation & Contingency

Mitigation: Store audit log entries in a separate feature_flag_audit_log table with an index on (organization_id, changed_at DESC). Implement cursor-based pagination in the repository and limit the initial load to 50 entries.

Contingency: If table size becomes a performance concern, add a Supabase scheduled function to archive entries older than 90 days to cold storage, and add a database index on changed_at for range queries.