critical priority medium complexity infrastructure pending infrastructure specialist Tier 2

Acceptance Criteria

Entrypoint is bin/accessibility_lint.dart, executable via dart run bin/accessibility_lint.dart from project root
Script accepts optional --path <directory> argument defaulting to lib/ for the scan root
Script accepts optional --min-contrast <value> argument defaulting to 4.5 (WCAG AA normal text)
Contrast check: detects Color() or Color.fromARGB() / Color.fromRGBO() literal pairs used as foreground/background in the same widget build method and validates ratio using ContrastRatioValidator from task-004
Touch target check: detects SizedBox, Container, or ConstrainedBox with explicit width or height < 44 that wraps a GestureDetector, InkWell, or ElevatedButton
Font style check: detects TextStyle declarations with fontStyle: FontStyle.italic or fontWeight values below FontWeight.w400 (w100–w300)
JSON report written to stdout has structure: { violations: [ { file, line, column, rule, message, severity } ], summary: { total, errors, warnings } }
Exit code is 0 when no violations found, 1 when any error-severity violation exists
Warnings (e.g., soft contrast violations between 3.0–4.5) do not cause non-zero exit code but appear in report
Script handles parse errors in individual files gracefully — logs a warning and continues scanning remaining files
Running on the project's own lib/ directory with no violations must complete in under 30 seconds on a standard CI machine

Technical Requirements

frameworks
Dart (latest)
analyzer package (pub.dev) for AST parsing
apis
dart:io (File, Directory, exit)
package:analyzer/dart/analysis/
ContrastRatioValidator (from task-004)
data models
LintViolation (file: String, line: int, column: int, rule: String, message: String, severity: 'error'|'warning')
LintReport (violations: List<LintViolation>, summary: LintSummary)
LintSummary (total: int, errors: int, warnings: int)
performance requirements
Complete full lib/ scan in under 30 seconds on CI hardware
Process files in parallel using Dart isolates or async/await with Future.wait for file reads
security requirements
Script must not write any files — only stdout/stderr output
Must not execute any code from the scanned source files — AST analysis only

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Structure the script as a thin bin/accessibility_lint.dart entry point that delegates to lib/tools/accessibility_linter.dart where the real logic lives. This allows unit-testing the logic without spawning a subprocess. Use the analyzer package's AnalysisContextCollection and ResolvedUnitResult to walk the AST — it provides accurate type information unlike regex scanning. Key visitor pattern: extend RecursiveAstVisitor with overrides for visitMethodInvocation (catch Color constructors), visitInstanceCreationExpression (catch SizedBox/Container), and visitNamedExpression (catch fontStyle/fontWeight arguments).

For the contrast check, you need to resolve both the foreground and background Color values from the AST literal — this is straightforward for Color(0xFFRRGGBB) literals but skip non-literal expressions (log a coverage gap). The script is intentionally conservative: flag only what can be statically determined. False negatives are acceptable; false positives are not. Add a // accessibility_lint_ignore comment mechanism so individual lines can be suppressed with a reason.

Testing Requirements

Test file: test/accessibility_lint_runner_test.dart. Use dart:io to create temporary .dart fixture files with known violations and run the lint logic against them. Required scenarios: (1) clean file produces empty violations list and exit code 0, (2) file with Color(0xFF000000) foreground on Color(0xFF767676) background triggers contrast violation, (3) SizedBox(width: 30, height: 30) wrapping GestureDetector triggers touch target violation, (4) TextStyle(fontStyle: FontStyle.italic) triggers font style violation, (5) TextStyle(fontWeight: FontWeight.w300) triggers font weight violation, (6) multiple violations in one file all appear in output, (7) malformed .dart file produces a warning entry, not a crash. Do NOT invoke the CLI via Process.run in unit tests — test the core lint logic functions directly to keep tests fast.

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.