critical priority low complexity infrastructure pending infrastructure specialist Tier 1

Acceptance Criteria

A file accessibility_lint_rules.yaml is created at the project root containing a structured YAML document with a top-level 'accessibility_rules' key
The YAML file lists at minimum: no_hardcoded_colors (maps to WCAG 1.4.3), no_italic_font_style (maps to WCAG 1.4.3 readability), no_font_weight_below_w400 (maps to WCAG 1.4.3)
Each rule entry includes: rule_id, description, wcag_criterion, severity (error|warning), and an example_violation code snippet as a YAML multiline string
analysis_options.yaml is updated to include: analyzer.errors entries that elevate detected violations to errors and an include: path to accessibility_lint_rules.yaml
Running dart analyze on a file containing Color(0xFFFF0000) literal produces an analyzer error referencing the no_hardcoded_colors rule
Running dart analyze on a file containing fontStyle: FontStyle.italic produces an analyzer error referencing the no_italic_font_style rule
Running dart analyze on a file containing fontWeight: FontWeight.w300 produces an analyzer error referencing the no_font_weight_below_w400 rule
Running dart analyze on a file using only designTokenProvider values produces zero errors and zero warnings
The configuration files are committed to version control and referenced in the CI pipeline documentation

Technical Requirements

frameworks
Flutter
Dart analyzer
custom_lint (if required for custom rules)
performance requirements
dart analyze with the accessibility configuration must complete within 60 seconds on a standard CI runner for a project of typical size
security requirements
Lint configuration must not disable any existing security-related lint rules (avoid_print, avoid_web_libraries_in_flutter, etc.)
Configuration changes must be reviewed in PRs — no direct push to main bypassing lint checks

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Dart's built-in analyzer supports custom lint rules via the custom_lint package (riverpod_lint is an example). For the three required rules, evaluate whether they can be enforced with built-in lints first: (1) no_hardcoded_colors — there is no official built-in lint for this; implement using custom_lint with an AstVisitor that flags InstanceCreationExpression nodes for Color() with literal arguments; (2) no_italic_font_style — similarly requires a custom_lint visitor flagging FontStyle.italic named argument references; (3) no_font_weight_below_w400 — requires detecting FontWeight.w100/w200/w300 references. If custom_lint is added, it becomes a dev_dependency. Structure accessibility_lint_rules.yaml as human-readable documentation for the rules, not as machine-executed configuration — the actual enforcement is via custom_lint Dart code.

The YAML file serves as the single source of truth for rule intent, linked from both code comments in the custom lint implementation and from the developer documentation. This dual-layer approach (machine enforcement + human-readable spec) is important for the multi-organization context: when a new developer from the NHF or Blindeforbundet partner teams opens a lint error, the YAML spec gives them immediate context on why the rule exists and which WCAG criterion it enforces.

Testing Requirements

Validation via intentional violation fixtures (see also task-013 for CI integration). For this task, create test/lint_fixtures/ with three violation files and one clean file. Manually run dart analyze on each fixture and confirm exit codes before committing. Document the manual validation results in the PR description.

For automated ongoing validation, the CI step added in task-013 will provide regression coverage. Additionally, verify that adding a new violation type to a fixture file causes a new lint error to appear (i.e., lint rules are not silently ignored).

Component
CI Accessibility Lint Runner
infrastructure medium
Epic Risks (4)
medium impact high prob integration

Flutter's textScaleFactor behaviour differs between iOS and Android, and third-party widgets used across the app (date pickers, bottom sheets, chips) may not respect the per-role scale caps applied by the dynamic-type-scale-service, causing overflow in screens this epic cannot directly control.

Mitigation & Contingency

Mitigation: Enumerate all third-party widget usages that render text. For each, verify whether they honour the inherited DefaultTextStyle and MediaQuery.textScaleFactor or use hardcoded sizes. File issues with upstream packages and wrap non-compliant widgets in MediaQuery overrides scoped to the safe cap for that role.

Contingency: If upstream packages cannot be patched within the sprint, implement a global MediaQuery wrapper at the app root that clamps textScaleFactor to the highest per-role safe value (typically 1.6–2.0), accepting that users at extreme OS scales see a safe cap rather than full scaling for those widgets.

high impact medium prob dependency

The CI accessibility lint runner depends on the Dart CLI toolchain and potentially custom_lint or a bespoke Dart script. CI environments differ from local dev environments in Dart SDK version, pub cache configuration, and platform availability, risking intermittent CI failures that block all pull requests.

Mitigation & Contingency

Mitigation: Pin the Dart SDK version in the CI workflow configuration. Package the lint runner as a self-contained Dart script with all dependencies vendored or declared in a dedicated pubspec.yaml. Add a CI smoke test that runs the runner against a known-compliant fixture and a known-violating fixture to verify the exit codes are correct.

Contingency: If the custom runner proves too fragile, fall back to running dart analyze with the flutter-accessibility-lint-config rules as the sole CI gate, and schedule the custom manifest validation as a separate non-blocking advisory check until the runner is stabilised.

medium impact medium prob technical

Wrapping all interactive widgets with a 44 pt minimum hit area via HitTestBehavior.opaque may cause unintended tap interception in widgets where interactive elements are closely stacked, particularly in the expense type selector, bulk confirmation screen, and notification filter bar.

Mitigation & Contingency

Mitigation: Conduct integration testing of the touch target wrapper specifically in dense layout scenarios (expense selector, filter bars, bottom sheets with multiple buttons). Use the Flutter Inspector to visualise hit areas and confirm no overlaps. Pair with the interactive-control-spacing-system to ensure minimum 8 dp gaps between expanded hit areas.

Contingency: If overlapping hit areas cause mis-tap regressions in specific screens, allow the touch target wrapper to accept an explicit hitAreaSize parameter that can be reduced below 44 pt only in contexts where the interactive-control-spacing-system guarantees sufficient gap, with a mandatory code review flag for any such override.

high impact medium prob scope

The contrast-safe-color-palette must guarantee WCAG AA ratios for both light and dark mode token sets. Dark mode color derivation is non-trivial — simply inverting a light palette often produces pairs that pass in one mode but fail in the other, and the token manifest must encode both sets explicitly.

Mitigation & Contingency

Mitigation: Define both light and dark token sets explicitly in the accessibility-token-manifest rather than deriving one from the other programmatically. Run the contrast-ratio-validator against both sets as part of the token manifest generation process and include both in the CI lint runner's validation scope.

Contingency: If time pressure forces a dark mode deferral, ship with light mode only and add a prominent in-app notice. Gate dark mode colour tokens behind a feature flag until the full dual-palette validation is complete.