npx claudepluginhub charleswiltgen/axiom --plugin axiomThis skill uses the workspace's default tool permissions.
**UX issues are not polish — they're defects that cause support tickets, bad reviews, and user churn.**
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
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?
These principles anchor every detection category. When a principle is violated, users get stuck, confused, or frustrated.
What the button/title says must match what the user gets. A "Settings" button that opens a profile page breaks trust.
Every modal (sheet, fullScreenCover, alert) must have a way out. A sheet without a dismiss button or drag-to-dismiss traps users.
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.
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.
Don't overwhelm on first screen. Show essentials first, details on demand. An onboarding flow that dumps 12 settings on page one loses users.
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?"
8 Core Defects (always check — these are UX bugs, not opinions):
3 Contextual Checks (check when product context warrants — these involve design judgment):
Core defects are always worth reporting. Contextual checks require product knowledge — flag them if they look wrong, but acknowledge they may be intentional decisions.
Views with no navigation forward, no actions, and no completion state.
Detect:
NavigationLink, Button, .sheet, .fullScreenCover, .navigationDestination, or dismiss actionIBAction, no addTarget, no navigation push/present calls, no UIBarButtonItemCommon cause: Placeholder views during development that ship to production.
Sheets or fullScreenCover without a dismiss path.
Detect:
.fullScreenCover without @Environment(\.dismiss) or explicit dismiss button; .sheet with .interactiveDismissDisabled(true) without alternative dismiss; alert/confirmation dialogs missing cancel actionspresent(_:animated:) with modalPresentationStyle = .fullScreen where presented VC has no dismiss/close button; isModalInPresentation = true without alternative dismiss pathWhy critical: Users literally cannot leave the screen. The only escape is force-quitting the app.
Primary actions hidden below fold, in menus, or behind navigation.
Detect:
ScrollView content.toolbar overflow menu (.secondaryAction)DisclosureGroup sectionsNot 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)?
NavigationTitle, button label, or tab name doesn't match the content.
Detect:
.navigationTitle("X") where view content is clearly about YNavigationLink("Settings") that navigates to a profile/account viewURL opens but lands on empty or broken state.
Detect:
.onOpenURL handlers that push a view without checking if data existsCross-reference: axiom-swiftui-nav covers deep link architecture. This category checks the UX outcome.
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 checkAsync operations with no feedback.
Detect:
.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 casesURLSession calls without UIActivityIndicatorView or progress UI; completion handlers that don't update UI on error; missing UIAlertController for failure casescatch blocks that only print/log in #if DEBUG with no user-visible feedback — the user sees the operation silently failFocus 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.
Actions only reachable via gestures or visual cues, invisible to assistive technology.
Detect:
.onLongPressGesture / .swipeActions / DragGesture without .accessibilityAction equivalent.accessibilityLabel or .accessibilityHintrefreshable) 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.
First-launch flow that's incomplete or overwhelming.
Detect:
@AppStorage-gated onboarding checkState/binding wiring issues that manifest as UX problems (view shows stale data, edits don't save, view appears empty when data exists).
Detect:
@Binding that are initialized with .constant() in non-preview code@Environment values not provided by ancestors@Observable models created locally when they should be injected@State used where @Binding should propagate changes upwardScope 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.
iPad sidebar missing, landscape broken, Mac Catalyst issues.
Detect:
NavigationStack without NavigationSplitView alternative for iPad.horizontalSizeClass checks for adaptive layoutFind all ways users enter the app:
@main App struct / SceneDelegate.onOpenURL handlers (deep links)Link destinationsUNUserNotificationCenterDelegate)Find all navigation structure:
NavigationStack / NavigationSplitViewTabView with tab structure.sheet / .fullScreenCover presentationsFor each entry point → completion path:
@Binding vars actually passed from parent?@Observable objects injected via environment?@Query results handled for empty case?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 |
| 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
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
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.
| 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. |
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)