Build stateless LabelKeyResolver with fallback logic
epic-dynamic-terminology-and-labels-foundation-task-004 — Implement LabelKeyResolver as a stateless utility class with a single resolve(String key, Map<String, String> labels, {Map<String, String> params}) method. Apply fallback chain: org-specific label → default English label from LabelKeyRegistry → formatted key string. Implement parameterized substitution replacing {{paramName}} placeholders with values from the params map. All methods are pure functions with no side effects.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
The default English labels map should be populated by copying the constant values from LabelKeyRegistry and providing human-readable English defaults. Do not use LabelKeyRegistry constants as the default value — the constant is a key, not a label. Maintain a separate `_defaultLabels` map. For the formatted-key fallback, implement a simple `_formatKey(String key)` helper that splits on `.` and `_`, capitalizes each word, and joins with spaces — this ensures the UI is always readable even during development before all labels are configured.
For parameterized substitution, use `String.replaceAllMapped(RegExp(r'\{\{(\w+)\}\}'), (m) => params?[m.group(1)] ?? m.group(0)!)` — compile the RegExp as a static field to avoid re-compilation. Keep this class free of any Flutter imports — it is pure Dart and must be testable without a Flutter environment.
Testing Requirements
Unit tests in `label_key_resolver_test.dart` using `flutter_test`. Group by behavior: (1) fallback chain — test each of the 3 fallback levels independently, (2) parameterized substitution — test single param, multiple params, missing param leaves placeholder, null params map treated as empty, (3) edge cases — empty string in labels map falls through, null-safe behavior with empty maps, key not in LabelKeyRegistry returns formatted fallback. Use `const` test data to keep tests fast and deterministic. Assert exact output strings, not just non-null.
Target: all 3 branches of the fallback chain explicitly tested.
The labels JSONB column in organization_configs may lack a consistent schema across organizations, causing deserialization failures or silent missing keys when a new organization is onboarded with a differently structured map.
Mitigation & Contingency
Mitigation: Define and enforce a canonical JSONB schema via a Supabase check constraint and a migration script. Validate the schema in TerminologyRepository at parse time and emit structured errors for any key-type mismatches.
Contingency: If schema drift is discovered post-deployment, LabelKeyResolver's fallback logic ensures the app continues to function with English defaults while a data migration is prepared to normalize the offending organization's config.
Device local storage corruption or platform-specific SharedPreferences serialization bugs could render a cached terminology map unreadable, causing the app to fall back to English defaults unexpectedly for an organization with custom terminology.
Mitigation & Contingency
Mitigation: Wrap all cache reads in try/catch, validate the deserialized map against a minimum-required-keys check, and evict corrupted entries automatically before re-fetching from Supabase.
Contingency: Surface a non-blocking in-app warning to the coordinator that terminology may be in default English until the next sync completes; trigger an immediate background sync.