Implement ContactSearchBar with debounce and clear button
epic-contact-list-management-ui-components-task-006 — Build the ContactSearchBar widget as a styled Flutter TextField with a 300ms debounce on value changes to prevent excessive provider calls. Include a clear (X) icon button that resets the search query and returns focus to the input. Provide an accessible label and hint text via semanticsLabel and decoration hintText. Expose an onChanged stream/callback and a controller for programmatic control by the parent ContactListScreen.
Acceptance Criteria
Technical Requirements
Implementation Notes
Implement debounce using a nullable Timer _debounce field in a StatefulWidget. In the TextField's onChanged handler: cancel any existing timer, then start a new Timer(Duration(milliseconds: 300), () => widget.onChanged(value)). In dispose(), call _debounce?.cancel() to prevent timer leaks. For the clear button: use a StatefulWidget local bool _hasText and update it via setState in the same onChanged handler β conditionally render the suffix icon based on this flag.
For the controller: if widget.controller is provided, use it directly; otherwise create an internal _controller and dispose it in dispose(). This dual-controller pattern avoids memory leaks when the parent manages state. The parent (ContactListScreen or Riverpod provider) should use the debounced value to filter the contact list β the ContactSearchBar itself is purely presentational and has no knowledge of providers.
Testing Requirements
Write flutter_test widget tests: (1) assert clear button is absent when field is empty and present after typing; (2) tap clear button and assert field value is empty and focus is returned; (3) simulate rapid keypresses using tester.enterText() in quick succession, then advance fake timer by 300ms and assert onChanged fires exactly once with the final value; (4) advance timer by 299ms and assert onChanged has NOT fired; (5) dispose widget mid-debounce and assert no callbacks fire after disposal (no late timer exceptions); (6) provide an external TextEditingController, programmatically set text, and assert onChanged fires after debounce. Use flutter_test's FakeAsync or pump(Duration) to control timer advancement without real-time waits.
Design token color values used in role badges, certification status indicators, and availability chips may not meet the WCAG 2.2 AA contrast ratio of 4.5:1 when rendered against card backgrounds, requiring rework after accessibility review and potentially blocking acceptance sign-off.
Mitigation & Contingency
Mitigation: Run the contrast-ratio-validator on every new token combination during widget development. Enforce the CI accessibility lint runner on all PRs touching visualization components, and validate against the contrast-safe-color-palette before finalizing card designs.
Contingency: If contrast failures are found late, adjust token values in the design token theme centrally β since all widgets consume design tokens rather than hardcoded colors, all affected widgets will be corrected by a single token update without per-widget changes.
The ContactViewSwitcher is required for Barnekreftforeningen but must not appear for other organizations. If the organization labels provider does not yet expose a reliable feature flag for this widget, it may render universally or be conditionally hidden in an inconsistent way, breaking the role-specific layout contract.
Mitigation & Contingency
Mitigation: Implement view switcher visibility as a constructor parameter on ContactListScreen injected from a provider, defaulting to hidden. Document the integration point for the org labels provider so the flag can be wired without changing the widget's API.
Contingency: If org labels integration is delayed beyond this epic, use a feature flag constant keyed to the Barnekreftforeningen organization ID as a temporary gate, with a tracked issue to replace it with the runtime labels provider before general release.