krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Commit 69c34c3ded

69c34c3ded7f112622aaaaad62b5a8a752ed3956

parent: 4cb642b1b7

Unsigned

cmc <hello@cleberg.net> · 2026-10-18 06:29 UTC

Accessibility audit and fixes (!19)

- New =MoonriseUITests= target: Xcode accessibility audit over every main screen; issues printed as =A11Y= lines.
- Fixes: wrapping instead of truncation (Launches, Library, Today summary), 44 pt tap targets, higher opacity on dimmed controls, combined fact rows, default tint in Settings.

| Issue type | Before | After |
|-
| Text clipped | 11 | 1 |
| Hit area too small | 9 | 0 |
| Contrast failed / nearly | 5 | 7 |
| Dynamic Type partially unsupported | 36 | 36 |
| Other | 2 | 2 |

Remaining contrast flags are white text under the translucent tab bar and the system Done button; Dynamic Type flags include system controls. =swift test= passes; UI test passes.

Layout: unified · split

App/Sources/LaunchesScreen.swift +2 −2
@@ -64,9 +64,9 @@ struct LaunchesScreen: View {
6464 StatusTag(status: l.status, ink: ink, accent: accent)
6565 }
6666 Text(l.rocket).font(.title2.weight(.medium))
67 if let m = l.mission { Text(m).font(.subheadline).lineLimit(1) }
67 if let m = l.mission { Text(m).font(.subheadline) }
6868 HStack {
69 Text("\(l.provider) · \(l.location)").font(.caption2).opacity(0.6).lineLimit(1)
69 Text("\(l.provider) · \(l.location)").font(.caption2).opacity(0.7)
7070 Spacer()
7171 Countdown(to: l.net).font(.footnote.weight(.medium).monospacedDigit())
7272 }
App/Sources/LibraryScreen.swift +2 −1
@@ -66,7 +66,7 @@ struct PageRow: View {
6666 var body: some View {
6767 VStack(alignment: .leading, spacing: 2) {
6868 Text(page.title).font(.body)
69 Text(page.subtitle).font(.caption).opacity(0.6).lineLimit(1)
69 Text(page.subtitle).font(.caption).opacity(0.7)
7070 }
7171 .padding(.vertical, 2)
7272 }
@@ -111,6 +111,7 @@ struct LibraryPageView: View {
111111 Text(f.label).font(.footnote).opacity(0.6)
112112 Text(f.value).font(.subheadline.monospacedDigit())
113113 }
114 .accessibilityElement(children: .combine)
114115 Divider().overlay(ink.opacity(0.15))
115116 }
116117 }
App/Sources/SkyScreen.swift +1
@@ -59,6 +59,7 @@ struct SkyScreen: View {
5959 .sheet(isPresented: $showSettings) {
6060 SettingsScreen { model.applyLocationSettings() }
6161 .preferredColorScheme(.dark)
62 .tint(.blue)
6263 }
6364 .sheet(item: $libraryPage) { page in
6465 NavigationStack { LibraryPageView(page: page) }
App/Sources/SystemScreen.swift +6 −4
@@ -38,9 +38,11 @@ struct SystemScreen: View {
3838 .font(.subheadline.weight(.medium).monospacedDigit())
3939 .frame(width: 80, alignment: .leading)
4040 Slider(value: $offsetDays, in: range).tint(ink)
41 Button("Today") { playing = false; offsetDays = 0 }
42 .font(.footnote.weight(.medium))
43 .opacity(abs(offsetDays) < 1 ? 0.4 : 1)
41 Button { playing = false; offsetDays = 0 } label: {
42 Text("Today").frame(minWidth: 44, minHeight: 44).contentShape(Rectangle())
43 }
44 .font(.footnote.weight(.medium))
45 .opacity(abs(offsetDays) < 1 ? 0.6 : 1)
4446 }
4547 .padding(.top, 12)
4648
@@ -58,7 +60,7 @@ struct SystemScreen: View {
5860 ForEach(Spacecraft.all) { craft in
5961 GridRow {
6062 if let page = Library.page(spacecraft: craft.name) {
61 Button { libraryPage = page } label: { Text(craft.name).underline() }.buttonStyle(.plain)
63 Button { libraryPage = page } label: { Text(craft.name).underline().frame(minHeight: 44, alignment: .leading).contentShape(Rectangle()) }.buttonStyle(.plain)
6264 } else {
6365 Text(craft.name)
6466 }
App/Sources/TodaySheet.swift +8 −4
@@ -121,10 +121,12 @@ struct TodayBar: View {
121121 scrubber
122122 Button(action: onExpand) {
123123 HStack {
124 Text(summary).font(.footnote).opacity(0.75).lineLimit(2)
124 Text(summary).font(.footnote).opacity(0.8)
125125 Spacer()
126126 Image(systemName: "chevron.up").font(.caption.weight(.medium)).opacity(0.6)
127127 }
128 .frame(minHeight: 44)
129 .contentShape(Rectangle())
128130 }
129131 .buttonStyle(.plain)
130132 .accessibilityHint("Shows tonight's details")
@@ -157,9 +159,11 @@ struct TodayBar: View {
157159 .font(.footnote.monospacedDigit())
158160 .frame(width: 70, alignment: .leading)
159161 Slider(value: $model.offsetHours, in: -12...12, step: 0.25).tint(ink)
160 Button("Now") { model.offsetHours = 0 }
161 .font(.footnote.weight(.medium))
162 .opacity(model.offsetHours == 0 ? 0.4 : 1)
162 Button { model.offsetHours = 0 } label: {
163 Text("Now").frame(minWidth: 44, minHeight: 44).contentShape(Rectangle())
164 }
165 .font(.footnote.weight(.medium))
166 .opacity(model.offsetHours == 0 ? 0.6 : 1)
163167 }
164168 }
165169
App/UITests/AccessibilityAuditTests.swift added +49
@@ -0,0 +1,49 @@
1import XCTest
2
3/// Runs Xcode's accessibility audit on each main screen and prints every issue.
4/// Issues are recorded, not failed, so one run reports everything.
5final class AccessibilityAuditTests: XCTestCase {
6 var app: XCUIApplication!
7 var issues: [String] = []
8
9 override func setUp() {
10 continueAfterFailure = true
11 app = XCUIApplication()
12 app.launchArguments += ["-onboarded", "YES", "-locationMode", "city", "-nightMode", "off"]
13 app.launch()
14 }
15
16 private func audit(_ screen: String, _ types: XCUIAccessibilityAuditType = .all) {
17 try? app.performAccessibilityAudit(for: types) { issue in
18 let label = issue.element?.label ?? ""
19 let id = issue.element?.identifier ?? ""
20 let line = "[\(screen)] \(issue.auditType): \(issue.compactDescription) — element: '\(label)' \(id)"
21 self.issues.append(line)
22 print("A11Y " + line)
23 return true
24 }
25 }
26
27 private func tab(_ name: String) {
28 app.tabBars.buttons[name].firstMatch.tap()
29 sleep(3)
30 }
31
32 func testAuditAllScreens() {
33 sleep(3)
34 audit("Sky")
35 app.buttons["Settings"].tap(); sleep(1)
36 audit("Settings")
37 app.buttons["Done"].tap(); sleep(1)
38 tab("System"); audit("System")
39 tab("Sun"); audit("Sun")
40 tab("Launches"); audit("Launches")
41 tab("Library"); audit("Library")
42 app.staticTexts["Planets"].firstMatch.tap(); sleep(1)
43 app.staticTexts["Saturn"].firstMatch.tap(); sleep(2)
44 audit("Library page: Saturn")
45 print("ACCESSIBILITY-AUDIT-BEGIN")
46 issues.forEach { print($0) }
47 print("ACCESSIBILITY-AUDIT-END \(issues.count) issues")
48 }
49}
App/project.yml +16
@@ -10,6 +10,8 @@ targets:
1010 Moonrise:
1111 type: application
1212 platform: iOS
13 scheme:
14 testTargets: [MoonriseUITests]
1315 sources: [Sources, Shared]
1416 dependencies:
1517 - package: MoonriseKit
@@ -81,3 +83,17 @@ targets:
8183 TARGETED_DEVICE_FAMILY: "1"
8284 MARKETING_VERSION: "1.0"
8385 CURRENT_PROJECT_VERSION: "1"
86 MoonriseUITests:
87 type: bundle.ui-testing
88 platform: iOS
89 sources: [UITests]
90 dependencies:
91 - target: Moonrise
92 settings:
93 base:
94 PRODUCT_BUNDLE_IDENTIFIER: sh.krz.moonrise.uitests
95 DEVELOPMENT_TEAM: ZCNAX3VL9D
96 CODE_SIGN_STYLE: Automatic
97 SWIFT_VERSION: "5.0"
98 TARGETED_DEVICE_FAMILY: "1"
99 GENERATE_INFOPLIST_FILE: YES