Skip to content

fix(a11y): audit findings before 1.0, plus Xcode Cloud release plumbing - #46

Merged
Ivan-LB merged 2 commits into
devfrom
fix/a11y-audit-findings
Aug 19, 2026
Merged

Ivan-LB merged 2 commits into
devfrom
fix/a11y-audit-findings

Conversation

@Ivan-LB

@Ivan-LB Ivan-LB commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Six real accessibility problems found by XCUIApplication.performAccessibilityAudit and a Simulator pass at AX5, each one a claim the App Store accessibility label would otherwise make falsely: hidden system tab bar exposed as four unlabelled buttons, category chip text under 4.5:1 (clay 3.59, sky 3.61, amber 3.45), goal stat pills wrapping "$1,480" into "$1,48 / 0", goal card name truncating to "Trip t…", Me hero labels at 0.7 alpha (3.4:1 in dark), "See all" hit areas at 41x16 pt, and the Home greeting read as the bare "Afternoon."
  • New tokens warmClayDeep / warmSkyDeep / warmLilacDeep (5:1 on their tints) and ExpenseCategory.tileTextColor; AnyLayout stacks at accessibility sizes in the goal detail, goal card and Me stat strip.
  • SavelyUITests/AccessibilityAuditUITests.swift runs the audit on demand and documents which findings were accepted and why, and what it cannot prove.
  • ci_scripts/ci_post_clone.sh + docs/xcode-cloud.md: Savely ships from Xcode Cloud (this Mac only has Xcode-beta and ASC rejected build 1). The script writes the gitignored Savely/Config.plist with the same printenv discipline as Fave and refuses only archive + tipsEnabled + no key.
  • Info.plist: ITSAppUsesNonExemptEncryption = false (HTTPS via URLSession only).

Why

Build 1 was rejected for the beta Xcode, so a build 2 is needed anyway. Fixing these first means build 2 can carry VoiceOver, Larger Text and Sufficient Contrast in the accessibility label instead of the three that were safe to claim today (Dark Interface, Differentiate Without Color, Reduced Motion).

Test plan

  • xcodebuild build -scheme Savely -destination 'platform=iOS Simulator,name=iPhone 17 Pro Max'
  • xcodebuild test -scheme Savely -skip-testing:SavelyUITests ... (102 tests, 0 failures)
  • xcodebuild test -only-testing:SavelyUITests/AccessibilityAuditUITests — remaining findings are the four tab captions (tab bar exemption), rows scrolled under the tab bar, the disabled Save button, and one "Text clipped" on the Money text field that is not understood and not claimed
  • Screenshot tour at content_size accessibility-extra-extra-extra-large: goal detail, goals list, log expense, privacy, month trend
  • ci_post_clone.sh dry-run on a scratch clone: canary key with &<> under bash -x, zero hits in trace, escaped in plist, plutil -lint OK, gate exits 1
  • Manual: VoiceOver on a real device across the tab bar and one goal card (XCUITest cannot stand in for VoiceOver; see the note in the audit test)

Risks

  • Contrast changes shift three accent hues darker only when used as text; glyphs keep the original tokens. Compare the quick-add chips light/dark.
  • WarmGoalCard header is restructured into identity / percent / favoriteButton; visual parity at default sizes checked in the tour, worth one glance in Xcode previews.
  • Nothing in ci_scripts/ runs on GitHub Actions; it only runs on Xcode Cloud.

Checklist

  • Branch named with approved prefix
  • Commits follow Conventional Commits
  • No hardcoded user-facing strings added (the greeting label composes existing strings)
  • No secrets staged
  • docs/xcode-cloud.md added; CLAUDE.md unchanged
  • Tests added (accessibility audit)

An accessibility audit (Apple's XCUIApplication.performAccessibilityAudit
over Home, Goals, a goal, Money, Me and Log expense) plus a Simulator pass
at AX5 turned up six real problems. Each one is a claim App Store Connect's
Accessibility Nutrition Label would otherwise make falsely.

- Hidden system tab bar exposed to VoiceOver: `.toolbar(.hidden, for:
  .tabBar)` on the TabView hides the bar but leaves four unlabelled buttons
  in the accessibility tree ("Element has no description" x4). Applied per
  child instead, they are gone.
- Category chip labels under 4.5:1: `tileColor` is a glyph color (3:1 on
  its tint). New `warmClayDeep` / `warmSkyDeep` / `warmLilacDeep` at 5:1
  and `ExpenseCategory.tileTextColor` for text on the tint; both quick-add
  and receipt-review chips use it. Amber already had `warmAmberDeep`.
- Goal detail stat pills at AX sizes: "$1,480" wrapped into "$1,48 / 0" in
  a third of the width. VStack at accessibility sizes via AnyLayout.
- Goal card name at AX sizes: "Trip to Oaxaca" truncated to "Trip t...".
  Two-line name and the percent/favorite row drops below the identity when
  the type is an accessibility size.
- Me tab: hero card labels were white at 0.7/0.75 alpha (4.0:1 light,
  3.4:1 dark); full white now. Stat strip stacks at AX sizes and labels
  wrap instead of shrinking; "See all" hit areas were 41x16 and 50x16 pt.
- Home greeting read to VoiceOver as the bare "Afternoon."; it is now one
  heading: "Good afternoon. Tuesday, August 18".

Also: the inline add fields on Money grow with Dynamic Type instead of a
fixed 40pt.

SavelyUITests/AccessibilityAuditUITests.swift runs the audit on demand and
documents the findings that were accepted and why, and what the audit
cannot prove (VoiceOver itself).

Verified: build, 102 unit tests, the audit, and the screenshot tour at
AX5 (goal detail, goals list, log expense) on iPhone 17 Pro Max.
App Store Connect refused build 1: this Mac only has Xcode-beta, and
Apple does not accept archives from a beta Xcode. Xcode Cloud archives
with a released Xcode, so, as Fave did, Savely ships from there.

ci_scripts/ci_post_clone.sh materialises the gitignored Savely/Config.plist
(a required Resources build input) from the OPENAI_API_KEY workflow secret
when present, or a placeholder when not, with the same printenv discipline
as Fave: the value never binds to a variable or reaches xtrace. Because
FeatureFlags.tipsEnabled is false the placeholder is what 1.0 should ship;
the only refusal is archive + flag on + no key. Dry-run against a scratch
clone with a canary key containing "&<>" under bash -x: zero hits in the
trace, one XML-escaped hit in the plist, plutil OK, and the gate exits 1.

Info.plist gains ITSAppUsesNonExemptEncryption = false: the app has no
cryptography of its own and its one network call is HTTPS via URLSession.

docs/xcode-cloud.md carries only what differs from Fave and the workflow
settings (released Xcode, Archive - iOS, manual start, ASC build counter
set to 2 because build 1 is taken).
@Ivan-LB
Ivan-LB merged commit 188d60a into dev Aug 19, 2026
1 check passed
@Ivan-LB
Ivan-LB deleted the fix/a11y-audit-findings branch August 19, 2026 01:17
@Ivan-LB Ivan-LB mentioned this pull request Aug 19, 2026
7 of 9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant