krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

v4.9.0: Docs/ACCESSIBILITY_VERIFICATION.md · raw

  1# Accessibility Verification Checklist (issue #21, Phase 6)
  2
  3Manual verification for the accessibility work in phases 1–5. Everything here is
  4what the automated audit (`Scripts/audit-a11y.sh`) **cannot** check: VoiceOver
  5speech, the rotor, custom-content ordering, announcements, Voice Control,
  6motion/transparency/colour settings, keyboard focus order, and the two system
  7design languages. A green audit is necessary, not sufficient — this is what makes
  8it sufficient.
  9
 10Each item traces to the phase that introduced it (e.g. `[P4]`) so a failure points
 11straight at the code. Check the box only when the **Expected** line is literally
 12true on the device.
 13
 14---
 15
 16## 0. Setup
 17
 18### Devices / runtimes
 19
 20The app supports **iOS 17.6+** and renders under two system design languages:
 21classic chrome (17.6–25) and Liquid Glass (26+). Materials, surfaces, and
 22contrast resolve differently between them, so visual passes need both.
 23
 24- [ ] Primary device on **iOS 26+** (Liquid Glass).
 25- [ ] A device or simulator on the **floor** (oldest available ≥ 17.6; 18.x is
 26      the practical minimum since no 17.6 runtime ships). A simulator is fine for
 27      the visual and Dynamic Type passes; VoiceOver/Voice Control are best on
 28      hardware.
 29
 30### Seed data — required, or half the checklist is untestable
 31
 32Several items only render with tracked domains and a completed lookup. The audit
 33simulator has none, which is exactly why the dense rows and widget are unverified
 34so far. Before starting:
 35
 36- [ ] Inspect tab → run a lookup on a **live** domain (e.g. `cleberg.net`), let
 37      all sections load.
 38- [ ] Run a lookup on a domain with a **weak/expiring or missing** cert and
 39      missing SPF/DMARC, so warning/critical tones and badges actually appear.
 40- [ ] Track **at least 4** domains with mixed health (one healthy, one warning,
 41      one critical, one unreachable) so Dashboard tiles, dense watchlist rows, and
 42      the widget all have content.
 43- [ ] Run a **Bulk** lookup on ~5 domains so `BatchResultRowView` renders.
 44- [ ] Add the **Domain Portfolio** widget to the Home Screen in all three sizes
 45      (small, medium, large).
 46- [ ] Force the Pro tier if needed so gated screens (Workflows, Scheduled
 47      Reports, Compare) are reachable.
 48
 49### Settings map (paths used throughout)
 50
 51| Setting | Path |
 52| --- | --- |
 53| VoiceOver | Settings → Accessibility → VoiceOver |
 54| Screen Curtain | VoiceOver on → triple-tap with 3 fingers |
 55| Voice Control | Settings → Accessibility → Voice Control |
 56| Larger Text / Dynamic Type | Settings → Accessibility → Display & Text Size → Larger Text |
 57| Bold Text | Settings → Accessibility → Display & Text Size → Bold Text |
 58| Increase Contrast | Settings → Accessibility → Display & Text Size → Increase Contrast |
 59| Differentiate Without Color | Settings → Accessibility → Display & Text Size → Differentiate Without Color |
 60| Smart Invert | Settings → Accessibility → Display & Text Size → Smart Invert |
 61| Reduce Motion | Settings → Accessibility → Motion → Reduce Motion |
 62| Reduce Transparency | Settings → Accessibility → Display & Text Size → Reduce Transparency |
 63| Full Keyboard Access (iPad) | Settings → Accessibility → Keyboards → Full Keyboard Access |
 64| App appearance override | In-app: Settings tab → Display → Appearance |
 65
 66> Ordering note: the passes are grouped so each iOS setting is toggled **once**.
 67> Do them top to bottom to avoid thrashing Settings.
 68
 69---
 70
 71## 1. Baseline visual — Light, Dark, System `[P1][P2]`
 72
 73No assistive tech on. Toggle appearance via **in-app Settings → Display →
 74Appearance**, then confirm the system setting is also honoured.
 75
 76- [ ] **System** appearance follows the device; flipping the device Light/Dark
 77      flips the app.
 78- [ ] **Light** and **Dark** overrides hold regardless of the device setting.
 79- [ ] Accent is **blue** everywhere — tab bar selection, links, section titles'
 80      "info" accents, the Insights icons, the selected quick-filter chip. No
 81      leftover **cyan**. `[P1]`
 82- [ ] Warning tone reads as **orange**, not olive/brown — check a "Warning" or
 83      "Expiring" badge and the Dashboard "Warning" tile. `[P1 rebalance]`
 84- [ ] The selected Dashboard summary tile is a soft **blue-tinted** surface, not
 85      lavender/violet. `[P2 wash fix]`
 86- [ ] Prominent buttons (**Run**, **Run Batch**, **Scan**) show a **white label
 87      on a blue fill** that is comfortably readable in both schemes. `[P1 AccentFill]`
 88- [ ] Secondary/detail text (row labels, timestamps, "Monitoring off") is legible
 89      in **Light** — not washed-out grey. `[P2 AppTextSecondary]`
 90- [ ] Status badges pair an **icon + text + colour** (e.g. lock + "Valid"),
 91      never colour alone.
 92- [ ] Repeat the whole list on the **floor runtime**. Note any Liquid-Glass-only
 93      difference. `[cross-runtime]`
 94
 95---
 96
 97## 2. Dynamic Type & reflow — up to Accessibility 5 `[P3]`
 98
 99Larger Text → drag to **maximum** (AX5). Walk every primary screen: Inspect
100(with results), Dashboard, Audit, History, Settings, Watchlist, a tracked-domain
101detail, Workflows.
102
103- [ ] All body text **scales up** (it already did pre-P3; confirm nothing is
104      pinned). The Dashboard tile numbers scale too. `[P3 fixed .system(size:)]`
105- [ ] **No clipped headings.** Empty-state titles ("No Portfolio Yet", "No Audits
106      Yet", "No History Yet", "No Tracked Domains", "No Batch Results Yet") **wrap
107      onto multiple lines** rather than truncating with "…". `[P3 Label→HStack]`
108- [ ] **No card requires horizontal scrolling.** Inspect result cards, the risk
109      card, and detail rows **wrap vertically**; there is no hidden left-right
110      gesture to reach content. `[P3 CardView reflow]`
111- [ ] Dense rows (`BatchResultRowView`, `WatchlistRowView`) remain **readable**112      text may be tall but must not overlap the trailing badge or clip. If it
113      does, that is the deferred `ViewThatFits` work, not a P3 regression — log it.
114      `[deferred]`
115- [ ] Every tappable control is at least **44×44pt** at default size. Spot-check
116      the **copy buttons** on data rows (the most-repeated control), the
117      **collapsible section headers**, and **Run**. `[P3 tap targets]`
118- [ ] Turn on **Bold Text**; confirm no layout breaks and contrast holds.
119- [ ] **Widget**: at AX sizes the widget content stays readable and is **not
120      truncated into nonsense** — it clamps at Accessibility 1 by design. `[P3 widget clamp]`
121- [ ] Repeat the clipping/reflow spot-checks on the **floor runtime**.
122
123---
124
125## 3. VoiceOver `[P4]`
126
127Enable VoiceOver. Learn the gestures if needed: swipe right = next element, swipe
128up/down on the **rotor** set to "More Content" = reveal extra fields, two-finger
129swipe up = read from top.
130
131### 3a. Icon-only controls announce a purpose, not a symbol name
132
133Focus each and confirm the spoken label. **None** should say a raw symbol name
134("arrow clockwise", "square and arrow up", "bolt circle").
135
136- [ ] Dashboard toolbar refresh → **"Refresh all tracked domains"**.
137- [ ] Inspect toolbar (after a lookup): clear → **"Clear results"**; actions menu
138**"Actions"**; export menu → **"Export"**.
139- [ ] Watchlist: add → **"Add domain"**; filter → **"Filter and sort"**.
140- [ ] History filter → **"Filter"**.
141- [ ] Timeline grouping → **"Group timeline"**.
142- [ ] Workflows: create → **"Create workflow"**; a run summary's export →
143      **"Export summary"**; re-run → **"Re-run workflow"**; a shared workflow's
144      person icon → **"Shared"**.
145
146### 3b. Toggles announce their state
147
148- [ ] Inspect **Save** (bookmark): label **"Save domain"**, value **"Not saved"**;
149      activate → value becomes **"Saved"** and the element reports **selected**.
150- [ ] Domain section **Pin**: label **"Pin domain"**, value toggles
151      **"Pinned"/"Not pinned"** and reports **selected** when pinned.
152- [ ] Audit checklist item: reports **selected** when complete, and a **hint**
153      ("Marks complete" / "Marks incomplete").
154- [ ] Audit area picker and Workflow domain picker rows report **selected** when
155      chosen.
156
157### 3c. Badges and headings
158
159- [ ] A status badge reads as **one word** — "Critical", "Valid", "Secure" — not
160      "icon, Critical". `[P4 badge combine]`
161- [ ] Set the rotor to **Headings**. Section titles ("Summary", "Risk", "DNS",
162      etc.) and the collapsible Inspect section headers are reachable as headings
163      and let you **jump between sections**. `[P4 .isHeader]`
164- [ ] A collapsible Inspect header announces **"Expanded"/"Collapsed"** as its
165      value with a hint, and toggling it updates the value. `[P4]`
166- [ ] Confirm the header's **trailing controls** (Track / Pin / Note on the Domain
167      section) are still **individually focusable** — they were deliberately *not*
168      merged into the header. `[P4 no combine on trailing]`
169
170### 3d. Dense rows — combine + More Content rotor (the flagship)
171
172On the **Watchlist** and a **Bulk** result list, with the rotor on **More Content**:
173
174- [ ] Each row is **one VoiceOver stop**, not eight. `[P4]`
175- [ ] The row's **label is the domain**; its **value is the status** (e.g.
176      "Registered" / "Critical, Registered").
177- [ ] Swiping up/down on More Content reveals the extra fields **in order**:
178      - Watchlist row: **Certificate** (spoken first, high importance),
179        Monitoring, Updated, Pinned.
180      - Batch row: **Risk** (high importance), IP address, Checked, Source,
181        Impact/Status.
182- [ ] Risk and Certificate are spoken **without** needing the rotor (high
183      importance); the rest wait for the swipe.
184
185### 3e. Speech for technical strings
186
187- [ ] Focus a **DNS record value** (Inspect → DNS section) and an SSL **Cipher
188      Suite** (Web section). Punctuation (`;`, `~`, `_`, `-`) is **spoken**, and
189      the string reads character/token-sensibly rather than as garbled prose.
190      `[P4 speechStyle .technical]`
191- [ ] Focus a plain prose value (e.g. Issuer common name) and confirm it is **not**
192      spelled out awkwardly — only technical rows get the treatment.
193
194### 3f. Completion announcements
195
196- [ ] Run a **single lookup**. On completion VoiceOver speaks **"Lookup complete
197      for `<domain>`. `<availability>`."** without you moving focus. `[P4]`
198- [ ] Run a **sweep / Check All**. On completion it speaks **"Sweep complete. N
199      domains, X changed, Y warnings."** `[P4]`
200- [ ] The announcements do **not** fire per-domain during a long sweep (would
201      flood the queue) — only once at the end.
202
203### 3g. Widget under VoiceOver
204
205- [ ] A medium/large widget domain row reads as one phrase, e.g. **"example.com,
206      critical, certificate expires in 12 days"** (or "pinned", or "certificate
207      expired"). `[P4]`
208- [ ] The small widget's count pills read **"N healthy"**, **"N warning"**,
209      **"N critical"** — not a bare number. `[P4]`
210
211### 3h. Full walkthrough with Screen Curtain
212
213Turn on Screen Curtain (triple-tap, 3 fingers — screen goes black). Complete the
214core journey **without looking**:
215
216- [ ] Inspect a domain → hear the sections → **Save** it → open **Watchlist**217      open its **detail** → back out. Every step is discoverable and every control
218      announces a purpose and state. Log anything that leaves you stuck.
219
220---
221
222## 4. Voice Control — label-in-name (WCAG 2.5.3) `[P4]`
223
224Enable Voice Control. Say the **printed** text of controls. Every visible-text
225control must respond to its visible name (this is why labels preserve visible
226text rather than replacing it).
227
228- [ ] "Tap **Run**" runs the lookup (not broken by a relabel).
229- [ ] "Tap **Track**", "Tap **Note**", "Tap **Compare**", "Tap **Cancel**",
230      "Tap **Save**" each work by their printed word.
231- [ ] Say **"Show numbers"**; confirm the icon-only controls get numbered overlays
232      and are operable (they have labels, so they also respond to "Show names").
233- [ ] No control is reachable *only* by a name that differs from its visible text.
234
235---
236
237## 5. Colour & contrast settings `[P1][P2][P5]`
238
239### 5a. Increase Contrast
240
241- [ ] Enable. Status colours and the accent shift to their **high-contrast**
242      variants; nothing becomes unreadable in either scheme. `[P1 HC variants]`
243- [ ] The Settings section headers that were marginal now clear — this is the one
244      the audit already measured (light 21 → 18). `[P2]`
245
246### 5b. Differentiate Without Color — the P5 payoff
247
248Enable. This is the pass that validates most of Phase 5.
249
250- [ ] **Dashboard summary tiles**: the small dot becomes a **per-filter symbol**
251      (grid, checkmark, triangle, octagon, refresh, clock, wifi-slash). `[P5]`
252- [ ] **Selected quick-filter chip** gains a **checkmark + border** — selection no
253      longer depends on fill colour alone. `[P5]`
254- [ ] **Inspect data rows** with a warning/failure value show a **leading symbol**
255      (triangle / octagon) before the value. `[P5 LabeledValueRow]`
256- [ ] Turning the setting **off** removes those extras (no permanent clutter). `[P5]`
257- [ ] **Widget** status already uses symbols regardless of this setting — confirm
258      each row shows checkmark/triangle/octagon, not a bare dot. `[P5]`
259
260### 5c. Smart Invert
261
262- [ ] Enable. UI inverts sensibly; images/icons that should stay un-inverted do.
263      Text stays legible. Note anything that inverts wrongly. `[verification]`
264
265---
266
267## 6. Motion & transparency `[P5]`
268
269### 6a. Reduce Motion
270
271Enable. Confirm each animated transition becomes an **instant** state change (no
272slide/fade):
273
274- [ ] Copy button check-mark swap (tap a copy button) — flips instantly. `[P5]`
275- [ ] Collapsible Inspect section expand/collapse — no ease animation. `[P5]`
276- [ ] Timeline scroll-to-section — jumps, no scroll animation. `[P5]`
277- [ ] Watchlist list reorder (change sort/filter) — no reflow animation. `[P5]`
278
279### 6b. Reduce Transparency
280
281- [ ] Enable. Trigger the **Data Management** success toast (Settings → Data
282      Management → perform a clear). Its pill background is an **opaque surface**,
283      not a blur. `[P5]`
284- [ ] On **iOS 26+**, confirm system-composited chrome (nav/tab bars) still reads
285      acceptably — the app can't declare that translucency itself. `[cross-runtime]`
286
287---
288
289## 7. iPad — Full Keyboard Access & split layout `[verification]`
290
291On iPad (regular width, so `RootTabView` shows the `NavigationSplitView`
292sidebar + detail). Enable Full Keyboard Access; attach or use the software
293alternative.
294
295- [ ] **Tab** moves focus in a **logical order** — sidebar → detail, top → bottom,
296      no traps.
297- [ ] The blue **focus ring** is visible on every focusable control.
298- [ ] Sidebar tab selection and detail controls are all reachable and operable by
299      keyboard.
300- [ ] Switching tabs via keyboard updates the detail pane correctly.
301
302---
303
304## 8. Cross-runtime sign-off `[cross-runtime]`
305
306- [ ] Sections 1, 2, 5, 6 re-checked on the **floor runtime** (classic chrome).
307- [ ] Sections 1, 2, 5, 6 checked on **iOS 26+** (Liquid Glass).
308- [ ] Any behaviour that differs between the two is logged below with the runtime
309      noted.
310
311---
312
313## Sign-off
314
315| Pass | 26+ (Liquid Glass) | Floor (classic) | Notes |
316| --- | --- | --- | --- |
317| 1 Baseline visual |  |  |  |
318| 2 Dynamic Type / reflow |  |  |  |
319| 3 VoiceOver | n/a-runtime |  | do once on hardware |
320| 4 Voice Control | n/a-runtime |  | do once on hardware |
321| 5 Colour & contrast |  |  |  |
322| 6 Motion & transparency |  |  |  |
323| 7 iPad keyboard | n/a |  | iPad only |
324
325## Defect log
326
327Record failures here; each becomes an issue or a fix commit.
328
329| # | Pass / item | Device / runtime | Observed | Expected | Traces to |
330| --- | --- | --- | --- | --- | --- |
331|  |  |  |  |  |  |
332
333---
334
335## Known-deferred (not defects — expected gaps)
336
337- **`ViewThatFits` dense-row reflow** was not implemented (unverifiable in the
338  audit sim). If §2 shows dense rows overlapping/truncating at AX5 with real data,
339  that is this gap surfacing — file it against the deferred item, don't treat it
340  as a P3 regression.
341- **Live Activity / Dynamic Island** custom accessibility was intentionally not
342  added (ActivityKit exposes the labelled `ProgressView` already). Sanity-check a
343  running sweep's Live Activity reads acceptably, but a finding here is
344  enhancement, not regression.
345- **Localization** — all strings are English literals by design for now; a11y
346  strings were written `LocalizedStringKey`-compatible for a future catalog.