| name | ui-path-radar |
| description | UI path tracer for SwiftUI/UIKit apps. 5-layer audit with 30 issue categories: discover entry points, trace flows, detect dead ends and broken promises, evaluate UX impact, verify data wiring. Supports targeted trace, diff against previous audits, and handoff to planning skills. Triggers: "trace UI paths", "find dead ends", "/ui-path-radar". |
| version | 2.2.0 |
| author | Terry Nyberg |
| license | MIT |
| allowed-tools | ["Read","Grep","Glob","Bash","Edit","Write","AskUserQuestion"] |
| inherits | radar-suite-core.md |
| metadata | {"tier":"execution","category":"analysis"} |
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) / #else block before claiming platform-broken. Stuffolio has 266 such blocks; dropping the #else branch 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_approach field. A finding without this citation is REJECTED.
-
Write coaching fields. Populate current_approach, suggested_fix, better_approach (with citation), better_approach_tradeoffs โ all mandatory. Load coaching examples via .radar-suite/project.yaml coaching_examples array.
-
Validate against schema gate. Run the gate checks from radar-suite-core.md. If any mandatory field is missing, either fix the finding or downgrade confidence to possible and increment rejected_no_citation in the handoff.
-
Write the finding to the handoff YAML with full axis + coaching fields.
Reclassification logging: When the verification checklist reclassifies a finding's axis (e.g., "Dead End" โ axis_3_dead_code via reachability trace), log the reclassification in the finding's verification_log so capstone and the user can see the framework caught a would-be false positive:
verification_log:
- check: reachability_trace
result: "no production call site found; reclassified from axis_1_bug (Dead End) to axis_3_dead_code"
Axis summary block. At the end of the handoff, include:
axis_summary:
axis_1_bug: [count]
axis_2_scatter: [count]
axis_3_dead_code: [count]
axis_3_smelly: [count]
rejected_no_citation: [count]
Design Principles
1. Honor the Promise
When a button/card says "Do X", tapping it should DO X.
Not "go somewhere you might find X."
2. Context-Aware Shortcuts
If user's context implies a specific item, skip pickers.
3. State Preservation
When navigating to a feature, set up the expected state.
4. Consistent Access Patterns
Same feature should be accessed the same way everywhere.
5. Data Integrity
If the app tracks data relevant to a feature, the feature must use it.
Never show mock/hardcoded data when real user data exists.
Never ignore model relationships that would improve decisions.
6. Primary Action Visibility
The primary action must be visible without scrolling after the user completes the key interaction.
Pin Save/Continue/Done buttons outside ScrollView or in toolbar. Never bury them below tall content.
7. Escape Hatch
Every view must have a visible way to go forward OR back. Cancel alone is not enough after user completes a step.
8. Gesture Discoverability
Every action available via gesture (swipe, long-press) should also be accessible via a visible button or menu.
Freshness
Base all findings on current source code only. Do not read or reference
files in .agents/, scratch/, or prior audit reports. Ignore cached
findings from auto-memory or previous sessions. Every finding must come
from scanning the actual codebase as it exists now.
Before Starting
Ask the user:
Question 1: "How should fixes be handled?"
- Auto-fix safe items (Recommended) โ Apply isolated, low-blast-radius fixes automatically. Present cross-cutting fixes and design decisions for approval first.
- Review first โ Present all findings with ratings, then ask before making any changes. Fixes still happen โ you just approve each wave first.
IMPORTANT: Both modes lead to fixes. "Review first" means the user sees the plan before code changes โ it does NOT mean "skip fixes and jump to handoff." After presenting findings, ALWAYS offer to fix them regardless of which mode was selected.
Question 2: "How should results be delivered?"
- Display only (Recommended) โ Show findings in the conversation. No file written.
- Report only โ Write findings to
.ui-path-radar/[DATE]-audit.md. Minimal conversation output. Before writing, per Artifact Lifecycle (Class 3) in radar-suite-core.md, archive any existing .ui-path-radar/*-audit.md to .ui-path-radar/archive/superseded/.
- Display and report โ Show findings in the conversation AND write to file.
Question 3: "What's your experience level with Swift/SwiftUI?"
- Beginner โ New to Swift or SwiftUI. Explain findings in plain language with analogies. Define technical terms on first use.
- Intermediate โ Comfortable with SwiftUI basics. Use standard terminology but explain non-obvious patterns.
- Experienced (Recommended) โ Fluent with SwiftUI, navigation, state management. Concise findings, no definitions.
- Senior/Expert โ Deep expertise. Terse output, file:line references only, skip explanations โ just the findings table.
Question 4: "Will you be stepping away during the audit?"
- I'll be here (Recommended) โ Normal mode. Permission prompts may appear for writes/edits.
- Hands-free (walk away safe) โ Read-only tools (Read, Grep, Glob) for Layers 1-4. No Bash, no Edit, no Write. Results held in conversation output.
- Pre-approved โ You have already configured Claude Code permissions. Run at full speed.
Permission Modes
Normal Mode
- Read any file without asking.
- Edit files only if user chose auto-fix and the fix is isolated to the audited flow.
- Build and run tests without asking.
- If a fix breaks the build, restore the original code and document as "Documented."
Hands-Free Mode
Guarantees no blocking prompts. Only uses: Read, Grep, Glob.
Does NOT use: Bash, Edit, Write, AskUserQuestion.
When complete:
Hands-free audit complete through Step [N] of 5: [plain description].
Steps requiring your input: [list with plain descriptions]
Reply to continue with supervised steps.
Pre-Approved Mode
Full speed, no restrictions. Assumes you've set up permissions.
Permission Setup (for unattended runs)
# Already safe by default (no setup needed):
Read, Grep, Glob โ always auto-approved
# Add these for unattended Bash scans:
Bash(find:*)
Bash(wc:*)
Bash(stat:*)
Do NOT auto-approve (keep prompted โ they modify state):
Edit, Write โ file modifications
Bash(rm:*), Bash(git:*) โ destructive operations
Tip: If you frequently run audit-only layers (1-4), the Hands-free mode eliminates permission prompts entirely without changing any settings.
Context Budget
If context is running low, prioritize in this order:
- Finish the current phase
- Emit findings for what you've audited so far
- Skip remaining unaudited flows
Never start auditing a new flow you can't finish.
Experience-Level Adaptation
Adjust ALL output based on the user's experience level:
Beginner
- Use plain language with real-world analogies.
- Define technical terms on first use in parentheses.
- Explain flags/categories and why they matter.
- Include file context: "DashboardView.swift (the main home screen) line 118"
- Explain the "why" behind each suggestion.
- Use compact 4-column table format.
Intermediate
- Use standard SwiftUI terminology without defining basics.
- Explain non-obvious patterns.
- Standard file:line format.
- Full 9-column format with brief finding descriptions.
Experienced (default)
- Concise findings. No definitions or explanations of standard patterns.
- Recommendations: what to fix, where.
- Full 9-column format, terse findings.
Senior/Expert
- Minimal findings text. No prose between tables.
- File:line + one-line fix description only.
- Skip: progress explanations, design principle citations, category definitions.
- Full 9-column format, maximally compressed.
Enforcement Rule
At the start of each layer, silently check: "Am I writing at the selected experience level?" Do NOT drift toward "Experienced" as a default.
Execution Instructions
Skill Introduction (MANDATORY โ run before anything else)
On first invocation, ask the user two questions in a single AskUserQuestion call:
Question 1: "What's your experience level with Swift/SwiftUI?"
- Beginner โ New to Swift. Plain language, analogies, define terms on first use.
- Intermediate โ Comfortable with SwiftUI basics. Standard terms, explain non-obvious patterns.
- Experienced (Recommended) โ Fluent with SwiftUI. Concise findings, no definitions.
- Senior/Expert โ Deep expertise. Terse, file:line only, skip explanations.
Question 2: "Would you like a brief explanation of what this skill does?"
- No, let's go (Recommended) โ Skip explanation, proceed to audit.
- Yes, explain it โ Show a 3-5 sentence explanation adapted to the user's experience level, then proceed.
Experience-adapted explanations:
-
Beginner: "UI Path Radar checks every button, link, and menu item in your app to make sure they actually work. Think of it like walking through every door in a building to verify none are locked, lead nowhere, or open to the wrong room. It finds 'dead ends' (buttons that do nothing), 'broken promises' (a button says 'Export PDF' but opens the wrong screen), and missing features. It runs in 5 layers, each going deeper โ from finding all the buttons, to tracing what happens when you tap them, to checking if the data behind them is real."
-
Intermediate: "UI Path Radar systematically audits all UI entry points (sheets, navigation links, toolbar buttons, deep links, notifications) across your SwiftUI app. It traces user flows end-to-end, flags dead ends, promise-scope mismatches, platform gaps, and orphaned state. Five layers: discovery โ flow tracing โ issue detection โ UX evaluation โ data wiring verification."
-
Experienced: "5-layer UI path audit: entry point discovery, flow tracing, issue detection (dead ends, promise mismatches, orphaned state, platform gaps), semantic UX evaluation, and data wiring verification. Outputs issue rating tables with fix plans."
-
Senior/Expert: "Entry point โ flow trace โ issue scan โ UX eval โ data wiring. Rating tables + fix plans."
Store the experience level as USER_EXPERIENCE and apply to ALL output for the session.
User impact explanations: Can be toggled at any time with --explain / --no-explain. When enabled, each finding gets a 3-line companion explanation (what's wrong, fix, user experience before/after). See the shared rating system doc for format and rules. Store as EXPLAIN_FINDINGS (default: false).
Experience-level auto-apply: If USER_EXPERIENCE = Beginner, auto-set EXPLAIN_FINDINGS = true and default sort to impact. If Senior/Expert, default sort to effort. Apply all output rules from Experience-Level Output Rules table in radar-suite-core.md.
Shared Patterns
See radar-suite-core.md for: Tier System, Pipeline UX Enhancements, Table Format, Plain Language Communication, Work Receipts, Contradiction Detection, Finding Classification, Audit Methodology, Context Exhaustion, Progress Banner, Issue Rating Tables, Handoff YAML schema, Known-Intentional Suppression, Pattern Reintroduction Detection, Experience-Level Output Rules, Implementation Sort Algorithm, short_title requirement.
Pre-Scan Startup (MANDATORY โ before any layer scan)
-
Known-intentional check: Read .radar-suite/known-intentional.yaml (if exists). Store as KNOWN_INTENTIONAL. Before presenting any finding during the audit, check it against these entries. If file + pattern match, skip silently and increment intentional_suppressed counter.
-
Pattern reintroduction check: Read .radar-suite/ledger.yaml for status: fixed findings with pattern_fingerprint and grep_pattern. For each, grep the codebase. If the pattern appears in a new file without the exclusion_pattern, report as "Reintroduced pattern" at ๐ก HIGH urgency.
When invoked, perform the audit:
If no arguments or "full":
Before starting, print:
Full Audit: 5 steps โ estimated total: ~10-30 min depending on codebase size
Step 1: Find all entry points โ Step 2: Trace how users navigate โ Step 3: Detect issues โ Step 4: Evaluate user impact โ Step 5: Verify data wiring
Run all 5 layers sequentially, outputting findings to .ui-path-radar/ in the project root.
Between layers, print: โ Step [N] of 5 complete: [plain description] โ starting Step [N+1]: [plain description]
If "layer1" or "discovery": enumerate-required
Before starting, count Swift files and print an estimate:
Layer 1: Discovery โ scanning [N] Swift files
Estimated time: ~[1-2 min for <200 files / 3-5 min for 500+ files]
Progress will be shown after each tier completes.
Scan in 3 tiers, from top-level down. After completing each tier, print a progress line:
Tier 1: Top-Level Structure
- Find the app's navigation skeleton:
TabView, NavigationSplitView, sidebar sections
- For each top-level destination, identify the view file
- Scan for sheet routing enums:
grep -r "enum.*Sheet\|enum.*SheetType" Sources/
- Scan for navigation state:
grep -r "selectedSection\|selectedTab\|activeSheet" Sources/
After Tier 1, print: Layer 1: โ Tier 1 Structure (1/3) โ found [N] tabs, [N] sidebar items, [N] sheet enums
Tier 2: Entry Point Patterns
Scan for these patterns across ALL source files:
| Pattern | Search | Priority |
|---|
| Sheets | .sheet(, .fullScreenCover( | HIGH โ primary feature access |
| Navigation | NavigationLink(, .navigationDestination( | HIGH โ screen transitions |
| Tab views | TabView, .tabItem( | HIGH โ top-level entry points |
| Buttons with state | Button(.*{ near showing or activeSheet or selected | HIGH โ action triggers |
| Deep links | onOpenURL, DeepLinkRouter, URL scheme handlers | HIGH โ external entry points |
| Notification nav | .onReceive(NotificationCenter, NotificationCenter.default near navigation/sheet state | HIGH โ invisible triggers |
| Spotlight/Handoff | onContinueUserActivity, CSSearchableItemActionType | HIGH โ system search entry |
| MenuBarExtra / CommandGroup | MenuBarExtra, CommandGroup, .commands { | HIGH โ macOS app menu commands |
| File operations | .fileImporter, .fileExporter, PhotosPicker | MEDIUM โ data entry paths |
| Context menus | .contextMenu { | MEDIUM โ long-press actions |
| Swipe actions | .swipeActions | MEDIUM โ list row shortcuts |
| Toolbars | .toolbar { with Button | MEDIUM โ persistent actions |
| Keyboard shortcuts | .keyboardShortcut | MEDIUM โ power user entry |
| Promotion cards | PromotionCard, CompactPromotionCard, dismissable feature cards | MEDIUM โ conditional entry |
| Confirmation dialogs | .confirmationDialog(, .alert( | LOW โ exit gates, not entry points |
Zero-match handling: When a scanned pattern returns 0 results, do NOT silently skip it. Report: "<PatternName>: 0 matches โ pattern not present in codebase". This prevents confusion where a category appears "clean" when it was never scanned.
After Tier 2, print: Layer 1: โ Tier 2 Patterns (2/3) โ found [N] entry points across [N] patterns
Tier 3: Container View Enumeration
For views that are feature hubs (tools, settings, reports, dashboards), don't just log the hub as one entry point โ enumerate each actionable card/row inside it as a sub-entry-point. These hubs often contain 10-20+ features that the Tier 2 scan misses because they're wired through enum-based routing, not direct .sheet() modifiers.
Also identify the primary detail view (the view users spend the most time in) and audit its sheet/action surface separately โ it often has the largest entry point count.
After Tier 3, print: Layer 1: โ Tier 3 Containers (3/3) โ enumerated [N] hub views, [N] sub-entry-points added
Catalog Rules
For each entry point found:
| Field | Description |
|---|
| Label | What the user sees (button text, card title, menu item) |
| Location | File and line where the trigger lives |
| Action type | Sheet, navigation, state change, deep link, notification, keyboard shortcut |
| Destination | What view/screen opens |
| Depth | Hierarchy level: L0 (tab/sidebar) โ L1 (section view) โ L2 (detail/sheet) โ L3 (sub-sheet) |
| Condition | When is this entry point visible? (e.g., "only if items exist", "after dismissal: never") |
| Flags | Suspicious patterns (see below) |
Flags to apply during discovery:
| Flag | Description |
|---|
dead_end | Trigger exists but destination is missing or broken |
promise_mismatch | Specific label opens generic/broad destination |
incomplete_nav | Lands on section top, not the specific feature |
missing_state | Navigation without setting up expected mode/state |
two_step | Requires intermediate picker before reaching feature |
no_feedback | Action completes without confirmation |
orphaned | View exists but has no entry point |
platform_gap | Works on one platform, broken on another |
alias | Entry point mirrors another entry point's destination. Group aliases separately โ they confirm redundancy, not new paths |
conditional | Entry point disappears after user action. Scan for: @AppStorage keys containing hasDismissed, hasUsed, hasScanned, hasSeen; if !settings.someFlag guards around UI elements; .onboardingComplete checks. |
invisible | Entry point triggered by notification, deep link, or Spotlight โ not visible in the UI hierarchy |
callback_wiring | View takes an onComplete, onItemSelected, or similar closure โ trace what the closure does |
container_mismatch | Navigation sets selectedSection to a value that may not be a valid tag in the current container |
De-duplication Rules
- Context menus repeated on identical UI elements (e.g., same menu on product/receipt/nameplate photos): catalog once, note multiplier (e.g., "ร3 photo types")
- QuickFind / Spotlight / Siri Shortcuts that mirror other entry points: tag as
alias, group in a separate "Meta-Entry-Points" section at the end
- Confirmation dialogs: list separately as "Exit Gates" โ they guard destructive actions, not open features
Discovery Output
Group the table by hierarchy level, not flat:
## L0: Top-Level Navigation (tabs, sidebar)
| # | Label | Location | Action | Destination | Flags |
## L1: Section Views (dashboard, tools, settings, detail)
| # | Label | Location | Action | Destination | Flags |
## L2: Feature Sheets & Sub-Navigation
| # | Label | Location | Action | Destination | Flags |
## Meta-Entry-Points (QuickFind, Spotlight, Deep Links, Keyboard Shortcuts)
| # | Label | Location | Action | Mirrors # | Flags |
## Exit Gates (Confirmation Dialogs)
| # | Label | Location | Guards |
After the tables, list:
- Total entry points found (primary + aliases)
- Count by flag type
- Count by depth level
- Recommended flows to audit in Layer 2 (flagged entries first, deepest paths second)
If "layer2" or "trace" (no path argument): enumerate-required
Before starting, print:
Layer 2: Flow Tracing โ [N] flagged entry points to trace
Estimated time: ~[2-3 min for <5 flows / 5-8 min for 10+ flows]
- Read flagged entry points from Layer 1
- For each flagged entry point, trace the complete user journey. After each flow, print:
Layer 2: โ Flow [N]/[total] โ "[flow name]"
- Document in
layer2-traces/flow-XXX.yaml
- Identify gaps between expected and actual journeys
IMPORTANT โ Trace into callbacks and closures:
When tracing a flow, do NOT stop at the view presentation. If a sheet presents a view that takes an onComplete, onItemSelected, or similar closure, trace what that closure does. An empty closure { } or unimplemented handler is a dead end.
IMPORTANT โ Verify navigation container constraints:
At each navigation step, if selectedSection = .X is set:
- Check whether
.X is a valid tag in the current navigation container (TabView tags on iPhone, sidebar items on iPad/macOS)
- If it's not a valid tag, the assignment is a no-op โ flag as dead end
- Different platforms may have different valid tags โ check both
Check both pre-action AND post-action feedback:
- Pre-action: Is there a confirmation dialog before destructive operations?
- Post-action: Is there a toast, haptic, or visual confirmation after non-destructive actions (save, duplicate, archive)?
Flow Trace Template
flow_trace:
id: "flow-001"
name: "Feature Name from Entry Point"
entry_point: "entry-point-id"
steps:
- step: 1
action: "User taps [button/card/menu item]"
file: "SourceFile.swift:78"
code: "selectedSection = .tools"
issues: []
- step: 2
action: "App navigates to [destination]"
file: "DestinationView.swift"
result: "[what user sees]"
issues:
- category: "incomplete_navigation"
detail: "User must scroll to find feature"
expected_journey:
- "[step 1]"
- "[step 2]"
actual_journey:
- "[step 1]"
- "[extra step]"
- "[step 2]"
gap_analysis:
type: "[issue category]"
extra_steps: 2
user_confusion_risk: "medium"
If "trace" with path argument (e.g., trace "Dashboard โ Add Item โ Photo โ Save"):
Targeted flow trace โ trace a specific user journey described in natural language:
- Parse the path description into discrete steps (split on
โ, ->, or ,)
- For each step, identify the SwiftUI view, button, or action that triggers it
- Trace the complete code path step by step (file, line, state changes, view transitions)
- At each step, check for issues (Buried Primary Action, Dismiss Trap, Promise-Scope Mismatch, Missing Feedback, Callback Wiring)
- Document the trace and any issues found
- Output: Issue Rating Table for any findings, plus the step-by-step trace
If "layer3" or "issues": mixed
Before starting, print:
Layer 3: Issue Detection โ scanning [N] entry points for issues
Estimated time: ~[2-4 min for <100 entries / 5-10 min for 200+]
CRITICAL: Do NOT delegate Layer 3 checks to Explore subagents. Run each check directly using Grep/Glob/Read tools against the full codebase. Subagent sampling causes false negatives โ a previous audit checked 1 of 7 list views for gesture-only actions and concluded "all clear," missing 6 findings.
Step 0 โ Cross-layer verification (MANDATORY):
Before scanning for new issues, re-verify ALL flagged findings from Layer 1 and Layer 2. For each:
- Check whether the flag still holds when you look at extension files (
+Sections.swift, +Actions.swift, etc.) and ViewModel bindings (@Bindable, Binding<Bool> parameters)
- If a finding was based on "this @State var is never set to true" โ also check if it's passed as a
$binding to a ViewModel method or child view that sets .wrappedValue = true
- Retract false positives explicitly:
~~#N โ [original finding]~~ RETRACTED: [reason]
- Print:
Layer 3: โ Cross-layer verification โ [N] confirmed, [N] retracted
Then proceed with automated checks 1-18 below.
After each check, print: Layer 3: โ Check [N] [check name] ([N]/18) โ [N] findings so far
Automated Check 1: Sheet Case Coverage
What it detects: Enum cases defined in a sheet routing enum (e.g., DashboardSheetType) that have no corresponding handler in the sheetContent(for:) switch โ the case exists but presenting it shows nothing or crashes.
How to detect:
grep -n "case " <SheetEnumFile>.swift | grep -v "//"
grep -n "case \." <SheetContentFile>.swift
Also check the reverse: handlers that reference cases no longer in the enum (dead handlers).
Safe patterns (do NOT flag):
- Cases with a
default: handler that provides meaningful content
- Deprecated cases with explicit comments
Severity: ๐ด CRITICAL (sheet opens blank or crashes)
Automated Check 2: Orphaned Views
What it detects: View structs defined (struct XxxView: View) but never instantiated anywhere outside of #Preview blocks.
How to detect:
grep -rn "struct.*: View" Sources/ --include="*.swift" | grep -v "Preview\|test\|Test"
grep -rn "ViewName(" Sources/ --include="*.swift" | grep -v "#Preview\|Preview {"
Also check for orphaned modifiers: Structs conforming to ViewModifier that are defined but .modifier() is never called with them, and no .xxxModifier() extension applies them.
Safe patterns (do NOT flag):
- Views used only in previews if explicitly marked as preview-only
- Views instantiated dynamically via reflection or factory patterns
- Views referenced in
@ViewBuilder results
Severity: โช LOW (dead code, no user impact โ but may indicate ~hundreds of lines of wasted code)
Automated Check 3: Promise-Scope Mismatch
What it detects: A specific-sounding CTA ("Track AppleCare+", "Set Warranty") that opens a generic/overly-broad destination. The user expects a focused action but gets a container with the action buried among unrelated content.
How to detect:
grep -rn "EditItemSheetWrapper\|FullEditView\|SettingsView" Sources/ --include="*.swift" \
| grep -i "sheet\|present"
grep -A5 "onItemSelected" Sources/ --include="*.swift" \
| grep "showing\|activeSheet\|EditItem"
grep -B5 "EditItemSheetWrapper" Sources/ --include="*.swift" \
| grep "Track\|Set\|Add\|Manage\|Configure"
Programmatic detection logic:
- Find CTA labels with specific verbs ("Track X", "Set Y", "Add Z", "Manage W")
- Trace to the destination view
- Count distinct sections/concerns in that destination
- If CTA specificity = 1 concern but destination has 3+ unrelated sections โ flag
Safe patterns (do NOT flag):
- General CTAs ("Edit Product", "Settings") opening broad views โ this is expected
- CTAs that open a pre-scrolled or pre-filtered version of a broad view
Severity: ๐ก HIGH (user confusion, broken trust in CTAs)
Automated Check 4: Entry Point Coverage (Orphan Feature Detection)
What it detects: Features defined in routing enums (sheet types, navigation sections, command palette items) that lack a visible UI entry point outside the command palette. Users who don't use the command palette will never discover these features.
Three-tier detection:
Tier 1: Enumerate all feature destinations
grep -n "case " Sources/ --include="*.swift" -r | grep -i "Sheet\|SheetType\|Section\|Destination"
grep -rn "QuickFindItem\|QuickFindSheet" Sources/ --include="*.swift" | grep "case "
For each case found, record it in a feature inventory table:
| Feature | Enum | Has Primary UI Entry? | Has Command Palette Entry? | Tap Depth |
Tier 2: Cross-reference against visible UI triggers
grep -rn "activeSheet = \.featureName" Sources/ --include="*.swift" | grep -v "QuickFind\|CommandPalette"
grep -rn "selectedSection = \.featureName" Sources/ --include="*.swift"
Tier 3: Classify each feature
| Classification | Criteria | Severity |
|---|
| Orphan | No UI trigger at all (only in code, never presented) | ๐ด CRITICAL |
| Command-palette-only | Trigger exists only in QuickFind/Go To/Spotlight | ๐ก HIGH |
| Deeply buried | Primary UI trigger exists but requires 4+ taps | ๐ก HIGH |
| Adequately surfaced | Reachable in 1-3 taps from a tab | No finding |
What to flag:
- Features with zero visible UI entry points (command-palette-only or orphan)
- Features requiring 4+ taps when they serve a primary user need (not a power-user utility)
- Features whose only entry point is a gesture (swipe, long-press) with no visual hint
Safe patterns (do NOT flag):
- Power-user/maintenance utilities (photo optimizer, data cleanup) at 3+ taps โ these are correctly placed as secondary tools
- Features intentionally gated behind settings (e.g., Legacy Wishes behind
legacyWishesEnabled)
- Debug/developer-only features
- Command palette entries that mirror an existing primary UI entry point (aliases)
Severity: ๐ด CRITICAL if feature is user-facing with no entry point, ๐ก HIGH if command-palette-only or 4+ taps for a primary feature, โช LOW if internal/utility
Automated Check 5: Buried Primary Action
What it detects: Primary action button (Save, Continue, Done, Submit) placed inside a ScrollView after tall content, making it invisible without scrolling. Users see only "Cancel" and feel trapped.
How to detect:
grep -rn "\.borderedProminent\|\.controlSize(.large)" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u > /tmp/primary_buttons.txt
grep -rn "ScrollView" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u > /tmp/scrollviews.txt
comm -12 /tmp/primary_buttons.txt /tmp/scrollviews.txt
Safe patterns (do NOT flag):
VStack(spacing: 0) {
ScrollView { content }
Divider()
actionButtons.padding()
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") { ... }
}
}
Form {
Section { ... }
Section { Button("Save") { ... } }
}
VStack(spacing: 0) {
ScrollView { photoGrid }
bottomActionBar
}
Severity: ๐ก HIGH (user feels trapped, only sees Cancel)
Automated Check 6: Dismiss Traps
What it detects: A view where the only visible action is Cancel/Dismiss/back with no forward path shown.
How to detect:
grep -rn "cancellationAction" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u > /tmp/cancel_views.txt
grep -rn "confirmationAction\|primaryAction" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u > /tmp/forward_views.txt
comm -23 /tmp/cancel_views.txt /tmp/forward_views.txt
Safe patterns (do NOT flag):
.toolbar {
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { ... } }
ToolbarItem(placement: .confirmationAction) { Button("Done") { ... } }
}
Severity: ๐ก HIGH (user feels stuck after completing a step)
Automated Check 7: Gesture-Only Actions
What it detects: Feature or action accessible only via gesture (swipe, long-press, context menu) with no visible button or menu alternative.
How to detect:
grep -rn "\.swipeActions\|\.contextMenu" Sources/ --include="*.swift"
grep -A10 "\.swipeActions\|\.contextMenu" Sources/ --include="*.swift" \
| grep 'Button("'
IMPORTANT โ Check ALL list views, not just a sample. A previous audit checked 1 of 7 list views and concluded "all clear," missing 6 findings.
Safe patterns (do NOT flag):
.swipeActions { Button("Delete") { ... } }
.toolbar {
ToolbarItem { Menu { Button("Delete") { ... } } }
}
Severity: ๐ข MEDIUM (feature undiscoverable for some users; ๐ก HIGH on macOS where swipe is rare)
Automated Check 8: Loading State Traps
What it detects: A view that shows a loading indicator with no way for the user to cancel, go back, or timeout.
How to detect:
grep -rn "interactiveDismissDisabled" Sources/ --include="*.swift"
grep -B5 -A5 "ProgressView" Sources/ --include="*.swift" \
| grep -l "ignoresSafeArea\|ZStack"
Safe patterns (do NOT flag):
Severity: ๐ข MEDIUM (user trapped if operation hangs)
Automated Check 9: Context Dropping
What it detects: Navigation path has item/context available at the source but drops it before reaching the destination. The right destination is opened but without the context the user expects.
How to detect:
grep -rn "#if os(iOS)" Sources/ --include="*.swift" \
| xargs -I{} grep -l "\.sheet\|\.fullScreenCover" {} 2>/dev/null
grep -B2 -A15 "NotificationCenter.default.post" Sources/ --include="*.swift" \
| grep -E "userInfo|let context"
grep -rn "NavigationContext\|NavigationInfo" Sources/ --include="*.swift"
grep -B10 "show.*= true" Sources/ --include="*.swift" \
| grep -E "item\.|onItemSelected"
Safe patterns (do NOT flag):
Severity: ๐ก HIGH (user loses context; may cause data loss)
Automated Check 10: Notification Navigation Fragility
What it detects: Navigation between views using NotificationCenter with untyped [String: Any] dictionaries instead of typed function calls or bindings. Key typos, type mismatches, or omitted fields are silent at compile time.
How to detect:
grep -rn "NotificationCenter.default.post" Sources/ --include="*.swift" \
| grep -v "object: nil)" \
| grep -i "userInfo"
grep -rn "\.requestNavigate\|\.navigateTo\|\.showFeature\|\.openSection" Sources/ --include="*.swift"
grep -rn "\.onReceive\|publisher(for:" Sources/ --include="*.swift" \
| grep -i "navigate\|show\|open"
Safe patterns (do NOT flag):
Severity: ๐ก HIGH (silent bugs, no compiler safety)
Automated Check 10b: Notification Type-Safety
What it detects: Same userInfo key sent with one type (e.g., PersistentIdentifier) but cast to a different type on receive (e.g., as? String). Compiles silently, fails at runtime with silent nil.
How to detect:
grep -B2 -A20 "NotificationCenter.default.post" Sources/ --include="*.swift" -rn \
| grep -E '"[a-zA-Z]+":' > /tmp/sender_keys.txt
grep -B2 -A10 "userInfo\?" Sources/ --include="*.swift" -rn \
| grep -E '"[a-zA-Z]+".*as\?' > /tmp/receiver_casts.txt
Severity: ๐ด CRITICAL (type mismatches cause silent navigation failures)
Automated Check 11: Sheet Presentation Asymmetry
What it detects: Same feature uses fundamentally different presentation mechanisms on different platforms โ e.g., iOS uses .sheet with direct view init, macOS uses NotificationCenter โ navigation. Both work, but different mechanisms drift apart.
How to detect:
grep -rn "#if os(iOS)" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u > /tmp/platform_split.txt
for f in $(cat /tmp/platform_split.txt); do
ios_sheet=$(grep -c "\.sheet\|\.fullScreenCover" "$f" 2>/dev/null || echo 0)
notification=$(grep -c "NotificationCenter.default.post" "$f" 2>/dev/null || echo 0)
if [ "$ios_sheet" -gt 0 ] && [ "$notification" -gt 0 ]; then
echo "ASYMMETRY: $f"
fi
done
Safe patterns (do NOT flag):
Severity: ๐ก HIGH (maintenance burden, drift risk, enables context dropping)
Automated Check 12: Stale Navigation Context
What it detects: A view stores navigation context in @State for later use, but the context is never cleared โ can become stale if source item is deleted or user navigates away.
How to detect:
grep -rn "@State.*private.*var.*[Cc]ontext\|@State.*private.*var.*[Ii]nfo\|@State.*private.*var.*[Nn]avigation" \
Sources/ --include="*.swift"
Safe patterns (do NOT flag):
Severity: ๐ข MEDIUM (edge case but can cause crashes or stale data display)
Automated Check 13: Simulated Delay
What it detects: Task.sleep or asyncAfter followed by hardcoded data assignment โ simulating a network fetch or AI computation with fake data.
How to detect:
grep -rn "Task\.sleep\|asyncAfter" Sources/ --include="*.swift" \
| grep -v "Test\|Preview\|#Preview" \
| grep -v "// animation\|// dismiss\|// sheet" > /tmp/delay_sites.txt
while IFS=: read -r file line _; do
sed -n "$((line+1)),$((line+10))p" "$file" \
| grep -q "= \[.*\]\|= .*(\|= \".*\"\|= true\|= false\|\.result =\|\.suggestion" \
&& echo "SIMULATED: $file:$line"
done < /tmp/delay_sites.txt
Severity: ๐ข MEDIUM to ๐ด CRITICAL (depends on whether users see fake data as real)
Automated Check 14: Navigation Container Constraints
What it detects: selectedSection = .X assignments where .X is not a valid tag in the current navigation container. On iPhone (TabView), only tab tags are valid. On iPad/macOS (sidebar), sidebar items are valid. Setting a non-existent tag is a silent no-op โ the user taps and nothing happens.
How to detect:
grep -rn "selectedSection = \." Sources/ --include="*.swift"
grep -rn "\.tag(\." Sources/ --include="*.swift" | grep -i "tab"
grep -rn "\.tag(\." Sources/ --include="*.swift" | grep -i "sidebar\|NavigationSplitView"
Safe patterns (do NOT flag):
Severity: ๐ก HIGH (user taps, nothing happens โ silent dead end)
Automated Check 15: Notification Lifecycle
What it detects: Notification names that are posted but never received (dead posts), or declared but never posted or received (dead declarations). Also catches receivers without corresponding posters.
How to detect:
grep -rn "static let\|static var" Sources/ --include="*.swift" \
| grep "Notification.Name\|NSNotification.Name"
Safe patterns (do NOT flag):
Severity: โช LOW for dead declarations, ๐ข MEDIUM for dead posts (code that runs but has no effect), ๐ก HIGH for receivers without posters (code waiting for something that never happens)
Automated Check 16: Success Feedback
What it detects: Non-destructive actions (save, duplicate, archive, bookmark, export) that complete without any user-visible confirmation โ no toast, haptic, banner, or navigation change.
How to detect:
grep -rn "modelContext\.save\|dismiss()\|\.delete\|duplicate\|archive\|export\|\.insert" \
Sources/ --include="*.swift"
Safe patterns (do NOT flag):
Severity: ๐ข MEDIUM (user unsure if action succeeded)
Automated Check 17: Empty State Coverage
What it detects: List or collection views that show nothing when empty โ no ContentUnavailableView, no placeholder text, no onboarding prompt. Users see a blank screen and think the app is broken.
How to detect:
grep -rn "List {\|ForEach\|LazyVGrid\|LazyHGrid\|LazyVStack" Sources/ --include="*.swift" \
| cut -d: -f1 | sort -u
Severity: ๐ก HIGH (users think app is broken when they first open it)
Automated Check 18: Error Recovery
What it detects: Error states displayed to the user with no retry button, no "Open Settings" link, or no other recovery path. The user sees an error and has no way forward.
How to detect:
grep -rn "\.alert\|errorMessage\|showError\|isError" Sources/ --include="*.swift"
Severity: ๐ก HIGH (user stuck at error with no path forward)
Verification Template (MANDATORY for Layer 3)
Before grading issues, produce this table for each flagged entry point from Layer 1:
| # | Entry Point | Flag | Verified? | Receipt | Status |
|---|-------------|------|-----------|---------|--------|
| 1 | [label] | [flag] | ? | (file:line checked) | confirmed / retracted / needs-runtime |
Rules:
- Every flagged entry point from Layer 1 must appear in this table
? in the Verified column means the finding hasn't been checked in Layer 3 yet
- Layer 3 cannot produce a grade while any flagged entry has
? in Verified
- Retracted findings stay in the table with strikethrough โ they prove you checked, not just confirmed
If "layer4" or "evaluate": enumerate-required
Before starting, print:
Layer 4: Semantic Evaluation โ [N] issues to evaluate
Estimated time: ~[1-2 min for <10 issues / 3-5 min for 20+]
- For each issue, assess user impact using these 4 criteria (score each 1-5):
| Criterion | 1 (no impact) | 3 (moderate) | 5 (severe) |
|---|
| Discoverability | Feature is obvious, multiple entry points | Requires learning but findable | Hidden, gesture-only, or buried 4+ taps deep |
| Efficiency | One tap from context | 2-3 taps, minor detour | 4+ taps, navigation required, context lost |
| Feedback | Clear confirmation + undo | Confirmation but no undo | No feedback, user unsure if action completed |
| Recovery | Easy undo, no data loss | Partial undo, minor data loss possible | No undo, data loss, or must recreate from scratch |
-
For each finding, assign a confidence level:
verified โ confirmed by grep, file read, or code trace. The issue definitely exists.
probable โ code suggests the issue but not fully traced through all code paths.
needs-runtime โ static analysis can't determine; requires running the app to confirm.
-
Map violations to design principles (#1-#8)
-
After each evaluation, print: Layer 4: โ Issue [N]/[total] โ [confidence] โ [D/E/F/R scores]
-
Output to layer4-semantic-evaluation.md
If "layer5" or "data-wiring" or "wiring": enumerate-required
Before starting, print:
Layer 5: Data Wiring โ inventorying models and cross-referencing features
Estimated time: ~[2-4 min for <20 features / 5-10 min for 40+]
- Model inventory. Catalog what data the app tracks:
grep -rn "@Model" Sources/Models/ --include="*.swift" -l
grep -rn "var.*:.*\[.*\]?" Sources/Models/ --include="*.swift" | grep -v "//"
grep -rn "var.*:.*{" Sources/Models/ --include="*.swift" | grep -i "total\|average\|count\|cost\|price"
Print: Layer 5: โ Model inventory (1/4) โ [N] models, [N] properties
-
Select features to cross-reference. Don't check every feature โ select the top 5-8 using these criteria:
- Features that make decisions based on model data (advisors, calculators, suggestion engines)
- Features with the most model properties available but unclear how many they use
- Features flagged in earlier layers
- The primary detail view (reads the most model data)
For each, check what model data it reads vs what's available. Print: Layer 5: โ Feature scan (2/4) โ [N] features checked
-
Mock data detection:
grep -rn "asyncAfter" Sources/Features/ --include="*.swift" -A 10 | grep -B 5 "=.*[0-9]\|\".*\$"
grep -rn "let.*=.*\[" Sources/Features/ --include="*.swift" | grep -i "alternative\|suggestion\|recommendation"
grep -rn "Score.*=.*[0-9]\|rating.*=.*[0-9]" Sources/Features/ --include="*.swift" | grep -v "test\|Test\|preview\|Preview"
grep -rn "func fetch\|func load\|func compute" Sources/Features/ --include="*.swift" -A 15 | grep "asyncAfter\|sleep\|\.random"
Print: Layer 5: โ Mock data scan (3/4)
- Cross-reference matrix:
| Feature | Data Available | Data Used | Data Ignored |
|---|
| [name] | [model properties] | [what it reads] | [gap] |
-
Form-to-detail parity check (MANDATORY):
For every model that has BOTH a form/edit view AND a detail/read-only view, verify that every user-editable field in the form has a corresponding display in the detail view.
Method:
a. Identify form/detail view pairs (e.g., ExtendedWarrantyFormView + EnhancedItemDetailView+Sections extended warranty section; RMAFormView + RMAListView).
b. In each form, enumerate fields bound to TextFields, Pickers, Toggles, DatePickers, Steppers.
c. For each editable field, grep the detail view for a display consumer. Classify:
displayed: Field appears in a DetailKeyValueRow, Text(), or equivalent read-only rendering.
form-only: Field is only read by the form to populate edit state. User enters data that vanishes after save.
computed: Field feeds a computed property that IS displayed (indirect display -- acceptable).
serialization-only: Field is backed up/exported but never shown to the user in any view.
d. Report form-only fields as findings with pattern_fingerprint: form_editable_but_no_detail_display.
Why this matters: Users who enter data and can't see it after saving perceive it as data loss, even when the data is correctly persisted. This is the most common "vanishing field" UX bug pattern and is invisible to data-integrity audits (the data round-trips perfectly).
Handoff input: If data-model-radar ran first, check .agents/ui-audit/data-model-radar-handoff.yaml for form_only_fields[]. These are pre-identified suspects -- verify each against the actual detail view rather than re-scanning from scratch.
Print: Layer 5: โ Form-to-detail parity (5/[total]) โ [N] form-only fields found
-
Integration gap detection:
grep -rn "class.*Manager\|class.*Service" Sources/ --include="*.swift" | grep -v "test\|Test"
- Platform parity check:
grep -rn "#if os(iOS)" Sources/ --include="*.swift" -A 3 | grep -i "dismiss\|toolbar\|done"
grep -rl "extension.*View" Sources/ --include="*.swift" | grep "+"
Print: Layer 5: โ Platform parity (7/8)
- Output to
layer5-data-wiring.yaml
If "diff":
Compare current codebase against the previous audit to show what changed:
- Read existing
.ui-path-radar/layer3-results.yaml and .ui-path-radar/handoff.yaml
- For each previously-reported issue, check if the referenced file + line still has the problem
- Run a quick scan for NEW issues not in the previous report
- Output a diff summary:
Audit Diff: <previous date> โ <current date>
โ
Resolved: <count> issues fixed since last audit
๐ด Still Open: <count> issues remain
๐ New: <count> new issues detected
๐ Changed: <count> files modified since audit (may need re-verification)
- Show the full Issue Rating Table with a Status column prepended (โ
/๐ด/๐)
If "fix" or "fixes":
- Read
layer3-results.yaml and layer5-data-wiring.yaml for unfixed issues
- Generate specific code fixes
- Prioritize by severity (critical first)
- Group into:
- Safe fixes โ isolated, low blast radius
- Cross-cutting fixes โ touch shared code
- Requires design decision โ multiple valid approaches
- Deferred โ no action needed now
- Out of scope โ belongs to a different audit type
If "status":
- Read existing audit files
- Report: issues found, fixed, remaining
- Show priority queue for unfixed issues
Output Format
CRITICAL FORMATTING RULE: The Issue Rating Table below IS the output. Do NOT create separate sections for "Critical Issues", "Data Wiring Issues", "Recommendations", or any other vertical breakdown of findings. Every finding โ navigation issues, data wiring issues, orphaned code, missing feedback, design violations โ goes into ONE table as ONE row. Context goes in the Finding column. No exceptions.
Layer Transition Summary (between each layer)
When completing a layer and moving to the next, print:
โ Step [N] of 5 complete: [plain description] โ [M] findings ([X] verified, [Y] probable, [Z] needs-runtime)
Retracted from prior steps: [count or "none"]
Cumulative: [total] findings ([C] critical, [H] high, [M] medium, [L] low)
Next: Step [N+1] โ [plain description of what it does and why it matters given current findings].
โ "proceed" | "explain #[N]" | "stop here"
Final Output (after all layers or after a single layer run)
After completing the audit, provide these 6 items in order:
- One-line summary โ entry point count, issue count by severity (one sentence, not a section)
- Issue Rating Table โ every finding in a single table. Each finding MUST include a confidence tag (
verified / probable / needs-runtime) in the Finding column.
- Proactive risk callout โ Auto-identify the top 3 riskiest findings:
Before proceeding, these findings have elevated risk profiles:
#[N] โ [short description] (Risk:Fix [indicator], Blast [count] files)
#[N] โ [short description] (Risk:No Fix [indicator], [consequence])
โ Say "explain #[N]" for a detailed risk breakdown before deciding.
- Cross-skill handoff notes (if applicable):
Related areas to check next:
#[N] โ Check whether saved data survives editing round-trips (data safety audit)
#[N] โ Check visual layout and spacing for this screen (visual quality audit)
- Limitations disclaimer:
Limitations: Static analysis only. Not checked: animation smoothness,
real-device timing, race conditions under memory pressure, subjective
UX feel, accessibility with VoiceOver. Consider runtime testing for
findings marked "needs-runtime."
- One-line next step โ suggest next action
Items 3-5 may be omitted if not applicable.
Issue Rating Table
Hard formatting rule โ Table, not list: ALL findings MUST be in a single markdown table. Each finding is ONE ROW. Never expand into individual sections or bullet-pointed ratings. ALL categories go in the same table.
Full table:
| # | Finding | Conf | Urgency | Risk:Fix | Risk:NoFix | ROI | Blast | Effort | Status |
|-----|---------------------------|----------|--------------|----------|------------|----------|----------|---------|--------|
| 1 | Dead end: ".warranty" | verified | ๐ด Critical | โช Low | ๐ด Crit | ๐ Exc | ๐ข 2f | Trivial | Open |
| | unhandled | | | | | | | |
Compact table (narrow terminal):
| # | Finding | Conf | Urgency | Effort |
|-----|---------------------------|----------|--------------|---------|
| 1 | Dead end: ".warranty" | verified | ๐ด Critical | Trivial |
Indicator Scale
| Indicator | General meaning | ROI meaning |
|---|
| ๐ด | Critical / high concern | Poor return โ reconsider |
| ๐ก | High / notable | Marginal return |
| ๐ข | Medium / moderate | Good return |
| โช | Low / negligible | โ |
| ๐ | Pass / positive | Excellent return |
- Urgency: ๐ด CRITICAL (dead end, wrong destination, mock data) ยท ๐ก HIGH (broken promise, missing activation, unwired data) ยท ๐ข MEDIUM (two-step flow, missing feedback) ยท โช LOW (inconsistency, orphaned code)
- Risk: Fix: Risk of the fix introducing regressions
- Risk: No Fix: User-facing consequence of leaving the issue
- ROI: ๐ Excellent ยท ๐ข Good ยท ๐ก Marginal ยท ๐ด Poor
- Blast Radius: Number of files the fix touches. Do not use
<br> tags. Count by grepping for callers/references before rating.
- Fix Effort: Trivial / Small / Medium / Large
Finding Dependencies and Fingerprints
When creating findings, populate these optional fields where relationships are obvious:
depends_on/enables: UI path findings often chain -- a dead-end fix enables a flow that was previously untestable. If one fix must come before another, populate with finding IDs.
pattern_fingerprint/grep_pattern/exclusion_pattern: Assign fingerprints for generalizable UI anti-patterns (e.g., dead_end_sheet, unhandled_navigation_case, mock_data_in_production, missing_platform_parity).
Fix Application Workflow
After presenting findings, apply fixes in waves. After each wave (including commits), always print the progress banner and auto-prompt for the next wave. Never leave the user with a blank prompt.
Waves
| Wave | Section | Est. Time | Description |
|---|
| 1 | Safe fixes + tests | ~10-15 min | Isolated, low blast radius. Auto-apply. Write tests. |
| 2 | Cross-cutting fixes + tests | ~15-25 min | Touch shared code. Present for review. Write tests. |
| 3 | Design decisions | ~5-15 min | Multiple options. Requires user input per item. |
| 4 | Build + Test + Commit | ~5 min | Build both platforms, run tests, stage, commit. |
Every fix must have a test. Do not move to the next wave until tests for the current wave's fixes are written and compiling.
Skip empty waves.
Progress Banner (MANDATORY after every wave)
CRITICAL โ BLOCKING requirement. After EVERY wave and EVERY commit, your NEXT output MUST be the progress banner followed by the next-wave AskUserQuestion.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Wave [N] of [total] complete: [wave name]
[X] findings fixed, [Y] remaining, [Z] deferred
โฑ Next: Wave [N+1] โ [wave name] (~[time estimate])
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Then immediately ask: "Ready for Wave [N+1]?" with options:
- Proceed (Recommended) โ Start the next wave
- Commit first โ Commit current changes before continuing
- Stop here โ End for now, resume later
Pipeline Mode Behavior (Tier 2/3)
When running inside a Tier 2 or Tier 3 pipeline (detected via tier field in .radar-suite/session-prefs.yaml):
- On skill start: Emit the pipeline-level progress banner (see
radar-suite-core.md Pipeline UX Enhancements #1). If this is the first skill in the pipeline OR experience_level is Beginner/Intermediate, also emit the audit-only statement.
- On skill completion: Emit a per-skill mini rating table marked "PRELIMINARY" (see Pipeline UX Enhancements #2). Then emit the pipeline-level progress banner showing this skill as complete.
- Within-skill wave banners (above) are still emitted normally in addition to the pipeline-level banners.
short_title Requirement (v2.1)
Every finding MUST include a short_title field (max 8 words). This is the human-scannable label used in pipeline banners, pre-capstone summaries, and ledger output.
Example: short_title: "Delete button missing on edit sheet"
All finding ID references in output (tables, banners, summaries) use the format: RS-NNN (short_title).
Regression Canaries
After fixing a workflow issue, generate a "canary" โ a specific check that detects if the issue recurs. Canaries are stored in .ui-path-radar/canaries.yaml and can be run as a quick regression check before release.
Canary Format
canaries:
- id: "canary-001"
issue_ref: "issue-017"
description: "NavigationContext must include existingItemID"
check_type: "grep_match"
file: "Sources/Views/Navigation/AppNavigationView.swift"
pattern: "existingItemID.*PersistentIdentifier"
expect: "match"
added: "2026-03-08"
- id: "canary-002"
issue_ref: "issue-013"
description: "Continue button must appear before tall content in flow"
check_type: "line_order"
file: "Sources/Features/ItemManagement/Views/UnifiedPhotoFlow.swift"
first_pattern: "Continue with AI Analysis"
second_pattern: "sourcePickerContent"
expect: "first_before_second"
added: "2026-03-08"
Canary Check Types
| Type | Description | Pass condition |
|---|
grep_match | Pattern must exist in file | Pattern found |
grep_absent | Pattern must NOT exist in file | Pattern not found |
line_order | Two patterns must appear in specific order | First before second |
param_count | Count params in a function/dict | Count matches expected |
platform_parity | Same pattern must exist in both iOS and macOS blocks | Found in both |
Running Canaries
for canary in $(yq '.canaries[].id' .ui-path-radar/canaries.yaml); do
file=$(yq ".canaries[] | select(.id == \"$canary\") | .file" .ui-path-radar/canaries.yaml)
pattern=$(yq ".canaries[] | select(.id == \"$canary\") | .pattern" .ui-path-radar/canaries.yaml)
expect=$(yq ".canaries[] | select(.id == \"$canary\") | .expect" .ui-path-radar/canaries.yaml)
if [ "$expect" = "match" ]; then
grep -q "$pattern" "$file" && echo "โ
$canary" || echo "โ $canary REGRESSION"
elif [ "$expect" = "absent" ]; then
grep -q "$pattern" "$file" && echo "โ $canary REGRESSION" || echo "โ
$canary"
fi
done
When to Generate Canaries
After each fix in fix mode, generate a canary:
- After fixing Context Dropping โ canary verifying the field exists in both paths
- After fixing Buried Primary Action โ canary verifying button order
- After fixing Platform Parity Gap โ canary verifying pattern on both platforms
- After fixing Dismiss Trap โ canary verifying forward action exists
Canaries are additive โ they accumulate over time as a regression safety net.
Handoff Brief Generation
After completing all layers (full audit) or fix mode, generate .ui-path-radar/handoff.yaml.
When to Generate
- After a full 5-layer audit completes
- After
fix mode completes (refreshes with current state)
- NOT after individual layer runs
Format
project: <project name from directory>
audit_date: <ISO 8601 date>
source_files_scanned: <count>
summary:
total_issues: <count>
critical: <count>
high: <count>
medium: <count>
low: <count>
file_timestamps:
<file path>: "<ISO 8601 mod date>"
issues:
- id: <sequential number>
finding: "<description>"
category: <dead_end|wrong_destination|mock_data|incomplete_navigation|missing_activation|unwired_data|platform_gap|promise_scope_mismatch|buried_primary_action|dismiss_trap|two_step_flow|missing_feedback|gesture_only_action|loading_state_trap|context_dropping|notification_nav_fragility|sheet_presentation_asymmetry|stale_navigation_context|navigation_container_mismatch|empty_state_missing|error_recovery_missing|inconsistent_pattern|orphaned_code|command_palette_only|deeply_buried_feature|double_nested_navigation>
urgency: <critical|high|medium|low>
risk_fix: <critical|high|medium|low>
risk_no_fix: <critical|high|medium|low>
roi: <excellent|good|marginal|poor>
blast_radius: "<description, e.g. '1 file' or '4 files'>"
fix_effort: <trivial|small|medium|large>
files:
- <file path>
suggested_fix: "<what to do, not how>"
group_hint: "<optional grouping suggestion>"
File Timestamps
stat -f "%Sm" -t "%Y-%m-%dT%H:%M:%SZ" "<file path>"
Group Hints
Common hints: missing_confirmations, missing_feedback, orphaned_features, dead_code, platform_parity, dead_notifications, navigation_container
Cross-Skill Handoff
UI Path Radar complements data-model-radar (model layer), roundtrip-radar (data safety), ui-enhancer-radar (visual quality), and capstone-radar (ship readiness).
On Completion โ Write Handoff
After completing an audit, write .agents/ui-audit/ui-path-radar-handoff.yaml:
source: ui-path-radar
date: <ISO 8601>
project: <project name>
file_timestamps:
<file path>: "<ISO 8601 mod date>"
for_roundtrip_radar:
suspects:
- workflow: "<affected workflow>"
finding: "<what was found>"
file: "<file:line>"
question: "<specific question for roundtrip-radar to verify>"
group_hint: "<optional>"
for_ui_enhancer_radar:
suspects:
- view: "<view file>"
finding: "<what was found>"
action: "remove or wire up before visual audit"
group_hint: "<optional>"
for_capstone_radar:
blockers:
- finding: "<description>"
urgency: "<CRITICAL|HIGH>"
group_hint: "<optional>"
checks_performed:
automated_checks: 18
categories_scanned:
- dead_end
- wrong_destination
- mock_data
- destructive_no_confirm
- silent_state_reset
- incomplete_navigation
- missing_activation
- unwired_data
- platform_gap
- promise_scope_mismatch
- buried_primary_action
- dismiss_trap
- context_dropping
- notif_nav_fragility
- sheet_asymmetry
- empty_state_missing
- error_recovery_missing
- nav_container_mismatch
- two_step_flow
- missing_feedback
- gesture_only_action
- loading_state_trap
- stale_nav_context
- phantom_touch_target
- race_condition_ux
- invisible_selection
- inconsistent_pattern
- orphaned_code
- command_palette_only
- deeply_buried_feature
- double_nested_nav
persona_evaluation: false
confidence_scoring: true
End-of-Run Directory Cleanup (MANDATORY)
Per the Artifact Lifecycle rules in radar-suite-core.md, before returning from this skill:
- List files in
.radar-suite/ (and .ui-path-radar/ if used).
- Move any stale single-use handoffs (
RESUME_PHASE_*.md, RESUME_*.md except NEXT_STEPS.md, *-v[0-9]*.md) to .radar-suite/archive/superseded/.
- Confirm Class 1 persistent-state files (
ledger.yaml, session-prefs.yaml) are in-place rewrites โ not dated or versioned.
- Confirm Class 2 handoff files are overwrites, not appends.
This prevents .radar-suite/ from accumulating stale prose artifacts across runs.
Write to Unified Ledger (MANDATORY)
After writing the handoff YAML, also write findings to .radar-suite/ledger.yaml following the Ledger Write Rules in radar-suite-core.md:
- Read existing ledger (or initialize if missing)
- Record this session (timestamp, skill name, build)
- For each finding: check for duplicates, assign RS-NNN ID if new, set
impact_category, compute file_hash
- Write updated ledger
Impact category mapping for ui-path-radar findings:
- Dead-end screen (no way forward or back) โ
ux-broken
- Broken navigation (button does nothing, link goes nowhere) โ
ux-broken
- Missing empty state or loading state โ
ux-degraded
- Buried CTA or hard-to-find feature โ
ux-degraded
- Accessibility dead end โ
ux-broken
- Visual-only issues โ
polish
On Startup โ Read Ledger & Handoffs (MANDATORY)
Before starting the audit, read the unified ledger and ALL companion handoff YAMLs:
Read .radar-suite/ledger.yaml (if exists) โ check for existing findings to avoid duplicates
Read .agents/ui-audit/data-model-radar-handoff.yaml (if exists)
Read .agents/ui-audit/roundtrip-radar-handoff.yaml (if exists)
Read .agents/ui-audit/ui-enhancer-radar-handoff.yaml (if exists)
Read .agents/ui-audit/capstone-radar-handoff.yaml (if exists)
Read .workflow-audit/persona-handoff.yaml (if exists)
Read .workflow-audit/handoff.yaml (if exists)
Workflow-Audit Persona Integration
When .workflow-audit/persona-handoff.yaml exists:
- Display: "Persona evaluation from workflow-audit available -- incorporating."
- Layer 4 enrichment: Use persona D/E/F/R ratings to weight your own Layer 4 scoring. If workflow-audit rated a workflow's Feedback as 2/5, weight Feedback-related issues higher for that workflow.
- Skip persona derivation: Use workflow-audit's personas instead of re-deriving them. They were built from semantic analysis you don't replicate.
- Category overlap: Read
checks_performed.categories_scanned. For categories both skills check, still run your own automated checks but note "Also flagged by workflow-audit" for duplicate findings in the same file.
When .workflow-audit/handoff.yaml exists (without persona handoff):
- Import CRITICAL/HIGH findings as companion findings tagged
[via workflow-audit]
- Note which categories workflow-audit checked
When neither exists: proceed normally. No change to audit behavior.
Ledger check: If the ledger contains findings for views you're about to audit, note their RS-NNN IDs. When you find the same issue, update the existing finding instead of creating a new one.
Regression check: For any fixed findings in the ledger whose file_hash no longer matches the current file, flag for re-verification per the Regression Detection protocol in radar-suite-core.md.
Parse for_ui_path_radar sections. Incorporate as priority targets โ verify each independently.
If not found, proceed normally.
Cautionary Note
This skill is a tool, not an oracle.
It systematically scans using pattern matching and heuristics. It surfaces real issues you'd miss manually โ but it has inherent limitations:
Good at: Structural inconsistencies, patterns that compile but fail silently, cross-platform parity, repeatable checklists.
Can miss: Business logic correctness, UX nuance, false positives (intentionally retained code), novel bug patterns.
Use responsibly: Treat findings as leads to investigate, not verdicts. Verify critical findings manually. Don't assume clean = zero issues โ it means zero known-pattern issues.
End Reminder
After every layer/wave/commit: print progress banner โ AskUserQuestion โ never blank prompt.