Skip to content

Accessibility: remaining VoiceOver and Dynamic Type debt after #1096 #1098

Description

@romchornyi

Summary

What remains of the VoiceOver / Dynamic Type debt after #1096. The wallet-backup blocker is tracked separately in #1097 because it needs a product decision first.

Baseline after #1096: 658 findings (scripts/a11y_audit.py --summary). The CI ratchet from #1091 stops this growing; this issue is about paying it down.

1. Security sliders are unusable — P0

DashWallet/Sources/UI/Menu/Security/Advanced Security/Views/DWSegmentSlider.m contains zero accessibility code. These are the auto-lock timer and the spending limit. A custom slider with no .adjustable trait does not exist for VoiceOver at all — a blind user cannot change either setting.

This is the only remaining P0 outside #1097. Not a one-liner: it needs accessibilityIncrement/accessibilityDecrement and a spoken value.

2. Nothing is ever announced

UIAccessibility.post has 0 uses app-wide, as does UIAccessibility.is*. Nothing tells a blind user that a copy succeeded, a PIN was wrong, a payment was sent, or that a quote expired mid-purchase. Copy feedback in particular is haptic + a visual HUD only (UIView+DWHUD.m).

3. Dynamic Type is actively defeated — worth its own effort

DashWallet/Sources/UI/SwiftUI Components/Font+DWStyle.swift:20-69 declares an extension Font whose members shadow Apple's own text stylesbody, headline, largeTitle, title2, title3, callout, footnote, caption2 — as frozen .system(size:) values.

An app-module extension legally shadows the framework property, compiles without warning, and wins inside the module. So ~135 call sites that write .font(.body) and look entirely correct do not scale with the user's text size. There are 554 direct .system(size:) calls and zero uses of relativeTo: anywhere.

A low-vision user who raises text size in iOS Settings sees no change in the SwiftUI UI. The 2019 ObjC screens scale correctly, so the SwiftUI rewrite made this worse.

Fixing the tokens is ~4-6 hours; the layout QA that follows is 30-50, because every SwiftUI screen was built to frozen line heights. Probably its own ticket once someone picks it up.

4. Composite rows read as fragments

Contact rows, transaction cells and merchant rows announce as piles of disconnected pieces — .accessibilityElement(children: .combine) has only 7 uses app-wide. Worst offenders noted in the audit: ContactsScreen.swift (tappable rows via .onTapGesture, no button trait), PaymentSuccessHeader, the merchant/ATM list cells.

5. Interface Builder

8 image-only controls across the 38 .xib/.storyboard files have no accessibility configuration — including the Payments-sheet close button (Payments.storyboard:40) and the sync retry button (SyncView.xib:65). Only 3 IB files carry any accessibility markup at all, and all of it is traits without labels.

6. Known blind spots in the tooling

The static rules under-report, so 658 is a floor, not a measurement. Two confirmed misses, both real defects found by reading:

Anyone extending scripts/a11y_audit.py should start here: a Button whose label is a property reference rather than a literal.

Suggested order

  1. DWSegmentSlider (P0, blocks a real setting)
  2. Announcements for copy / wrong PIN / payment sent
  3. Composite rows on the main flows
  4. Dynamic Type as a separate ticket
  5. IB files, opportunistically when those screens are touched

Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions