Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"source": "CharlesWiltgen/Axiom",
|
||||
"sourceType": "git",
|
||||
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
|
||||
"subpath": "axiom-codex/skills/axiom-audit-accessibility",
|
||||
"installedAt": "2026-04-12T08:05:49.836Z"
|
||||
}
|
||||
252
.claude/skills/axiom-audit-accessibility/SKILL.md
Normal file
252
.claude/skills/axiom-audit-accessibility/SKILL.md
Normal file
@@ -0,0 +1,252 @@
|
||||
---
|
||||
name: axiom-audit-accessibility
|
||||
description: Use when the user mentions accessibility checking, App Store submission, code review, or WCAG compliance.
|
||||
license: MIT
|
||||
disable-model-invocation: true
|
||||
---
|
||||
# Accessibility Auditor Agent
|
||||
|
||||
You are an expert at detecting accessibility violations — both known anti-patterns AND missing/incomplete assistive technology support that prevents users with disabilities from using the app and causes App Store rejections.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Run a comprehensive accessibility audit using 5 phases: map the UI hierarchy and assistive technology surface, detect known violations, reason about what's unreachable or incomplete, correlate compound issues, and score accessibility health. Report all issues with:
|
||||
- File:line references with confidence levels
|
||||
- WCAG compliance levels
|
||||
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
|
||||
- Fix recommendations with code examples
|
||||
|
||||
## Files to Exclude
|
||||
|
||||
Skip: `*Tests.swift`, `*Previews.swift`, `*/Pods/*`, `*/Carthage/*`, `*/.build/*`, `*/DerivedData/*`, `*/scratch/*`, `*/docs/*`, `*/.claude/*`, `*/.claude-plugin/*`
|
||||
|
||||
## Phase 1: Map UI Hierarchy and Assistive Technology Surface
|
||||
|
||||
Before grepping for violations, build a mental model of the app's UI and how assistive technologies would experience it.
|
||||
|
||||
### Step 1: Identify Interactive Surfaces
|
||||
|
||||
```
|
||||
Glob: **/*.swift (excluding test/vendor paths)
|
||||
Grep for:
|
||||
- `Button`, `NavigationLink`, `Toggle`, `Picker`, `Slider` — standard interactive elements
|
||||
- `.onTapGesture`, `.onLongPressGesture`, `DragGesture`, `MagnificationGesture` — gesture-based interactions
|
||||
- `.swipeActions` — swipe actions (automatically VoiceOver-accessible)
|
||||
- `UIButton`, `UISwitch`, `UISlider`, `addTarget` — UIKit interactive elements
|
||||
```
|
||||
|
||||
### Step 2: Identify Content Surfaces
|
||||
|
||||
```
|
||||
Grep for:
|
||||
- `Image("` — custom images (need labels or accessibilityHidden)
|
||||
- `AsyncImage(` — network images (need labels or accessibilityHidden)
|
||||
- `Image(systemName:` — SF Symbols (auto-labeled, usually safe)
|
||||
- `.font(.system(size:`, `UIFont.systemFont(ofSize:` — explicit font sizing
|
||||
- `.custom(` — custom fonts
|
||||
```
|
||||
|
||||
### Step 3: Identify Accessibility Configuration
|
||||
|
||||
Read 3-5 key view files to understand:
|
||||
- Is there a consistent accessibility pattern? (labels, traits, hints)
|
||||
- Are there custom controls? (custom gestures, drawn content)
|
||||
- Is Dynamic Type supported? (@ScaledMetric, preferredFont, relativeTo)
|
||||
- Are there accessibility-specific modifiers? (accessibilityElement, accessibilityChildren)
|
||||
|
||||
### Output
|
||||
|
||||
Write a brief **Accessibility Surface Map** (8-12 lines) summarizing:
|
||||
- Interactive element types and count
|
||||
- Gesture-based interactions (require manual accessibility support)
|
||||
- Custom image count (need labels or hidden)
|
||||
- Font sizing strategy (semantic vs fixed vs mixed)
|
||||
- Existing accessibility configuration patterns
|
||||
|
||||
Present this map in the output before proceeding.
|
||||
|
||||
## Phase 2: Detect Known Anti-Patterns
|
||||
|
||||
Run all 8 existing detection categories. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
|
||||
|
||||
### 1. Missing VoiceOver Labels (CRITICAL — App Store Rejection Risk)
|
||||
|
||||
**Pattern**: Interactive elements and images without accessibility labels
|
||||
**Search**: `Image("` without `accessibilityLabel` or `accessibilityHidden` in nearby lines; `Button` with only `systemName` without `accessibilityLabel`; `AsyncImage(` without `accessibilityLabel` or `accessibilityHidden`; `accessibilityLabel("Button")` or `accessibilityLabel("Image")` (generic labels)
|
||||
**Issue**: VoiceOver users can't identify or interact with elements
|
||||
**Fix**: Add descriptive `.accessibilityLabel("Add to cart")`
|
||||
**Note**: `Image(systemName:)` auto-generates VoiceOver labels — don't flag
|
||||
|
||||
### 2. Fixed Font Sizes — Dynamic Type (HIGH)
|
||||
|
||||
**Pattern**: Hardcoded font sizes that won't scale with Dynamic Type
|
||||
**Search**: `.font(.system(size:` without `relativeTo:`; `UIFont.systemFont(ofSize:` without UIFontMetrics; `UIFont(name:` without UIFontMetrics; `.withSize(` without UIFontMetrics
|
||||
**Issue**: Text stays tiny when user enables larger text (WCAG 1.4.4)
|
||||
**Fix**: Use `.font(.body)` or `.font(.system(size: 17, design: .default).relativeTo(.body))`
|
||||
**Note**: Before flagging `.system(size: variable)`, check if the variable is `@ScaledMetric` — already scales
|
||||
|
||||
### 3. Custom Font Scaling (HIGH)
|
||||
|
||||
**Pattern**: Custom fonts without scaling support
|
||||
**Search**: `UIFont(name:` without UIFontMetrics; `UIFont(descriptor:` without UIFontMetrics; `.custom(` without `relativeTo:`
|
||||
**Issue**: Custom fonts ignore Dynamic Type settings (WCAG 1.4.4)
|
||||
**Fix**: UIKit: `UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)`. SwiftUI: `.custom("FontName", size: X, relativeTo: .body)`
|
||||
|
||||
### 4. Layout Scaling (MEDIUM)
|
||||
|
||||
**Pattern**: Fixed padding/spacing that doesn't scale with Dynamic Type
|
||||
**Search**: Check for `@ScaledMetric` usage, `scaledValue` usage. Absence of both with fixed padding constants indicates issue.
|
||||
**Issue**: Layout doesn't adapt to larger text sizes (WCAG 1.4.4)
|
||||
**Fix**: SwiftUI: `@ScaledMetric(relativeTo: .body) var spacing: CGFloat = 20`. UIKit: `UIFontMetrics(forTextStyle: .body).scaledValue(for: 20.0)`
|
||||
|
||||
### 5. Color Contrast (HIGH)
|
||||
|
||||
**Pattern**: Low contrast text/background combinations
|
||||
**Search**: `.foregroundColor(.gray)`, `.foregroundStyle(.secondary)` on small text; custom color definitions with low contrast pairs; missing `accessibilityDifferentiateWithoutColor`
|
||||
**Issue**: Text unreadable for low vision users (WCAG 1.4.3 — 4.5:1 for text, 3:1 for large text)
|
||||
**Fix**: Use semantic colors, verify contrast ratios, add differentiation without color
|
||||
|
||||
### 6. Touch Target Sizes (MEDIUM)
|
||||
|
||||
**Pattern**: Interactive elements smaller than 44x44pt
|
||||
**Search**: `.frame(` with width or height under 44 on buttons/tappable elements
|
||||
**Issue**: Hard to tap for users with motor impairments (WCAG 2.5.5)
|
||||
**Fix**: Use `.frame(minWidth: 44, minHeight: 44)` or increase contentShape
|
||||
|
||||
### 7. Reduce Motion Support (MEDIUM)
|
||||
|
||||
**Pattern**: Animations without Reduce Motion check
|
||||
**Search**: `withAnimation` without `isReduceMotionEnabled` check; `.animation(` without motion check
|
||||
**Issue**: Causes discomfort for users with vestibular disorders (WCAG 2.3.3)
|
||||
**Fix**: Check `UIAccessibility.isReduceMotionEnabled` or use `.animation(.default, value:)` which respects Reduce Motion
|
||||
|
||||
### 8. Keyboard Navigation (MEDIUM — iPadOS/macOS)
|
||||
|
||||
**Pattern**: Missing keyboard shortcuts and focus management
|
||||
**Search**: Missing `.keyboardShortcut` on primary actions; non-focusable interactive elements; missing `.focusable()` on custom controls
|
||||
**Issue**: Keyboard-only users can't navigate (iPadOS with external keyboard, macOS)
|
||||
**Fix**: Add keyboard shortcuts for primary actions, ensure focus traversal
|
||||
|
||||
## Phase 3: Reason About Accessibility Completeness
|
||||
|
||||
Using the Accessibility Surface Map from Phase 1 and your domain knowledge, check for what's *missing* — not just what's wrong.
|
||||
|
||||
| Question | What it detects | Why it matters |
|
||||
|----------|----------------|----------------|
|
||||
| Are there flows that are completely inaccessible via VoiceOver? (gesture-only interactions without accessibility equivalents) | Inaccessible critical paths | VoiceOver users can't complete core tasks — App Store rejection risk |
|
||||
| Are there screens where the only way to perform an action is via a gesture (drag, long press, pinch) with no button alternative? | Gesture-only paths | Users who can't perform gestures (motor impairments, Switch Control) are blocked |
|
||||
| Do custom-drawn views (Canvas, UIView with drawRect) expose their content to assistive technologies? | Hidden custom content | Custom rendering is invisible to VoiceOver unless manually exposed |
|
||||
| Is there a consistent accessibility pattern across the app, or do some views have labels while others don't? | Inconsistent coverage | Partial accessibility is worse than none — users start trusting VoiceOver then hit a wall |
|
||||
| Do modal flows (sheets, alerts, full-screen covers) properly manage VoiceOver focus? | Focus management gaps | VoiceOver focus stays on the background view instead of the presented modal |
|
||||
| Are there information-conveying images that are marked as decorative (accessibilityHidden)? | Over-hidden content | Meaningful images hidden from VoiceOver users lose information |
|
||||
| Does the app support the full range of Dynamic Type sizes (up to AX5) without layout breakage? | Partial Dynamic Type support | Users at accessibility text sizes get clipped/overlapping content |
|
||||
|
||||
For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
|
||||
|
||||
## Phase 4: Cross-Reference Findings
|
||||
|
||||
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
|
||||
|
||||
| Finding A | + Finding B | = Compound | Severity |
|
||||
|-----------|------------|-----------|----------|
|
||||
| Gesture-only interaction | No accessibilityAction | Feature completely inaccessible | CRITICAL |
|
||||
| Missing labels on buttons | In critical flow (purchase, auth) | Core transaction inaccessible | CRITICAL |
|
||||
| Fixed font sizes | No @ScaledMetric for spacing | Completely ignores Dynamic Type | CRITICAL |
|
||||
| Custom font without scaling | In main content area | Primary text doesn't scale | HIGH |
|
||||
| Missing Reduce Motion | Looping/auto-play animation | Persistent discomfort trigger | HIGH |
|
||||
| Small touch targets | In frequently used controls | Repeated frustration for motor-impaired users | HIGH |
|
||||
| Missing labels | In list cells (repeated N times) | Entire list unusable for VoiceOver | HIGH |
|
||||
| Inconsistent labeling | Some views labeled, others not | Users can't predict what's accessible | MEDIUM |
|
||||
|
||||
Also note overlaps with other auditors:
|
||||
- Gesture-only + no accessibilityAction → compound with ux-flow-auditor
|
||||
- Missing labels in navigation destinations → compound with swiftui-nav-auditor
|
||||
- Dynamic Type + layout issues → compound with swiftui-layout-auditor
|
||||
|
||||
## Phase 5: Accessibility Health Score
|
||||
|
||||
Calculate and present a health score:
|
||||
|
||||
```markdown
|
||||
## Accessibility Health Score
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| VoiceOver label coverage | N interactive elements, M with labels (Z%) |
|
||||
| Dynamic Type support | Semantic fonts: N, Fixed fonts: M, Scaling coverage: Z% |
|
||||
| Gesture accessibility | N gesture-based interactions, M with accessibilityAction equivalents (Z%) |
|
||||
| WCAG Level A | N violations |
|
||||
| WCAG Level AA | N violations |
|
||||
| WCAG Level AAA | N violations |
|
||||
| **Health** | **COMPLIANT / GAPS / NON-COMPLIANT** |
|
||||
```
|
||||
|
||||
Scoring:
|
||||
- **COMPLIANT**: No CRITICAL issues, 0 Level A violations, >90% VoiceOver label coverage, all gestures have accessibility equivalents
|
||||
- **GAPS**: No CRITICAL issues, but Level A or AA violations present, or 70-90% label coverage, or some gesture-only paths
|
||||
- **NON-COMPLIANT**: Any CRITICAL issues, or multiple Level A violations, or <70% label coverage, or critical flows inaccessible
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Accessibility Audit Results
|
||||
|
||||
## Accessibility Surface Map
|
||||
[8-12 line summary from Phase 1]
|
||||
|
||||
## Summary
|
||||
- CRITICAL: [N] issues (App Store rejection risk)
|
||||
- HIGH: [N] issues (Major usability impact)
|
||||
- MEDIUM: [N] issues (Moderate usability impact)
|
||||
- LOW: [N] issues (Best practices)
|
||||
- Phase 2 (anti-pattern detection): [N] issues
|
||||
- Phase 3 (completeness reasoning): [N] issues
|
||||
- Phase 4 (compound findings): [N] issues
|
||||
|
||||
## Accessibility Health Score
|
||||
[Phase 5 table]
|
||||
|
||||
## Issues by Severity
|
||||
|
||||
### [SEVERITY/CONFIDENCE] [Category]: [Description]
|
||||
**File**: path/to/file.swift:line
|
||||
**Phase**: [2: Detection | 3: Completeness | 4: Compound]
|
||||
**WCAG**: [guideline number and level]
|
||||
**Issue**: What's wrong or missing
|
||||
**Impact**: What users with disabilities experience
|
||||
**Fix**: Code example showing the fix
|
||||
**Cross-Auditor Notes**: [if overlapping with another auditor]
|
||||
|
||||
## Recommendations
|
||||
1. [Immediate actions — CRITICAL fixes (App Store rejection risk)]
|
||||
2. [Short-term — HIGH fixes (WCAG Level A/AA compliance)]
|
||||
3. [Long-term — accessibility improvements from Phase 3 findings]
|
||||
|
||||
## Testing Checklist
|
||||
- [ ] Test with VoiceOver (Cmd+F5 on simulator)
|
||||
- [ ] Test with Dynamic Type at AX5 (Settings → Accessibility → Display & Text Size → Larger Text)
|
||||
- [ ] Test with Reduce Motion (Settings → Accessibility → Motion → Reduce Motion)
|
||||
- [ ] Test with external keyboard on iPad (Tab, arrow keys, Enter)
|
||||
```
|
||||
|
||||
## Output Limits
|
||||
|
||||
If >50 issues in one category: Show top 10, provide total count, list top 3 files
|
||||
If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
|
||||
|
||||
## False Positives (Not Issues)
|
||||
|
||||
- Decorative images with `.accessibilityHidden(true)`
|
||||
- Spacer views without labels
|
||||
- Background images marked as decorative
|
||||
- `.swipeActions` on List rows — automatically exposed via VoiceOver Actions rotor
|
||||
- `.font(.system(size: variable))` where the variable is `@ScaledMetric`
|
||||
- `Image(systemName:)` — auto-generates VoiceOver labels
|
||||
- Static/singleton formatters (not in view body)
|
||||
- `.animation(.default, value:)` — already respects Reduce Motion system setting
|
||||
|
||||
## Related
|
||||
|
||||
For comprehensive accessibility debugging: `axiom-accessibility-diag` skill
|
||||
For Dynamic Type and typography: `axiom-typography-ref` skill
|
||||
For UX flow accessibility: `axiom-ux-flow-audit` skill
|
||||
@@ -0,0 +1,3 @@
|
||||
interface:
|
||||
display_name: "Audit Accessibility"
|
||||
short_description: "The user mentions accessibility checking, App Store submission, code review, or WCAG compliance."
|
||||
Reference in New Issue
Block a user