UI Path Radar
Quick Ref: 5-layer UI path audit: discover entry points → trace flows → detect issues → evaluate UX → verify data wiring. Output:
.ui-path-radar/in project root.
You are performing a systematic UI path audit on this SwiftUI application.
Required output: Every finding MUST include Urgency, Risk, ROI, and Blast Radius ratings using the Issue Rating Table format. Do not omit these ratings.
Quick Commands
| Command | Description |
|---|---|
/ui-path-radar | Full 5-layer audit |
/ui-path-radar layer1 | Discovery only — find all entry points |
/ui-path-radar layer2 | Trace — trace critical paths |
/ui-path-radar layer3 | Issues — detect problems across codebase |
/ui-path-radar layer4 | Evaluate — assess user impact |
/ui-path-radar layer5 | Data wiring — verify real data usage |
/ui-path-radar trace "A → B → C" | Trace a specific user flow path |
/ui-path-radar diff | Compare current findings against previous audit |
/ui-path-radar fix | Generate fixes for found issues |
/ui-path-radar status | Show audit progress and remaining issues |
--show-suppressed | Show findings suppressed by known-intentional entries |
--accept-intentional | Mark current finding as known-intentional (not a bug) |
Overview
UI Path Radar uses a 5-layer approach:
| Layer | Purpose | Est. Time (small / large codebase) | Output |
|---|---|---|---|
| Layer 1 | Pattern Discovery — Find all UI entry points | ~1-2 min / ~3-5 min | Entry point inventory |
| Layer 2 | Flow Tracing — Trace critical paths in depth | ~2-3 min / ~5-8 min | Detailed flow traces |
| Layer 3 | Issue Detection — Categorize issues across codebase | ~2-4 min / ~5-10 min | Issue catalog |
| Layer 4 | Semantic Evaluation — Evaluate from user perspective | ~1-2 min / ~3-5 min | UX impact analysis |
| Layer 5 | Data Wiring — Verify features use real data | ~2-4 min / ~5-10 min | Data integrity report |
Codebase size guide: Small = <200 files, Large = 500+ files. Estimate from
find Sources -name "*.swift" | wc -l.
Issue Categories
Each category maps to a default axis (see skills/radar-suite-axis-classification/SKILL.md for the framework). The default axis may be overridden by the verification checklist (see "Axis Classification Protocol" section below) — e.g., a "Dead End" finding whose branch is unreachable from any production call site gets reclassified from axis_1_bug to axis_3_dead_code by the reachability trace.
| Category | Severity | Default Axis | Description |
|---|---|---|---|
| Dead End | 🔴 CRITICAL | axis_1_bug (→ axis_3_dead_code if unreachable) | Entry point leads nowhere |
| Wrong Destination | 🔴 CRITICAL | axis_1_bug | Entry point leads to wrong place |
| Mock Data | 🔴 CRITICAL | axis_1_bug | Feature shows fabricated data when real data exists |
| Destructive Without Confirmation | 🔴 CRITICAL | axis_1_bug | Delete/clear happens immediately without confirmation dialog |
| Silent State Reset | 🔴 CRITICAL | axis_1_bug | In-progress work lost when navigating away and back (form clears, selections lost) |
| Incomplete Navigation | 🟡 HIGH | axis_1_bug | User must scroll/search after landing |
| Missing Auto-Activation | 🟡 HIGH | axis_1_bug | Expected mode/state not set |
| Unwired Data | 🟡 HIGH | axis_1_bug (→ axis_3_smelly if model field has no read/write sites) | Model data exists but feature ignores it |
| Platform Parity Gap | 🟡 HIGH | axis_1_bug | Feature works on one platform, broken on another |
| Promise-Scope Mismatch | 🟡 HIGH | axis_1_bug | Specific CTA opens generic/broad destination |
| Buried Primary Action | 🟡 HIGH | axis_1_bug | Primary button hidden below scroll fold |
| Dismiss Trap | 🟡 HIGH | axis_1_bug | Only visible action is Cancel/back, no forward path |
| Context Dropping | 🟡 HIGH | axis_1_bug | Navigation path loses item context between platforms or via notifications |
| Notification Nav Fragility | 🟡 HIGH | axis_1_bug | Untyped NotificationCenter dict used for navigation context |
| Sheet Presentation Asymmetry | 🟡 HIGH | axis_2_scatter (→ axis_1_bug if only one platform works) | Different presentation mechanisms per platform for same feature |
| Empty State Missing | 🟡 HIGH | axis_1_bug (→ axis_3_dead_code if empty case unreachable) | No guidance when list/view is empty — users think app is broken |
| Error Recovery Missing | 🟡 HIGH | axis_1_bug | Error displayed but no retry button or recovery path |
| Keyboard Obscures Input | 🟡 HIGH | axis_1_bug | Text field covered by keyboard with no scroll adjustment (iOS) |
| Permission Denied Dead End | 🟡 HIGH | axis_1_bug | Permission denied but no explanation or path to Settings |
| Modal Stacking | 🟡 HIGH | axis_1_bug | Multiple sheets/alerts open on top of each other |
| Navigation Container Mismatch | 🟡 HIGH | axis_1_bug | selectedSection value not a valid tag in current TabView/sidebar |
| Two-Step Flow | 🟢 MEDIUM | axis_1_bug | Intermediate selection required |
| Missing Feedback | 🟢 MEDIUM | axis_1_bug | No confirmation of success |
| Gesture-Only Action | 🟢 MEDIUM | axis_1_bug | Feature only accessible via swipe/long-press |
| Loading State Trap | 🟢 MEDIUM | axis_1_bug | Spinner with no cancel/timeout/escape |
| Stale Navigation Context | 🟢 MEDIUM | axis_2_scatter (→ axis_1_bug if user observes stale data) | Cached context with no clearing/validation mechanism |
| Phantom Touch Target | 🟢 MEDIUM | axis_1_bug | Visual element looks tappable but isn't (icon without action, card without nav) |
| Race Condition UX | 🟢 MEDIUM | axis_1_bug | User can trigger conflicting operations simultaneously (double-tap, edit while sync) |
| Invisible Selection | 🟢 MEDIUM | axis_1_bug | Item is selected/active but visual indicator missing or too subtle |
| Inconsistent Pattern | ⚪ LOW | axis_2_scatter | Same feature accessed differently |
| Orphaned Code | ⚪ LOW | axis_3_dead_code (if unreachable) or axis_3_smelly (if reachable but unjustified) | Feature exists but no entry point |
| Command-Palette-Only Feature | 🟡 HIGH | axis_1_bug | Feature reachable only via command palette (QuickFind/Go To), no visible UI entry point |
| Deeply Buried Feature | 🟡 HIGH | axis_1_bug | User-facing feature requires 4+ taps from nearest tab bar item |
| Double-Nested Navigation | ⚪ LOW | axis_2_scatter | NavigationStack inside NavigationStack causing doubled nav bars |
Axis Classification Protocol (MANDATORY — before emitting any finding)
Every finding must be classified on the 3-axis framework and pass the schema gate in radar-suite-core.md before emission. The protocol:
-
Assign default axis from the table above based on the issue category.
-
Run required verification checks:
- Reachability trace (MANDATORY for Dead End, Empty State Missing, Orphaned Code) — walk upstream from the flagged branch at least 2 call-site levels. If no production call site reaches it, RECLASSIFY to
axis_3_dead_code. - Whole-file scan (MANDATORY for "missing handler" categories: Empty State Missing, Error Recovery Missing, Missing Feedback) — read the ENTIRE file (not just the flagged region) for handlers elsewhere. If found, RECLASSIFY to
axis_2_scatter. - Branch enumeration (MANDATORY for Platform Parity Gap, Sheet Presentation Asymmetry) — read BOTH sides of every
#if os(iOS)/#elseblock before claiming platform-broken. Stuffolio has 266 such blocks; dropping the#elsebranch is the #1 false-positive source. - Pattern citation lookup (MANDATORY for every finding, regardless of category) — grep the audited codebase for a similar pattern shape. Cite by file:line in the
better_approachfield. A finding without this citation is REJECTED.
- Reachability trace (MANDATORY for Dead End, Empty State Missing, Orphaned Code) — walk upstream from the flagged branch at least 2 call-site levels. If no production call site reaches it, RECLASSIFY to
-
Write coaching fields. Populate
current_approach,suggested_fix,better_approach(with citation), `better_approach_trade