medium priority low complexity infrastructure pending backend specialist Tier 0

Acceptance Criteria

Validator exposes a validate(ContactFormData data) method returning a ContactFormValidationResult with per-field error messages
Norwegian personnummer validation follows the official Modulus 11 algorithm and rejects invalid check digits
Norwegian phone number validation accepts 8-digit numbers with optional +47 prefix and rejects invalid formats
Email validation uses RFC 5322-compatible regex and rejects addresses without a domain
Required fields (name, phone or email) produce a RequiredFieldError when empty or whitespace-only
Validator returns all field errors simultaneously (not fail-fast) so inline UI can show all errors at once
Validation result is an immutable value object with a boolean isValid convenience getter
Validator is a plain Dart class with no Flutter or Supabase dependencies — usable in any layer
All validation rules are documented via inline comments referencing the source rule (e.g., Skatteetaten personnummer spec)

Technical Requirements

frameworks
Dart (pure, no Flutter dependency)
freezed (for result types)
data models
ContactFormData
ContactFormValidationResult
FieldValidationError
performance requirements
Synchronous validation — no async calls
Validate full form in under 5ms
security requirements
Do not log personnummer values in error messages or debug output
Validation errors must not reveal whether a personnummer belongs to a real person

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a stateless class ContactFormValidator with a single public validate() method. Keep each field rule in a private named method (e.g., _validatePersonnummer, _validatePhone) for readability and independent testability. For personnummer: extract birth digits and control digits, apply the two Modulus 11 weight sequences per the Norwegian Tax Authority specification, and handle the edge case where check digit 10 is invalid (no valid number exists). Do not use third-party validation packages — the logic is simple enough and adding a package creates a maintenance dependency.

Return a Map> keyed by field name so the UI can trivially look up errors per field.

Testing Requirements

Pure unit tests with flutter_test (no widget tests needed). Cover: valid personnummer (Modulus 11 passes), invalid personnummer (wrong check digit), personnummer with invalid birth date, valid Norwegian mobile number, number with +47 prefix, 7-digit number (too short), valid email, email missing @, email missing domain, all required fields empty, partial required fields, full valid form returns isValid true. Target 100% branch coverage — this is a pure logic class with no I/O.

Component
Contact Detail Screen
ui medium
Epic Risks (2)
low impact medium prob dependency

The Peer Mentor Profile tab on the contact detail screen depends on the peer-mentor-detail-screen-widget being delivered by the separate Peer Mentor Detail feature. If that feature is delayed, the navigation affordance will be present but lead to a stub screen, which may confuse coordinators in the TestFlight pilot.

Mitigation & Contingency

Mitigation: Implement the peer mentor tab with a feature flag guard. When the Peer Mentor Detail feature is incomplete, the flag disables the tab. Coordinate delivery timelines with the team responsible for Peer Mentor Detail to align TestFlight releases.

Contingency: If the Peer Mentor Detail feature is significantly delayed, ship the contact detail screen without the peer mentor tab in the first TestFlight build and add it as an incremental update once the dependent screen is ready.

medium impact medium prob technical

The contact detail screen must adapt its layout significantly based on organisation context: NHF shows affiliation chips, Blindeforbundet shows encrypted fields and assignment status, standard contacts show neither. Managing this conditional rendering without introducing bugs in each variant is complex and increases the risk of organisation-specific regressions.

Mitigation & Contingency

Mitigation: Define a ContactDetailViewModel that resolves all org-specific flags (showEncryptedFields, showAssignmentStatus, showMultiChapterChips) from the organisation config before the widget tree renders. Widget tests must cover all three organisation variants as separate test cases to catch regressions.

Contingency: If conditional rendering logic grows unwieldy, refactor into separate composable section widgets (ProfileHeaderSection, AffiliationSection, EncryptedFieldsSection) that are conditionally included by the parent screen, isolating org-specific logic to individual components.