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.
301 lines
14 KiB
Markdown
301 lines
14 KiB
Markdown
---
|
|
name: axiom-ux-flow-audit
|
|
description: Use when auditing user journeys, checking for UX dead ends, dismiss traps, buried CTAs, missing empty/loading/error states, or broken data paths in iOS apps (SwiftUI and UIKit).
|
|
license: MIT
|
|
---
|
|
|
|
# UX Flow Audit
|
|
|
|
**UX issues are not polish — they're defects that cause support tickets, bad reviews, and user churn.**
|
|
|
|
Axiom's code-level auditors check patterns. This skill checks what users actually experience: Can they complete their task? Can they get back? Do they know what's happening?
|
|
|
|
## 6 iOS UX Principles (Detection Anchors)
|
|
|
|
These principles anchor every detection category. When a principle is violated, users get stuck, confused, or frustrated.
|
|
|
|
### 1. Honor the Promise
|
|
|
|
What the button/title says must match what the user gets. A "Settings" button that opens a profile page breaks trust.
|
|
|
|
### 2. Escape Hatch
|
|
|
|
Every modal (sheet, fullScreenCover, alert) must have a way out. A sheet without a dismiss button or drag-to-dismiss traps users.
|
|
|
|
### 3. Primary Action Visibility
|
|
|
|
The main thing users came to do must be immediately visible and tappable. If the CTA requires scrolling or menu-diving, users won't find it.
|
|
|
|
### 4. Dead End Prevention
|
|
|
|
Every view must have a forward path (next step) or a completion state (success message, return to start). A view with no actions and no navigation is a dead end.
|
|
|
|
### 5. Progressive Disclosure
|
|
|
|
Don't overwhelm on first screen. Show essentials first, details on demand. An onboarding flow that dumps 12 settings on page one loses users.
|
|
|
|
### 6. Feedback Loop
|
|
|
|
Users must know what's happening during async operations. No loading state = "is it broken?" No error state = "what went wrong?" No empty state = "is this feature missing?"
|
|
|
|
## Detection Categories
|
|
|
|
**8 Core Defects** (always check — these are UX bugs, not opinions):
|
|
|
|
1. Dead-End Views (CRITICAL)
|
|
2. Dismiss Traps (CRITICAL)
|
|
3. Buried CTAs (HIGH)
|
|
4. Promise-Scope Mismatch (HIGH)
|
|
5. Deep Link Dead Ends (HIGH)
|
|
6. Missing Empty States (HIGH)
|
|
7. Missing Loading/Error States (HIGH)
|
|
8. Accessibility Dead Ends (HIGH)
|
|
|
|
**3 Contextual Checks** (check when product context warrants — these involve design judgment):
|
|
|
|
9. Onboarding Gaps (MEDIUM) — requires knowing the product's onboarding strategy
|
|
10. Broken Data Paths (MEDIUM) — overlaps with code correctness; include only when UX-visible
|
|
11. Platform Parity Gaps (MEDIUM) — depends on target device strategy
|
|
|
|
Core defects are always worth reporting. Contextual checks require product knowledge — flag them if they look wrong, but acknowledge they may be intentional decisions.
|
|
|
|
### 1. Dead-End Views (CRITICAL)
|
|
|
|
Views with no navigation forward, no actions, and no completion state.
|
|
|
|
**Detect**:
|
|
- SwiftUI: Views with no `NavigationLink`, `Button`, `.sheet`, `.fullScreenCover`, `.navigationDestination`, or dismiss action
|
|
- UIKit: View controllers with no `IBAction`, no `addTarget`, no navigation push/present calls, no `UIBarButtonItem`
|
|
- Check for views/VCs that are navigation destinations but offer no way to proceed or return
|
|
|
|
**Common cause**: Placeholder views during development that ship to production.
|
|
|
|
### 2. Dismiss Traps (CRITICAL)
|
|
|
|
Sheets or fullScreenCover without a dismiss path.
|
|
|
|
**Detect**:
|
|
- SwiftUI: `.fullScreenCover` without `@Environment(\.dismiss)` or explicit dismiss button; `.sheet` with `.interactiveDismissDisabled(true)` without alternative dismiss; alert/confirmation dialogs missing cancel actions
|
|
- UIKit: `present(_:animated:)` with `modalPresentationStyle = .fullScreen` where presented VC has no dismiss/close button; `isModalInPresentation = true` without alternative dismiss path
|
|
|
|
**Why critical**: Users literally cannot leave the screen. The only escape is force-quitting the app.
|
|
|
|
### 3. Buried CTAs (HIGH)
|
|
|
|
Primary actions hidden below fold, in menus, or behind navigation.
|
|
|
|
**Detect**:
|
|
- Primary action buttons placed after long `ScrollView` content
|
|
- Important actions only in `.toolbar` overflow menu (`.secondaryAction`)
|
|
- CTAs inside expandable `DisclosureGroup` sections
|
|
- No prominent action on the main tab's root view
|
|
|
|
**Not a buried CTA**: Below-fold placement that is intentional — checkout confirmation ("review order then confirm"), terms acceptance ("read then agree"), or content that the user should see before acting. The test: is the below-fold placement serving the user (they need context first) or hurting them (they can't find the action)?
|
|
|
|
### 4. Promise-Scope Mismatch (HIGH)
|
|
|
|
NavigationTitle, button label, or tab name doesn't match the content.
|
|
|
|
**Detect**:
|
|
- `.navigationTitle("X")` where view content is clearly about Y
|
|
- `NavigationLink("Settings")` that navigates to a profile/account view
|
|
- Tab labels that don't match tab content
|
|
- Button text suggesting one action but performing another
|
|
|
|
### 5. Deep Link Dead Ends (HIGH)
|
|
|
|
URL opens but lands on empty or broken state.
|
|
|
|
**Detect**:
|
|
- `.onOpenURL` handlers that push a view without checking if data exists
|
|
- Deep link destinations that assume pre-loaded state
|
|
- Universal link handling that doesn't validate the entity ID
|
|
- No fallback when deep-linked content is unavailable
|
|
|
|
**Cross-reference**: `axiom-swiftui-nav` covers deep link architecture. This category checks the UX outcome.
|
|
|
|
### 6. Missing Empty States (HIGH)
|
|
|
|
Lists, grids, or content views with no data show blank screen.
|
|
|
|
**Detect**:
|
|
- `List` or `ForEach` without `if items.isEmpty { ... }` or `.overlay` for empty state
|
|
- `@Query` results displayed without empty check
|
|
- Search results with no "no results" view
|
|
- Filtered views that can reach zero items
|
|
|
|
### 7. Missing Loading/Error States (HIGH)
|
|
|
|
Async operations with no feedback.
|
|
|
|
**Detect**:
|
|
- SwiftUI: `.task { }` or `Task { }` that fetches data without a loading indicator; `try await` without error presentation (no `.alert`, no error state variable); state enum missing `.loading` or `.error` cases
|
|
- UIKit: `URLSession` calls without `UIActivityIndicatorView` or progress UI; completion handlers that don't update UI on error; missing `UIAlertController` for failure cases
|
|
- Both: Network calls without timeout or retry UI
|
|
- Both: `catch` blocks that only `print`/log in `#if DEBUG` with no user-visible feedback — the user sees the operation silently fail
|
|
|
|
**Focus on network/write operations**: Skip loading indicators for fast local reads (GRDB queries, UserDefaults, cached data) that complete in under 100ms — adding spinners to these creates visual flicker. Focus on network calls, database writes, and any operation that can meaningfully fail.
|
|
|
|
**Scan systematically**: When you find a silent-error pattern in one file (e.g., `catch { print(...) }` without user feedback), scan ALL similar files for the same pattern. A single catch-block issue usually indicates a codebase-wide habit.
|
|
|
|
### 8. Accessibility Dead Ends (HIGH)
|
|
|
|
Actions only reachable via gestures or visual cues, invisible to assistive technology.
|
|
|
|
**Detect**:
|
|
- `.onLongPressGesture` / `.swipeActions` / `DragGesture` without `.accessibilityAction` equivalent
|
|
- Custom controls without `.accessibilityLabel` or `.accessibilityHint`
|
|
- Navigation that depends on color alone (e.g., "tap the green button")
|
|
- Pull-to-refresh (`refreshable`) without VoiceOver-accessible alternative (note: `refreshable` is automatically accessible — check custom implementations)
|
|
|
|
**Cross-reference**: `axiom-accessibility-diag` covers full WCAG compliance. This category specifically checks UX flow reachability from assistive technology.
|
|
|
|
### 9. Onboarding Gaps (MEDIUM)
|
|
|
|
First-launch flow that's incomplete or overwhelming.
|
|
|
|
**Detect**:
|
|
- No `@AppStorage`-gated onboarding check
|
|
- Onboarding flow without skip/later option
|
|
- More than 5 onboarding screens
|
|
- Onboarding that requires account creation before showing app value
|
|
|
|
### 10. Broken Data Paths (MEDIUM)
|
|
|
|
State/binding wiring issues that manifest as UX problems (view shows stale data, edits don't save, view appears empty when data exists).
|
|
|
|
**Detect**:
|
|
- Views accepting `@Binding` that are initialized with `.constant()` in non-preview code
|
|
- Views expecting `@Environment` values not provided by ancestors
|
|
- `@Observable` models created locally when they should be injected
|
|
- `@State` used where `@Binding` should propagate changes upward
|
|
|
|
**Scope note**: This overlaps with general SwiftUI correctness (`axiom-swiftui-debugging`). Include findings here only when the broken data path causes a visible UX problem — blank screen, stale content, edits that don't persist. Skip compiler-level or crash-level issues that belong in code review.
|
|
|
|
### 11. Platform Parity Gaps (MEDIUM)
|
|
|
|
iPad sidebar missing, landscape broken, Mac Catalyst issues.
|
|
|
|
**Detect**:
|
|
- `NavigationStack` without `NavigationSplitView` alternative for iPad
|
|
- No `.horizontalSizeClass` checks for adaptive layout
|
|
- Views that break in landscape (fixed heights, no scroll)
|
|
- Missing keyboard shortcut support on iPad/Mac
|
|
|
|
## Audit Process
|
|
|
|
### Step 1: Map Entry Points
|
|
|
|
Find all ways users enter the app:
|
|
- `@main` App struct / SceneDelegate
|
|
- `.onOpenURL` handlers (deep links)
|
|
- Widget `Link` destinations
|
|
- Notification response handlers (`UNUserNotificationCenterDelegate`)
|
|
- Spotlight/Siri intent handlers
|
|
|
|
### Step 2: Map Navigation Containers
|
|
|
|
Find all navigation structure:
|
|
- `NavigationStack` / `NavigationSplitView`
|
|
- `TabView` with tab structure
|
|
- `.sheet` / `.fullScreenCover` presentations
|
|
- Custom modal presentations
|
|
|
|
### Step 3: Trace Flows
|
|
|
|
For each entry point → completion path:
|
|
1. Can the user reach their goal?
|
|
2. Can the user get back?
|
|
3. Does the user know what's happening at each step?
|
|
|
|
### Step 4: Check Data Wiring
|
|
|
|
- Are `@Binding` vars actually passed from parent?
|
|
- Are `@Observable` objects injected via environment?
|
|
- Are `@Query` results handled for empty case?
|
|
|
|
### Step 5: Check Platform Adaptivity
|
|
|
|
- iPad: Does sidebar/split view work?
|
|
- Landscape: Does layout adapt?
|
|
- Mac Catalyst/Designed for iPad: Do keyboard shortcuts exist?
|
|
|
|
### Step 6: Check Accessibility Flows
|
|
|
|
- Can VoiceOver users complete every flow?
|
|
- Are gesture-only actions backed by accessibility actions?
|
|
|
|
## Cross-Auditor Correlation
|
|
|
|
When findings overlap with other Axiom auditors, note the correlation and elevate severity:
|
|
|
|
| UX Finding | Overlapping Auditor | Compound Effect | Severity Bump |
|
|
|------------|-------------------|-----------------|---------------|
|
|
| Dead end + missing NavigationPath | swiftui-nav-auditor | Programmatic fix impossible | CRITICAL |
|
|
| Gesture-only action + no `.accessibilityAction` | accessibility-auditor | Dead end for VoiceOver users | CRITICAL |
|
|
| Missing loading state + unhandled async error | concurrency-auditor | Crash + no user feedback | CRITICAL |
|
|
| Missing empty state + @Query with no results | swiftdata-auditor | Blank screen after data migration | HIGH |
|
|
| Deep link dead end + no URL validation | swiftui-nav-auditor | Silent failure from external link | HIGH |
|
|
|
|
## Output Format
|
|
|
|
### Enhanced Rating Table (for CRITICAL and HIGH findings)
|
|
|
|
| Finding | Urgency | Blast Radius | Fix Effort | ROI |
|
|
|---------|---------|-------------|-----------|-----|
|
|
| Dead-end after payment | Ship-blocker | All users | 30 min | Critical |
|
|
| Missing empty state on search | Next release | Users who search | 15 min | High |
|
|
|
|
**Urgency**: Ship-blocker / Next release / Backlog
|
|
**Blast Radius**: All users / Specific flow / Edge case
|
|
**Fix Effort**: Time estimate for the fix
|
|
**ROI**: Computed from urgency x blast radius / effort
|
|
|
|
### Navigation Reachability Score
|
|
|
|
At end of audit, output:
|
|
|
|
```
|
|
## Navigation Reachability
|
|
|
|
- Total screens found: [N] (views with navigation presentation)
|
|
- Deep-linkable screens: [N] (.onOpenURL can reach them)
|
|
- Widget-reachable screens: [N] (widget Link destinations)
|
|
- Notification-reachable screens: [N] (notification handlers)
|
|
- Coverage: [N]% of screens are externally reachable
|
|
```
|
|
|
|
## Fix Effort Reality Check
|
|
|
|
Most UX flow defects are fast fixes. When someone says "that's a big change," check this table:
|
|
|
|
| Defect | Typical Fix | Time |
|
|
|--------|------------|------|
|
|
| Dismiss trap (no close button) | Add toolbar Cancel button + `dismiss()` | 10-15 min |
|
|
| Missing empty state | Add `if items.isEmpty { ContentUnavailableView(...) }` | 15-20 min |
|
|
| Buried CTA (placement change) | Move button from `.secondaryAction` to `.primaryAction` | 20-30 min |
|
|
| Dead-end view (no forward path) | Add NavigationLink or action button | 15-30 min |
|
|
| Missing loading state | Add `@State var isLoading` + ProgressView overlay | 15-20 min |
|
|
| Silent error (no user feedback) | Add `.alert` presentation on catch block | 10-15 min |
|
|
| Gesture-only action | Add `.accessibilityAction` + visible button alternative | 15-20 min |
|
|
|
|
**The cost of NOT fixing**: A dismiss trap or dead end after payment generates 1-star reviews within hours of launch. Each review costs 10-20 positive reviews to offset. The 15-minute fix prevents weeks of damage control.
|
|
|
|
## Anti-Rationalization
|
|
|
|
| Thought | Reality |
|
|
|---------|---------|
|
|
| "UX issues are just polish, we'll fix later" | UX dead ends cause 1-star reviews. They're defects, not enhancements. A 15-min fix now prevents weeks of damage control. |
|
|
| "Users will figure it out" | Users don't figure it out. They delete the app. Average user tries for 30 seconds. |
|
|
| "We'll add empty states after launch" | Empty states are the FIRST thing new users see. Launching without them means launching broken. |
|
|
| "That fix is a big design change" | Most UX fixes are placement or state changes (10-30 min). Check the Fix Effort table above. |
|
|
| "Accessibility is a separate concern" | If VoiceOver users can't complete a flow, it's a dead end. Same defect, different user. |
|
|
| "This screen is just temporary" | Temporary screens ship. Check them anyway. |
|
|
| "The dismiss gesture handles it" | fullScreenCover has no dismiss gesture. That's the trap. |
|
|
|
|
## Resources
|
|
|
|
**Skills**: axiom-swiftui-nav, axiom-accessibility-diag, axiom-hig, axiom-swiftui-debugging
|
|
|
|
**Agents**: ux-flow-auditor (automated scanning), swiftui-nav-auditor (navigation architecture), accessibility-auditor (WCAG compliance)
|