critical priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

InteractiveControlSpacingSystem widget accepts a spacing token reference (enum or string key) and a List<Widget> children parameter
Widget renders children using Wrap or Column layout with SizedBox spacers injected between each adjacent child
Resolved spacing value from design token is applied as the gap between all adjacent tappable controls
Minimum enforced gap is 8pt (logical pixels); any token resolving below 8pt is clamped to 8pt
In debug mode (assert-enabled builds), an assert() fires immediately if the resolved token value is below 8pt with a descriptive message identifying the token key and actual value
No assert fires in release/profile builds — Dart assertion semantics strip them automatically
Widget supports both vertical (Column) and horizontal (Wrap) axis via an optional axis parameter
Widget composes cleanly inside existing screen layouts without requiring parent restructuring
Widget passes all accessibility audits: touch targets remain reachable by assistive technology after spacing is applied
Golden test baseline captured for both axis modes at 8pt and 16pt spacing tokens

Technical Requirements

frameworks
Flutter
flutter_test
data models
DesignToken (spacing tokens map)
performance requirements
Widget build time must not exceed 1ms overhead over plain Column/Wrap for lists of up to 20 children
No unnecessary rebuilds — spacing token must be resolved once per build, not per child
security requirements
No user data processed; no security concerns specific to this widget
ui components
InteractiveControlSpacingSystem (new widget)
SizedBox (spacer injection)
Column / Wrap (layout container)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Resolve the spacing token inside the build method using the project's existing design token lookup utility (from task-002). Use a private helper `_buildSpacedChildren(List children, double gap)` that maps children to an interleaved list with SizedBox(height: gap) or SizedBox(width: gap) separators. Place the debug assert directly after token resolution: `assert(resolvedSpacing >= 8.0, 'InteractiveControlSpacingSystem: token "$tokenKey" resolved to ${resolvedSpacing}pt, which is below the 8pt WCAG minimum touch-target gap.')`. Avoid using ListView — it introduces scroll semantics that are inappropriate for inline control groups.

Do not use padding on the container; spacers must live between children, not outside the first/last child. Ensure the widget is const-constructible when the token reference is a compile-time constant.

Testing Requirements

Unit tests (flutter_test): verify that SizedBox spacers with the correct logical pixel value are injected between every pair of adjacent children; verify clamping to 8pt when token resolves below minimum; verify no spacers are injected for a single child. Debug-mode assertion tests: use an assert-enabled test runner to confirm assert fires for sub-8pt token values and does not fire for compliant values. Golden tests: render widget with 3 button children in Column and Wrap modes at 8pt and 16pt tokens, save baselines, and fail on pixel-level regressions. Integration smoke test: embed widget in activity registration form screen and confirm layout renders without overflow at default scale.

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.