high priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

After successful decryption, a Timer starts with a duration equal to the configured timeout (default 30 seconds)
When the timer fires, the field transitions to masked state and SemanticsService.announce('Field [label] is now hidden', TextDirection.ltr) is called — matching the _onRemask() method from task-009
The timeout duration is sourced from a SensitiveFieldConfiguration object passed as an optional parameter; if null, the default of 30 seconds is used
Re-tapping the lock icon to reveal the same field again restarts the timer from the full timeout duration
Manually re-masking via the unlock icon cancels the pending timer
If the widget is disposed before the timer fires, Timer.cancel() is called in dispose() and no setState is attempted after disposal
The countdown is not shown to the user (no visible progress bar) unless the SensitiveFieldConfiguration explicitly enables it — this is an optional future enhancement, not required now
Widget tests verify timer fires after the configured duration using fake_async
Widget tests verify timer is cancelled on dispose without triggering setState

Technical Requirements

frameworks
Flutter
Dart
apis
dart:async Timer
data models
SensitiveFieldConfiguration (component 616 interface — timeout: Duration)
performance requirements
Timer callback must be lightweight: only call _onRemask() which calls setState and SemanticsService.announce
No animation or transition on re-masking — instant state change to avoid any flash of partially-visible content
security requirements
On re-mask (whether by timer or manual), _plaintextValue must be set to null and the reference released so the GC can collect it
If the app is sent to background (AppLifecycleState.paused), immediately re-mask without waiting for the timer to fire — add AppLifecycleObserver to handle this

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Add `Timer? _remaskTimer` as a private field in _EncryptedFieldDisplayState. In _onDecryptionSuccess(), after setting state, call `_remaskTimer?.cancel(); _remaskTimer = Timer(widget.configuration?.remaskTimeout ?? const Duration(seconds: 30), _onRemask);`.

In _onRemask(): `_remaskTimer?.cancel(); _remaskTimer = null; setState(() { _isRevealed = false; _plaintextValue = null; }); SemanticsService.announce(...)`. In dispose(): `_remaskTimer?.cancel(); _remaskTimer = null; super.dispose();`. For AppLifecycleObserver: mix in WidgetsBindingObserver, register in initState via WidgetsBinding.instance.addObserver(this), and override didChangeAppLifecycleState: if state == AppLifecycleState.paused && _isRevealed → call _onRemask(). Remove the observer in dispose() with WidgetsBinding.instance.removeObserver(this).

The SensitiveFieldConfiguration class should be a simple immutable Dart class with a remaskTimeout Duration field — it can be a stub for now if component 616 is not yet implemented, with the widget accepting `SensitiveFieldConfiguration? configuration` as an optional named parameter.

Testing Requirements

Widget tests using fake_async (test/widgets/encrypted_field_display_timer_test.dart): (1) reveal field with timeout of 5 seconds → advance fake timer by 5 seconds → assert field is in masked state and plaintext is absent; (2) reveal field → manually re-mask via unlock icon → advance fake timer beyond 5 seconds → assert no double setState or error; (3) reveal field → dispose widget before timer fires → assert no 'setState called after dispose' error (use FlutterError listener); (4) reveal field → re-reveal before timeout → assert timer restarts (advance to original deadline, assert still revealed, then advance to new deadline, assert masked); (5) reveal field with null SensitiveFieldConfiguration → assert default 30-second timer is used. Use FakeAsync.elapse for all timer-related assertions.

Component
Encrypted Field Display Widget
ui high
Epic Risks (3)
medium impact medium prob scope

The encrypted-field-display confirmation dialog adds interaction steps that may frustrate coordinators who access sensitive fields frequently, leading to requests to bypass the flow or skip read-receipt logging, which would violate Blindeforbundet's compliance requirements.

Mitigation & Contingency

Mitigation: Design the confirmation dialog to be as minimal as possible (one clear sentence, single confirm action) and ensure it does not reappear for the same field within a single screen session. Validate the UX with Blindeforbundet coordinators during the TestFlight pilot before finalising.

Contingency: If coordinators raise strong objections, escalate to Blindeforbundet's data protection officer to determine whether a lighter confirmation pattern (e.g., biometric confirmation instead of dialog) satisfies their compliance obligation.

medium impact medium prob technical

The activity-history-list infinite scroll requires paginated Supabase queries. Contacts with hundreds of activities (e.g., an HLF peer mentor with 380 annual registrations) could cause slow page loads or memory pressure on older devices if pagination boundaries are set too large.

Mitigation & Contingency

Mitigation: Use a page size of 20 records with cursor-based pagination. Implement list item recycling using Flutter's ListView.builder. Benchmark memory usage with 400+ item simulation on a low-end test device before TestFlight release.

Contingency: If performance degrades on older devices, reduce page size to 10 and add a time-window filter (last 30 days, last 6 months, all) so the default view loads a manageable record count for most coordinators.

low impact high prob scope

The cognitive load rule engine (from the Cognitive Accessibility feature) mandates no more than 7 fields per screen section. If a contact model has more than 7 editable fields, the edit-contact-screen layout must be split into sections, adding complexity not accounted for in the initial scope.

Mitigation & Contingency

Mitigation: Audit the full contact field list from all four organisations before implementation. Group fields into logical sections (personal info, contact details, affiliation) so no single section exceeds 7 fields. Use the cognitive-load-rule-engine component if it is already delivered by the Cognitive Accessibility feature.

Contingency: If the rule-engine component is not yet available, implement a simple manual section layout with accordion-style expansion for less-frequently edited fields to stay within the 7-field guideline without blocking delivery.