high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

InlineContextualHelpWidget renders as an info icon (Icons.info_outline or equivalent design-token icon) that toggles an inline expanded panel on tap — no modal, no bottom sheet, no overlay
When collapsed, the icon has a semantic label of 'Show help for [field label]' where field label is a required String parameter
When expanded, the help panel displays the resolved help text (≤80 words) from HelpContentRegistry via PlainLanguageContentService with org-specific overrides already applied
When expanded, semantic label of the icon changes to 'Hide help for [field label]' and focus moves to the expanded help text node automatically
Pressing the icon again collapses the panel and returns focus to the icon
The expansion animation completes in ≤200ms with AnimatedSize or AnimatedCrossFade — no janky layout jumps
The expanded text is wrapped in a Semantics widget that is read as a single paragraph by screen readers, not as individual word nodes
If HelpContentRegistry has no entry for the provided helpKey, the widget renders nothing (SizedBox.shrink()) — it does not show a broken or empty help panel
The widget works correctly at all Flutter text scale factors (1.0–2.0) without text overflow
The info icon touch target is minimum 44×44dp even when the icon glyph is smaller
Org-specific terminology overrides are applied transparently — the widget does not need to know which org is active

Technical Requirements

frameworks
Flutter
apis
HelpContentRegistry.resolve(helpKey)
PlainLanguageContentService.applyOrgOverrides(content, orgId)
OrganizationTerminologyService.getActiveOrgId()
data models
HelpContent (helpKey, text, maxWords)
OrgTerminologyOverride
performance requirements
Help content resolution must be synchronous (pre-loaded into memory at app start) — no async gap during build that could cause frame drops
Animation must maintain 60fps — avoid rebuilding ancestor widgets during AnimatedSize expansion
security requirements
Help text is static content loaded from local registry — no network fetch at render time, preventing MITM injection
Help text must be HTML-escaped if any dynamic content is interpolated to prevent XSS in web platform targets
ui components
InlineContextualHelpWidget (StatefulWidget)
HelpToggleIcon (IconButton with Semantics)
HelpTextPanel (AnimatedSize + Text)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a StatefulWidget (not a Cubit) because the expanded/collapsed state is purely local UI state with no persistence or cross-widget coordination needed — this keeps the widget self-contained. Use AnimatedSize wrapped around the help text container for smooth height animation without requiring an explicit animation controller. To move focus to the help text on expand, call FocusScope.of(context).requestFocus(helpTextFocusNode) inside a post-frame callback (WidgetsBinding.instance.addPostFrameCallback) so the widget is fully laid out before focus is applied. Do NOT use a Tooltip widget for this feature — Flutter Tooltips are shown on long-press only and are not accessible on touch-primary devices.

The ≤80-word constraint should be enforced at registry load time (a lint/test), not at render time, so the widget does not need truncation logic.

Testing Requirements

Write flutter_test widget tests covering: (1) widget renders icon-only when collapsed, (2) tap expands panel and shows resolved help text, (3) second tap collapses panel, (4) Semantics label changes between 'Show help' and 'Hide help' based on state, (5) focus moves to help text node on expansion (use FocusManager in test), (6) widget renders SizedBox.shrink() when helpKey has no registry entry, (7) text scale factor 2.0 does not cause overflow, (8) icon touch target bounding box is ≥44dp in both axes. Include a golden test for both collapsed and expanded states at 1.0 and 1.5 text scale. Target 100% line coverage.

Component
Help Content Registry
data low
Epic Risks (4)
high impact medium prob technical

The error message registry and help content registry both depend on bundled JSON assets loaded at startup. If asset loading fails silently (e.g. malformed JSON, missing pubspec asset declaration), the entire plain-language layer falls back to empty strings or raw error codes, breaking the accessibility guarantee app-wide.

Mitigation & Contingency

Mitigation: Implement eager validation of both assets during app initialisation with an assertion failure in debug mode and a structured error log in release mode. Add integration tests that verify asset loading in the Flutter test harness on every CI run.

Contingency: Ship a hardcoded minimum-viable fallback message set directly in Dart code so the app always has at least a safe generic message, preventing a blank or code-only error surface.

medium impact medium prob dependency

The AccessibilityDesignTokenEnforcer relies on dart_code_metrics custom lint rules. If the lint toolchain is not already configured in the project's CI pipeline, integrating a new linting plugin may cause unexpected build failures or require significant CI configuration work beyond the estimated scope.

Mitigation & Contingency

Mitigation: Audit the existing dart_code_metrics configuration in the project before starting implementation. Scope the lint rules to a separate Dart package that can be integrated incrementally, starting with the most critical rule (hard-coded colors) and adding others in subsequent iterations.

Contingency: Fall back to Flutter test-level assertions (using the cognitive-accessibility-audit utility) to catch violations in CI if the lint plugin integration is delayed, preserving enforcement coverage without blocking the epic.

medium impact low prob technical

WizardDraftRepository must choose between shared_preferences and Hive for local persistence. Choosing the wrong store for the data volume (e.g. shared_preferences for complex nested wizard state) can lead to serialisation bugs or performance degradation, particularly on lower-end Android devices used by some NHF members.

Mitigation & Contingency

Mitigation: Define a clean repository interface first and implement shared_preferences as the initial backend. Profile serialisation round-trip time with a realistic wizard state payload (≈10 fields) before committing to either store.

Contingency: Swap the persistence backend behind the repository interface without touching wizard UI code, which is possible precisely because the repository abstraction isolates the storage detail.

medium impact high prob scope

The AccessibilityDesignTokenEnforcer scope could expand significantly if a large portion of existing widgets use hard-coded values. Discovering widespread violations during this epic would force either a major refactor or a decision to exclude legacy components, potentially reducing the enforcer's coverage and value.

Mitigation & Contingency

Mitigation: Run a preliminary audit of existing widgets using a simple grep for hard-coded hex colors and raw pixel values before implementation begins. Use the results to set a realistic remediation boundary for this epic and log all out-of-scope violations as tracked tech-debt items.

Contingency: Scope the enforcer to new and modified components only (via file-path filters in dart_code_metrics config), shipping a partial but immediately valuable coverage rather than blocking the epic on full-codebase remediation.