critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

A HelpContentEntry Dart model is defined with fields: contextKey (String, required), explanation (String, required, ≤80 words enforced via debug assertion), illustrationRef (String?, optional — references an asset image path or icon name), orgOverrideKey (String?, optional — identifies which org terminology variant this entry applies to)
A HelpContentRegistry Dart class wraps a Map<String, HelpContentEntry> and exposes: lookup(String contextKey, {String? orgId}) returning HelpContentEntry? with org-override resolution, and a fallbackEntry for unknown keys
Org-specific override resolution: if an org-specific entry exists for the given orgId+contextKey, it is returned; otherwise the default entry for contextKey is returned
The base registry is loaded from `assets/help_content/help_content.json`; org-specific overrides are loaded from `assets/help_content/overrides/{orgId}.json` if the file exists
A Riverpod FutureProvider family (helpContentRegistryProvider(String orgId)) loads the base + org override and merges them, caching per orgId
The provider transitions through AsyncValue loading/data/error states correctly
A missing org override file is handled gracefully — provider falls back to base registry without throwing
lookup() with an unknown contextKey returns a generic help entry ('Tap for more information') rather than null
All explanation texts in bundled JSON are in Norwegian (bokmål) as the primary language, with English context keys
The JSON schema supports the organization labels system (dynamic terminology per org) documented in the architecture — override files allow orgs to replace terms like 'likeperson' with org-specific equivalents

Technical Requirements

frameworks
Flutter
Riverpod
data models
HelpContentEntry
HelpContentRegistry
performance requirements
Combined base + override asset loading must complete within 100ms
Registry merge (base + override) must be O(n) where n is number of override entries
Provider family caches per orgId — repeated calls with the same orgId do not re-read assets
security requirements
Help content is read-only bundled content — no user input is stored in the registry
Illustration references must point only to bundled asset paths — no external URLs that could be injected

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the same asset-loading pattern as task-003 (FutureProvider + rootBundle.loadString) to keep the two registries consistent. For the merge logic, create a static HelpContentRegistry.merge(base, override) factory that returns a new registry where override entries replace base entries with the same contextKey — do not mutate the base registry. The org override file path convention `assets/help_content/overrides/{orgId}.json` should be documented in CLAUDE.md so content authors know where to add new org files. Declare all potential override paths in pubspec.yaml as a directory asset (`assets/help_content/overrides/`) so Flutter bundles them all.

The illustrationRef field is intentionally flexible — it can point to a Flutter asset path (`assets/images/help_activity.png`) or a named icon string (`icon:info_outline`) — document the convention in the model's dartdoc. The connection to ErrorMessageRegistry (task-003): when an ErrorMessageEntry has a helpLinkId, that ID is used as the contextKey for lookup() in this registry — implement and test this lookup chain. Consider adding a `learnMoreUrl` field as nullable for future use, but leave it null in all bundled entries for now.

Testing Requirements

Unit tests: test HelpContentRegistry.lookup() with a known base key (returns base entry), a known key with an org override (returns override entry), and an unknown key (returns fallback). Test org override merge logic: construct a registry with a base and override map and verify override entries take precedence over base entries with the same contextKey. Test that a missing org override file does not throw — provider should resolve to base registry. Asset loading test: in flutter_test with a fake asset bundle, verify helpContentRegistryProvider(orgId) loads, merges, and caches correctly.

Schema validation test: load the actual bundled JSON files in a test and assert every entry's explanation is within the 80-word limit. Word-count test: write a word-count assertion utility and apply it to all explanation fields in the test suite to prevent content authors from exceeding the limit.

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.