| name | ui-enhancer-radar |
| description | Systematic iOS/SwiftUI UI audit with design intent interview, 14-domain analysis (including Color Audit with adaptive Color Profile, iPad Sheet Sizing caller-side audit, Button Hit Region three-factor interaction audit, and Silent Picker menu-presentation audit), element compaction, cross-view consistency checks, layout reorganization, design-aware push-back, App Store guardrails, and incremental apply with revert safety. Run /ui-enhancer-radar help for all commands. Triggers: "enhance this UI", "ui enhancer radar", "improve this view", "screen review", "ux audit", "ipad sheet truncation", "button not tappable on iPad", "picker does nothing". |
| version | 3.6.0 |
| author | Terry Nyberg |
| license | MIT |
| allowed-tools | ["Read","Grep","Glob","Bash","Write","Edit","AskUserQuestion"] |
| inherits | radar-suite-core.md |
| metadata | {"tier":"execution","category":"ux"} |
UI Enhancer Radar
Quick Ref: Screenshot + code analysis of any iOS/SwiftUI view. Design intent interview (sacred elements, aggressiveness), 14-domain analysis with layout reorganization, Color Audit (adaptive Color Profile), iPad Sheet Sizing caller-side audit, Button Hit Region three-factor audit, and Silent Picker menu-presentation audit, element compaction (compact vs remove vs keep), cross-view consistency checks, design-aware refinement with push-back and App Store guardrails, incremental apply with revert safety (git or file backup), visual verification guidance, and files-changed summary.
You are performing a systematic UI enhancement on a specific iOS/SwiftUI view, analyzing both the visual screenshot and the underlying code, then implementing improvements with tests.
Required output: Every finding MUST include a severity rating (Critical / High / Medium / Low) and estimated implementation effort (Trivial / Small / Medium / Large).
Quick Commands
| Command | Description |
|---|
/ui-enhancer-radar | Full audit with interview |
/ui-enhancer-radar space | Space efficiency analysis only |
/ui-enhancer-radar hierarchy | Visual hierarchy analysis only |
/ui-enhancer-radar density | Information density analysis only |
/ui-enhancer-radar interaction | Interaction patterns analysis only |
/ui-enhancer-radar accessibility | Accessibility audit only |
/ui-enhancer-radar hig | HIG compliance check only |
/ui-enhancer-radar dark-mode | Dark mode audit only |
/ui-enhancer-radar performance | Performance impact analysis only |
/ui-enhancer-radar design-system | Design system compliance only |
/ui-enhancer-radar color | Color audit only (inventory, flatness, consistency) |
/ui-enhancer-radar ipad-sheets | iPad sheet sizing audit only (caller-side .sheet() check) |
/ui-enhancer-radar hit-region | Button hit region audit only (three-factor: .plain + Form/List + trailing chevron) |
/ui-enhancer-radar silent-picker | Silent Picker audit only (menu Picker, no .pickerStyle, custom non-Form container) |
/ui-enhancer-radar compare | Compare before/after screenshots for progress |
/ui-enhancer-radar revert | Undo all changes back to last checkpoint |
/ui-enhancer-radar batch [path] | Audit all views in a directory, rank by severity |
--show-suppressed | Show findings suppressed by known-intentional entries |
--accept-intentional | Mark current finding as known-intentional (not a bug) |
/ui-enhancer-radar --capture | Capture screenshot from running simulator (optional) |
/ui-enhancer-radar --devices | Analyze layout across device sizes (optional) |
/ui-enhancer-radar fix-deferred | Resolve items deferred from a previous run |
/ui-enhancer-radar verify | Re-check previous findings without full re-audit (~5 min) |
Help Command
If the user runs /ui-enhancer-radar help, display this command reference and stop (do not start an audit):
UI Enhancer — Available Commands
FULL AUDIT
/ui-enhancer-radar Full 14-domain audit with interview
SINGLE DOMAIN (skip interview, run one domain)
/ui-enhancer-radar space Space efficiency analysis
/ui-enhancer-radar hierarchy Visual hierarchy analysis
/ui-enhancer-radar density Information density analysis
/ui-enhancer-radar interaction Interaction patterns analysis
/ui-enhancer-radar accessibility Accessibility audit
/ui-enhancer-radar hig HIG compliance check
/ui-enhancer-radar dark-mode Dark mode audit
/ui-enhancer-radar performance Performance impact analysis
/ui-enhancer-radar design-system Design system compliance
/ui-enhancer-radar color Color audit (inventory, flatness, consistency)
/ui-enhancer-radar ipad-sheets iPad sheet sizing audit (caller-side `.sheet()` check)
/ui-enhancer-radar hit-region Button hit region audit (.plain + chevron + Form/List)
UTILITIES
/ui-enhancer-radar compare Compare before/after screenshots
/ui-enhancer-radar revert Undo all changes back to checkpoint
/ui-enhancer-radar batch [path] Audit all views in a directory
/ui-enhancer-radar help Show this command list
OPTIONS (combine with any command)
--capture Capture screenshot from running simulator
--devices Analyze layout across device sizes
First-Run Hint
When /ui-enhancer-radar is invoked with no subcommand, display this hint once before the Phase 1 interview (not on subsequent runs in the same session):
Tip: Run /ui-enhancer-radar help to see all available commands, or continue for a full audit.
Then proceed directly to Phase 1.
Skill Introduction (MANDATORY — run before anything else)
This section replaces radar-suite-core.md § Session Setup for the ui-enhancer-radar entry point. Do NOT also run core's 4-question Session Setup — its questions are consolidated below. Phase 1 ("Interview") below reuses the answers captured here and does NOT re-ask experience level, fix mode, or attendance.
On first invocation, ask all setup 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: "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.
- Batch mode — Approve all fixes in each wave at once.
IMPORTANT: All three 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. (Exception: Hands-Free mode overrides this — see Question 3.)
Question 3: "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 analysis only (Read, Grep, Glob). No edits, no Bash, no prompts. Hands-Free overrides Question 2: all fixes are deferred regardless of
FIX_MODE. Phase 7b (Visual Inspection Gate) is skipped — no code changes will be applied. The skill emits the saved playbook and Hands-Free completion message instead of the next-phase AskUserQuestion (see § Hands-Free Mode below for full precedence rules).
- Pre-Approved — You have already configured Claude Code permissions for this session. Run at full speed without restriction.
Store as: USER_EXPERIENCE, FIX_MODE, PRESENCE_MODE. Apply to ALL output for the session, per radar-suite-core.md § Experience-Level Output Rules. Also persist to .radar-suite/session-prefs.yaml per radar-suite-core.md § Session Persistence.
Question 4 (optional follow-up): "Would you like a brief explanation of what this skill does?"
- No, let's go (Recommended) — Skip explanation, proceed to Phase 1 (Interview).
- Yes, explain it — Show one of the explanations below adapted to experience level, then proceed.
Experience-adapted explanations for UI Enhancer:
-
Beginner: "UI Enhancer is like having a professional designer review every screen in your app. It checks 14 different things — spacing, colors, accessibility, layout efficiency, iPad sheet sizing, button hit regions, and more — then suggests specific improvements. It won't just say 'this looks wrong'; it'll show you exactly what to change and why. It works one view at a time, applying changes incrementally so you can undo anything."
-
Intermediate: "UI Enhancer performs a 14-domain analysis of SwiftUI views: layout, spacing, color accessibility, typography, element compaction, cross-view consistency, iPad sheet sizing, button hit regions, and more. It interviews you about design intent first, then audits against Apple HIG and your app's design system. Changes are applied incrementally with revert safety."
-
Experienced: "14-domain SwiftUI UI audit with design intent interview, adaptive color profiles, element compaction, cross-view consistency checks, iPad sheet sizing caller-side audit, button hit region three-factor audit, silent picker menu-presentation audit, layout reorganization, App Store guardrails, and incremental apply with revert safety. Run /ui-enhancer-radar help for the full command list."
-
Senior/Expert: "14-domain view audit: layout, color, typography, spacing, compaction, consistency, accessibility, iPad sheet sizing, button hit region, silent picker. Interview → analyze → apply incrementally."
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 domain scan)
-
Known-intentional suppression: Run the protocol in radar-suite-core.md § Known-Intentional Suppression. Core owns this — do not restate the steps here.
-
Pattern reintroduction detection: Run the protocol in radar-suite-core.md § Pattern Reintroduction Detection. Core owns this.
Phase 1: Interview
Before analyzing, run a brief intake to focus the audit on what matters most.
Experience level was already captured in § Skill Introduction (Question 1). Do NOT re-ask it here. The questions below are content-specific to ui-enhancer-radar's audit shape — focus, priority, design intent, aggressiveness.
Display this instruction before the first set of questions:
To answer, type the option labels (e.g., "General polish, All domains, Moderate") or use numbers (e.g., "1, 1, 1"). You can answer all questions at once or one at a time.
questions:
[
{
"question": "What's the main reason for this review?",
"header": "Focus",
"options": [
{"label": "General polish (Recommended)", "description": "No specific complaint \u2014 just want it to be better"},
{"label": "Specific problem", "description": "Users or I have noticed something wrong"},
{"label": "Pre-release review", "description": "Final check before shipping"},
{"label": "Competitive parity", "description": "Want to match or exceed a competitor's UX"}
],
"multiSelect": false
},
{
"question": "Which aspects matter most right now?",
"header": "Priority",
"options": [
{"label": "All domains (Recommended)", "description": "Full 14-domain analysis"},
{"label": "Visual \u2014 layout and hierarchy", "description": "Space, visual weight, information density"},
{"label": "Interaction \u2014 usability", "description": "Touch targets, discoverability, feedback"},
{"label": "Technical \u2014 performance and compliance", "description": "Dark mode, perf, HIG, design system"}
],
"multiSelect": false
}
]
If "Specific problem", follow up:
questions:
[
{
"question": "What's the specific issue?",
"header": "Issue",
"options": [
{"label": "Too much wasted space (most common)", "description": "Content doesn't start until halfway down the screen"},
{"label": "Hard to find things", "description": "Users can't discover features or actions"},
{"label": "Looks cluttered or confusing", "description": "Too much competing for attention"},
{"label": "I'll describe it", "description": "Let me explain the problem"}
],
"multiSelect": false
}
]
If "Competitive parity", ask for a competitor screenshot to enable Domain 10 (Competitive Comparison).
Design Intent (always ask)
After the focus/priority questions, always ask about design intent and change aggressiveness:
questions:
[
{
"question": "Are there elements on this screen you consider essential to its identity — things that should be preserved even if they use extra space?",
"header": "Identity",
"options": [
{"label": "No sacred elements (Recommended)", "description": "Everything is fair game for optimization"},
{"label": "Yes, I'll point them out", "description": "I'll identify specific elements to preserve"},
{"label": "Keep all branding/headers", "description": "Preserve illustrated headers, icons, and branded sections"},
{"label": "Not sure — show me options", "description": "Offer compact vs remove for each visual element"}
],
"multiSelect": false
},
{
"question": "How aggressive should changes be?",
"header": "Aggressiveness",
"options": [
{"label": "Moderate (Recommended)", "description": "Compact where possible, remove only clear redundancies"},
{"label": "Conservative", "description": "Tighten spacing and compact only — never remove elements"},
{"label": "Aggressive", "description": "Maximize space — remove anything non-essential"}
],
"multiSelect": false
}
]
Attendance
Attendance mode (PRESENCE_MODE) was already captured in § Skill Introduction (Question 3). Do NOT re-ask here. The Hands-Free Mode section below documents precedence rules for write-tool deferral and AskUserQuestion suppression.
Hands-Free Mode (precedence rules — load-bearing)
When PRESENCE_MODE = Hands-Free:
- Hands-Free overrides
FIX_MODE. Even if Question 2 was answered Auto-fix safe items, Review first, or Batch mode, no fixes are applied in Hands-Free mode. All findings are emitted with Status: Deferred (hands-free) in the Issue Rating table. The Fix Plan, Report (Phase 6), and Playbook (Phase 7) are still produced — they're held in the conversation for the user to act on when they return.
- Phase 7b (Visual Inspection Gate) is skipped. Since no code changes will be applied, the gate has nothing to protect. The skill emits the saved playbook to
.agents/ui-enhancer-radar/[date]-[view]-playbook.md and prints the completion message below.
- Phase 7c-7f are deferred. Guided Visual Review, Apply Approved Changes, Pattern Sweep, and Refinement Loop all require interactive user prompts (
AskUserQuestion) and/or write tools. None of these run in Hands-Free.
- BLOCKING progress banner exception. The "CRITICAL — BLOCKING requirement" for the next-phase
AskUserQuestion (§ Phase Progress Banner) applies in Normal and Pre-Approved modes only. In Hands-Free, the progress banner still prints after each completed phase, but the AskUserQuestion is suppressed in favor of the completion message.
- Handoff YAML emitted inline. Writing
.agents/ui-audit/ui-enhancer-radar-handoff.yaml and .radar-suite/ledger.yaml requires Edit/Write — forbidden in Hands-Free. The skill emits these as fenced YAML blocks inline in the conversation so the user can persist them on return.
Hands-Free completion message:
⏱ Hands-free audit complete through Phase 5 (Domain Analysis) + Phase 6 (Report).
Phases skipped (deferred until you return): 7a-7f (apply), 8 (tests), 9 (summary).
Findings deferred: [count] (no fixes applied — Hands-Free mode)
Playbook saved: .agents/ui-enhancer-radar/[date]-[view]-playbook.md
Handoff YAML + ledger entries: emitted inline above; copy to .agents/ui-audit/ and .radar-suite/ when you return
Reply to continue with supervised phases.
When PRESENCE_MODE = Pre-Approved: full speed, no restrictions. Assumes permissions are configured per the Permission Setup guide below. All phases run normally.
Permission Setup (for unattended runs)
To avoid permission prompts during audits, pre-allow these read-only patterns in Claude Code settings. Safe to auto-approve — they cannot modify your codebase:
# 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
After all questions, always offer:
Anything else I should know? (design preferences, constraints, specific elements to preserve — or press Enter to skip)
Record any free-text input as additional constraints that apply throughout the audit. For example: "Preserve saturation and hue of card backgrounds" becomes a constraint checked during every finding.
If "Yes, I'll point them out" — ask the user to describe or screenshot the sacred elements. Tag them as [PRESERVE] in the findings table and default to compaction (Phase 6c) rather than removal.
If "Keep all branding/headers" — mark all illustrated headers, SheetHeaders, ContentIllustratedHeaders, and branded sections as [PRESERVE]. Only recommend compaction for these, never removal.
If "Not sure — show me options" — for every visual element, present the full Phase 6c compaction menu (compact / remove / keep).
Aggressiveness calibration
The aggressiveness setting affects all findings throughout the audit:
| Setting | Spacing | Headers | Decorative elements | Redundant info |
|---|
| Conservative | Tighten 10-20% | Compact only | Keep, reduce size | Merge, don't remove |
| Moderate | Tighten 20-40% | Compact (default), remove if redundant | Compact or remove | Remove if truly duplicate |
| Aggressive | Minimize to HIG minimum | Remove unless [PRESERVE] | Remove | Remove |
The interview determines:
- Which domains to run (all 14, or a focused subset)
- Severity weighting (user-reported problems get Critical minimum)
- Whether to include competitive comparison
- Which elements are sacred (
[PRESERVE] tag)
- How aggressive to be with changes (Conservative / Moderate / Aggressive)
- How to pitch explanations (experience level)
Experience-Level Adaptation
Adjust ALL output (findings, questions, recommendations, compaction options) based on the user's experience level:
- Beginner: Plain language, real-world analogies. "This header takes up 120 points of space — that's about a third of the visible screen on an iPhone. Compacting it would let users see their content sooner." Define SwiftUI terms on first use. When presenting compaction options, explain what each choice means visually.
- Intermediate: Standard SwiftUI terminology, explain non-obvious tradeoffs. "The
SheetHeader uses 120pt — compacting to inline icon+title saves 80pt and keeps the visual identity. The .stuffolioCard() modifier handles the styling." Explain architectural choices but not basic concepts.
- Experienced (default): Concise findings. "SheetHeader: 120pt → 40pt inline. Saves 80pt above fold." No definitions, focus on measurements and tradeoffs.
- Senior/Expert: Minimal. "SheetHeader 120→40pt.
.stuffolioCard() handles styling. 3 files touched." Skip design rationale — just the change, the impact, and the blast radius.
Phase 2: Gather Input
questions:
[
{
"question": "How would you like to provide the view to analyze?",
"header": "Input",
"options": [
{"label": "Screenshot + file path (Recommended)", "description": "Deepest analysis \u2014 visual + structural"},
{"label": "Screenshot only", "description": "Visual analysis, recommendations without code changes"},
{"label": "File path only", "description": "Code analysis, structural improvements"},
{"label": "Capture from simulator", "description": "I'll capture the screenshot automatically (optional)"}
],
"multiSelect": false
}
]
If "Capture from simulator" (optional feature):
xcrun simctl io booted screenshot /tmp/ui-enhancer-radar-capture.png
Then read the captured screenshot.
Phase 2b: View Type Classification
Before analyzing, classify the view type. This determines severity weighting, compensation strategies, and domain-specific heuristics throughout the audit.
Classify by reading the code and/or screenshot. Do NOT ask the user — infer from evidence:
| View Type | How to Identify | Implications |
|---|
| Dashboard / overview | Aggregates data from multiple sources, cards/widgets, summary stats | Space efficiency is Critical; visual variety matters most |
| Detail / inspector | Shows one item's full data, sections of attributes | Information density is Critical; hierarchy matters most |
| Form / input | Text fields, pickers, toggles for data entry | Interaction patterns are Critical; keep chrome minimal |
| List / table | Repeating rows of similar items, search/filter | Density and performance are Critical; row height matters |
| Help / reference | Static instructional text, tips, guides | Space is Medium priority; visual richness prevents boredom |
| Sheet / modal | Presented modally for a focused task | Check for duplicate headers (SheetContainer + SheetHeader) |
| Settings / config | Toggles, preferences, grouped sections | HIG compliance is Critical; follow system patterns |
Record the classification at the top of the report (e.g., "View type: Help / reference (sheet)"). Reference it when:
- Choosing severity levels (Domain 1-11)
- Offering compaction alternatives (Phase 6c)
- Recommending compensation techniques (Phase 6d)
- Deciding whether a finding is worth fixing
- Applying platform-specific design heuristics (see below)
Platform-Specific Design Heuristics
When the view runs on multiple platforms, apply platform-appropriate design expectations:
| View Type | macOS (with sidebar) | iPhone (no sidebar) |
|---|
| Dashboard | Should be a summary — show stats, alerts, and insights. Navigation lives in sidebar. Feature cards that duplicate sidebar items are redundant. | Should be a hub — provide entry points to all features. No sidebar means the dashboard IS the navigation. |
| Settings / config | Settings items accessible via sidebar don't need dashboard cards. Theme toggle in header is optional if Settings is 1 click away. | Settings behind a tab or gear icon — header controls add convenience. |
| Form / input | Wider layout allows side-by-side fields. Keyboard toolbar not needed (Tab key navigation). | Keyboard Done toolbar is essential. Single-column layout. |
| List / table | Can show more columns, denser rows. Hover effects for interactivity. | Swipe actions, pull-to-refresh. Touch-optimized row height (44pt min). |
How to apply: After classifying the view type, check which platform the screenshot/code targets. If macOS with sidebar, apply the "summary" heuristic for dashboards. If iPhone, apply the "hub" heuristic. Flag findings that are platform-specific (e.g., "This redundancy only applies on macOS where the sidebar provides the same navigation").
Phase 3: Screenshot Analysis
When a screenshot is provided, analyze it visually:
3a. First Impression (3-Second Test)
- What does the user see first? Is it what matters most?
- Can the user tell what this screen does within 3 seconds?
- Is the primary action obvious?
3b. Visual Scan Pattern
- Does the layout follow natural eye flow (F-pattern or Z-pattern)?
- Are related elements grouped visually?
- Is there a clear visual hierarchy (primary > secondary > tertiary)?
3c. Content-to-Chrome Ratio
- Measure: What percentage of the viewport is actual content vs. navigation chrome?
- Target: >60% content on iPhone, >70% on iPad
- Flag any view where content ratio falls below 50%
Phase 4: Code Analysis
When a SwiftUI file is provided, read it and analyze:
4a. View Structure
- Total nesting depth (VStack chains)
- Number of distinct visual sections
- Spacing and padding accumulation
- Bottom padding for floating elements
4b. Component Audit
- Redundant UI elements (same info shown twice)
- Elements that could be merged (separate rows that should be one)
- Hidden/conditional elements wasting space when collapsed
- Action button sizing and placement
4c. Text and Typography
- Font hierarchy clarity (is there a clear primary > secondary > tertiary?)
- Line limit truncation (important text cut off while less important text has room)
- Dynamic Type support (
.system(size:) vs semantic fonts)
4d. Color Usage
- Meaningful vs. decorative color
- Competing colors
- Contrast sufficiency
- Information redundancy (color + icon + text)
Phase 5: Domain Analysis
Run the applicable domains based on the interview:
Verification Template (MANDATORY per view audit)
Before grading a view, produce this checklist showing what was actually inspected:
| Domain | Checked? | Receipt | Findings |
|--------|----------|---------|----------|
| 1. Space Efficiency | ? | (file:line) | |
| 2. Visual Hierarchy | ? | (file:line) | |
| 3. Information Density | ? | (file:line) | |
| 4. Interaction Patterns | ? | (file:line) | |
| 5. Accessibility | ? | (file:line) | |
| 6. HIG Compliance | ? | (file:line) | |
| 7. Dark Mode | ? | (file:line) | |
| 8. Performance Impact | ? | (file:line) | |
| 9. Design System Compliance | ? | (file:line) | |
| 10. Competitive Comparison | ? | (file:line) | (only if competitor screenshot provided; otherwise skip with reason) |
| 11. Color Audit | ? | (file:line) | |
| 12. iPad Sheet Sizing | ? | (file:line) | |
| 13. Button Hit Region | ? | (file:line) | |
| 14. Silent Picker | ? | (file:line) | (candidates — verify on device) |
Rules:
- Every domain must be marked checked (yes) or skipped (no — with reason)
- Skipped domains cannot contribute to the grade (positive or negative)
- A grade cannot be produced while any domain has
?
- Domain 10 is conditionally applicable — only runs when the user supplies a competitor screenshot during Phase 1. If no screenshot, mark it
skipped (no competitor screenshot provided) and do not include in scoring.
Domain 1: Space Efficiency enumerate-required
Goal: Maximize content-to-chrome ratio; minimize wasted vertical space.
| Check | What to Look For | Common Fix |
|---|
| Header overhead | Custom headers stacking on nav bar | Collapse or merge headers |
| Dual button rows | Action buttons on separate line from navigation | Merge into one row |
| Section headers | Oversized section titles with icons | Reduce font size, remove decorative icons |
| Bottom padding | Excessive padding for floating elements | Reduce to actual element height + margin |
| Hints/tips | Permanent hints that should dismiss after first use | Use coach marks or remove after learning |
| Photo sections | Separate photo rows when thumbnail could be inline | Merge photo into title/header row |
| Dividers/spacers | Excessive VStack spacing or explicit Spacer() | Reduce spacing values |
| Mergeable sections | Small sections (1-2 items) with their own header overhead | Merge into adjacent related section |
| Relocatable controls | Buttons/toggles in separate rows that could fit in an existing header or toolbar | Move into header, nav bar, or existing row |
| Redundant entry points | Same action accessible from both a toolbar button AND a content card/row | Remove the duplicate; keep the more discoverable one |
Layout reorganization analysis (run for every view):
Before recommending individual element changes, check whether reorganizing the layout would save more space than tweaking individual elements:
- Count items per section — sections with 1-2 items are candidates for merging with adjacent sections
- Check for orphaned controls — buttons, toggles, or status indicators in their own row that could be absorbed into an existing element (e.g., theme toggle → header)
- Identify duplicate entry points — the same action accessible from both a toolbar/action bar AND a content card below; remove the less discoverable one
- Measure section header overhead — each section header costs ~40pt; merging 2 sections saves ~40pt without touching content
Metrics:
- Content starts at: [Y position in points from top]
- First interactive element at: [Y position]
- Content-to-chrome ratio: [percentage]
- Target: Content should start within 120pt of safe area top on iPhone
Domain 2: Visual Hierarchy enumerate-required
Goal: The most important information should be the most prominent.
| Check | What to Look For | Common Fix |
|---|
| Title prominence | Is the item/screen title the dominant element? | Ensure title is largest text |
| Competing elements | Multiple elements fighting for attention | Differentiate with size/weight/color |
| Status overload | Status indicators louder than content | Reduce to subtle badges |
| Date/number sizing | Dates or numbers displayed too large | Use caption/footnote for secondary data |
| Action vs. content | Action buttons more prominent than content | Tone down button styling |
| Truncation | Important text truncated while less important text has room | Allow wrapping or reprioritize |
| Color dominance | Bright colors on secondary elements | Reserve bright colors for primary actions |
Analysis technique:
- Squint at the screenshot — what stands out?
- That should be the primary content, not navigation chrome
- If navigation or status draws the eye first, hierarchy is wrong
Domain 3: Information Density enumerate-required
Goal: Show the right amount of information — not too sparse, not too cluttered.
| Check | What to Look For | Common Fix |
|---|
| Sparse rows | List rows with too much whitespace | Reduce padding, show more per row |
| Dense rows | Too much crammed into one row | Progressive disclosure |
| Redundant info | Same data shown in multiple places | Remove duplicates |
| Hidden useful info | Important data behind taps/scrolling | Surface in primary view |
| Badge overload | Too many status badges on one element | Prioritize, hide secondary |
| Empty states | Large empty areas when data is missing | Collapse section |
| Nonsense values | Negative numbers, meaningless dates | Use human labels or hide |
Density targets:
- List row: 2-3 lines max (title + subtitle + trailing status)
- Card: 4-6 data points visible without scrolling
- Form section: 3-5 fields visible without scrolling
Domain 4: Interaction Patterns mixed
Goal: Every interactive element should be discoverable, predictable, and satisfying.
| Check | What to Look For | Common Fix |
|---|
| Touch targets | Elements smaller than 44x44pt | Increase frame/padding |
| Ambiguous buttons | Buttons that look like labels | Add clear button styling |
| Combined buttons | Two features sharing one button | Separate into distinct buttons |
| Gesture-only actions | Actions only via swipe/long-press | Add visible button alternative |
| Dead ends | Screens with no clear next action | Add CTA or navigation hint |
| Feedback gaps | Actions with no visual/haptic response | Add animation or haptic |
| Scroll discovery | Content below fold with no indicator | Add hint or gradient |
| Menu depth | Important actions buried in menus | Surface frequently-used actions |
Domain 5: Accessibility mixed
Goal: Every user, regardless of ability, can use the view effectively.
| Check | What to Look For | Common Fix |
|---|
| Color-only info | Information conveyed only by color | Add icon + text (triple redundancy) |
| Fixed fonts | .system(size:) instead of semantic | Use Dynamic Type |
| Missing labels | Images/icons without accessibility labels | Add .accessibilityLabel() |
| Small text | Text below 11pt that doesn't scale | Use .caption minimum |
| Contrast ratio | Low contrast text on backgrounds | Ensure 4.5:1 (WCAG AA) |
| VoiceOver order | Reading order doesn't match visual | Reorder or group |
| Motion | Animations without Reduce Motion check | Check accessibility setting |
Domain 6: HIG Compliance mixed
Goal: Follow Apple Human Interface Guidelines for platform consistency.
| Check | What to Look For | Common Fix |
|---|
| Navigation | Custom back buttons, non-standard patterns | Use system NavigationStack |
| Tab bar | Incorrect icons or labels | Follow SF Symbol + short label |
| Sheet presentation | Missing drag indicator or dismiss | Add .presentationDragIndicator(.visible) |
| System colors | Hard-coded colors | Use .primary, .secondary |
| Platform differences | iOS-only patterns on macOS | Use #if os(iOS) |
| Safe areas | Content under notch or home indicator | Respect safe area insets |
| Standard controls | Custom controls duplicating system | Use SwiftUI standard controls |
Domain 7: Dark Mode grep-sufficient
Goal: The view should look correct and intentional in both light and dark mode.
| Check | What to Look For | Common Fix |
|---|
| Hardcoded colors | Color.white, Color.black, hex values | Use semantic colors (.primary, .background) |
| Background assumptions | .background(Color.white) | Use .background(Color(.systemBackground)) |
| Shadow visibility | Shadows invisible in dark mode | Use .shadow with adaptive opacity |
| Image contrast | Images with white/transparent backgrounds | Add dark mode variants or tinted backgrounds |
| Separator visibility | Light separators disappearing | Use .separator system color |
| Accent consistency | Accent colors that clash in dark mode | Test all accent colors in both modes |
| Material usage | Solid backgrounds where materials work better | Use .ultraThinMaterial for overlays |
Analysis: If screenshot provided, check if the view uses light or dark mode. If code available, run the automated checks below.
CRITICAL: Do NOT delegate Domain 7 checks to Explore subagents. Run each check directly using Grep/Read tools against the target view file(s). Subagent sampling causes false negatives.
Automated Detection
Check 7a: Hardcoded colors
grep -n "Color\.white\|Color\.black\|Color(red:\|Color(hex:\|UIColor(red:\|NSColor(red:" <view_file>.swift
grep -n "foregroundColor(\.white)\|foregroundStyle(\.white)\|foregroundColor(\.black)\|foregroundStyle(\.black)" <view_file>.swift
Safe patterns (do NOT flag):
.foregroundStyle(.primary)
.background(Color(.systemBackground))
Check 7b: Background assumptions
grep -n "\.background(Color\.white)\|\.background(\.white)\|\.background(Color\.black)" <view_file>.swift
grep -n "\.background(Color(red:" <view_file>.swift
Check 7c: Shadow visibility in dark mode
grep -n "\.shadow(color:.*\.black.*opacity.*0\.[0-1]" <view_file>.swift
grep -n "\.shadow(radius:" <view_file>.swift | grep -v "color:"
Check 7d: Material opportunities
grep -n "\.background(Color.*opacity\|\.background(\.ultraThin\|\.background(\.thin\|\.background(\.regular" <view_file>.swift
grep -B3 -A3 "ZStack" <view_file>.swift | grep "opacity"
Domain 8: Performance Impact grep-sufficient
Goal: UI patterns should not cause frame drops, excessive redraws, or memory issues.
| Check | What to Look For | Common Fix |
|---|
| Heavy body | Complex expressions in view body | Extract to computed properties |
| Inline images | Image decoding in body | Use AsyncCachedImage or background decoding |
| Missing lazy | Large lists without LazyVStack | Switch to lazy containers |
| Excessive state | Too many @State vars causing redraws | Consolidate or use @Observable |
| Geometry readers | GeometryReader in scroll views | Use .onGeometryChange or remove |
| Conditional complexity | Deep if/else chains in body | Extract to @ViewBuilder functions |
| Animation cost | Heavy animations on low-end devices | Reduce or check Reduce Motion |
Analysis: Read the SwiftUI file and run the automated checks below. Flag files over 500 lines that could benefit from extraction.
CRITICAL: Do NOT delegate Domain 8 checks to Explore subagents. Run each check directly.
Automated Detection
Check 8a: Missing lazy containers
grep -n "ScrollView" <view_file>.swift
grep -B5 "ForEach" <view_file>.swift | grep "VStack\b" | grep -v "LazyVStack"
Safe patterns (do NOT flag):
Check 8b: GeometryReader in scroll contexts
grep -n "GeometryReader" <view_file>.swift
grep -B10 "GeometryReader" <view_file>.swift | grep "ScrollView\|List {"
Safe patterns (do NOT flag):
Check 8c: Excessive @State count
grep -c "@State " <view_file>.swift
Check 8d: Heavy view body
grep -n "var body:" <view_file>.swift
wc -l <view_file>.swift
Check 8e: Image decoding in body
grep -n "UIImage(data:\|UIImage(contentsOf\|NSImage(data:\|NSImage(contentsOf" <view_file>.swift
grep -n "Data(contentsOf:" <view_file>.swift
Check 8f: Animation without Reduce Motion check
grep -n "\.animation(\|withAnimation" <view_file>.swift
grep -n "accessibilityReduceMotion" <view_file>.swift
Safe patterns (do NOT flag):
Domain 9: Design System Compliance enumerate-required
Goal: The view should follow the project's established design system. Compare it against the project's documented rules and flag both off-pattern usage (custom UI where a standard component/modifier exists, off-palette colors, non-@ScaledMetric icons, sheets not using SheetContainer) AND missing-for-consistency gaps (a feature most sibling views enable but this one doesn't).
Why this is project-specific: Domain 9 has no fixed ruleset — its rules ARE the project's design system. With no design-system source (conventions.yaml / CLAUDE.md rules / DESIGN_SYSTEM.md / style guide), it skips entirely and recommends establishing one. It never invents rules.
Reference: references/domain-9-design-system-compliance.md — full checks 9a–9e, unused-capability check, cross-view consistency pass, finding format, exclusions, acceptance criteria, synthetic fixture.
Detection steps (summary — full bodies in the reference):
- Read the design-system source (config → docs). If none exists, skip with a single "no design system found" note.
- 9a color palette — flag colors outside the approved palette.
- 9b component usage — custom header/container UI where a shared component exists.
- 9c modifier usage — card-like UI without
.stuffolioCard() / .actionCard().
- 9d sheet pattern — sheets not using
SheetContainer + SheetHeader.
- 9e icon sizing — fixed icon frames without
@ScaledMetric.
- Unused-capability check — view builds custom UI for a feature a shared component already exposes via a parameter → enable the parameter, delete the custom UI.
- Cross-view consistency — using the Adaptive View Profile, flag features most sibling views enable that this view lacks; always framed as a recommendation with a skip option.
Project-convention awareness: sources rules from .radar-suite/conventions.yaml (approved_palette, spacing_namespace, card_modifiers, sheet_container, shared_components) if present, else greps CLAUDE.md / DESIGN_SYSTEM.md / Sources/Views/Components/. No source → skip the domain.
Severity default: 🟢 MEDIUM (drift, not breakage). Elevates to 🟡 HIGH when the violation breaks a load-bearing accessibility rule the design system encodes (colorblind-palette rule, Dynamic Type via @ScaledMetric), when 5+ views share the off-pattern shape (systemic), or when a missing empty/loading/error state leaves a real flow with no feedback. Never CRITICAL on its own.
Exclusions (skip without flagging): no design-system source exists; dev-only harness / #Preview / fixture; a documented, sanctioned exception; platform-guarded chrome correct for its platform. Full list in the reference.
CRITICAL: Do NOT delegate Domain 9 to Explore subagents. The conformance comparison needs the project docs and the view file held together; a subagent that reads only the view loses the baseline and produces false positives.
Domain 10: Competitive Comparison (On Request) enumerate-required
Only runs when user provides a competitor screenshot during interview.
| Analysis | What to Compare |
|---|
| Information density | How much data is visible without scrolling? |
| Visual hierarchy | What does each app emphasize first? |
| Interaction patterns | How many taps to accomplish the same task? |
| Space efficiency | Content-to-chrome ratio comparison |
| Unique strengths | What does each app do better? |
Output as a side-by-side comparison table.
Domain 11: Color Audit mixed
Goal: Ensure intentional, consistent, and effective use of color throughout the view. Detect monochromatic flatness, semantic drift, opacity inconsistencies, and missing visual differentiation.
Adaptive Color Profile: On first run, this domain reads CLAUDE.md and design system files to learn the project's palette rules. Findings are saved to .agents/ui-enhancer-radar/color-profile.md so subsequent audits can compare views against established patterns.
CRITICAL: Do NOT delegate Domain 11 checks to Explore subagents. Run each check directly using Grep/Read tools against the target view file(s).
11a. Color Inventory Table
Build a table of every colored element in the view:
| Element | Color | Opacity | Role | Category |
|---|
| Header bg | .blue | 100% | Branding | Chrome |
| Section icon | .secondary | 100% | Decoration | Chrome |
| Toggle (on) | .blue | 100% | Interactive | System |
| Row text | .primary | 100% | Content | Text |
Categories: Chrome (navigation, headers, borders), Content (user data, labels), Interactive (buttons, toggles, pickers), Status (badges, indicators), Decoration (icons, backgrounds, separators)
Automated Detection for Color Inventory
grep -n "\.foregroundStyle(\|\.foregroundColor(\|\.fill(\|\.background(\|\.tint(\|Color\.\|Color(\|\.opacity(\|\.shadow(" <view_file>.swift
grep -on "\.\(blue\|red\|green\|yellow\|orange\|purple\|pink\|cyan\|teal\|indigo\|gray\|mint\|brown\|primary\|secondary\|tertiary\|white\|black\|clear\|accentColor\)" <view_file>.swift
grep -n "AccessibleColor\|sf3aYellow\|Color(red:\|Color(hex:\|Color(\"" <view_file>.swift
grep -n "\.opacity(" <view_file>.swift
Build the inventory table from these results. Each grep match becomes a row.
11b. Color Distribution
Count unique colors and how many elements use each:
Color Distribution:
.secondary / .gray: 14 elements ████████████████ (58%) ⚠️ DOMINANT
.blue: 4 elements ████ (17%)
.primary: 3 elements ███ (12%)
.red: 2 elements ██ (8%)
.tertiary: 1 element █ (4%)
Flag: Any color family used by >50% of elements → "Monochromatic risk"
Flag: Any color used only once → "Orphan color — is it intentional?"
11c. Monochromatic Detection (Form Flatness)
This is the most critical check for form/settings views. When a view is visually flat — same background, same text color, same icon color everywhere — users cannot scan it effectively.
Automated Detection for Monochromatic Risk
grep -oh "\.\(blue\|red\|yellow\|orange\|purple\|pink\|cyan\|teal\|indigo\|gray\|mint\|brown\|primary\|secondary\|tertiary\|accentColor\)" <view_file>.swift \
| sort -u | wc -l
grep -oh "\.\(blue\|red\|yellow\|orange\|purple\|pink\|cyan\|teal\|indigo\|gray\|mint\|brown\|primary\|secondary\|tertiary\)" <view_file>.swift \
| sort | uniq -c | sort -rn
Color Variance Score: Count distinct color families (not counting opacity variants) visible in the view, excluding system chrome (status bar, nav bar).
| Score | Distinct Colors | Assessment |
|---|
| 1-2 | Monochromatic | Critical — view appears as a flat, undifferentiated wall |
| 3-4 | Low variety | High — sections blend together, hard to scan |
| 5-6 | Adequate | Medium — functional but could benefit from more differentiation |
| 7+ | Good variety | Pass — clear visual zones |
When monochromatic is detected, recommend (in order):
- Colored section header icons — Each section gets a semantically colored icon circle (e.g., Network = blue cloud, Privacy = red shield, Data = purple database). This alone breaks the monochrome wall into scannable zones.
- Section background tints — Subtle colored backgrounds (5-8% opacity) behind each section group, using the section's accent color.
- Icon colorization — Replace
.secondary gray icons with semantically meaningful colors from the project palette (shield = red, cloud = blue, sparkles = yellow).
- Interactive row highlighting — Rows with pickers, navigation chevrons, or buttons get a subtle accent indicator to distinguish from static display rows.
11d. Section Distinguishability
Can you tell where one section ends and another begins without reading the text?
| Check | What to Look For | Fix |
|---|
| Section headers same style as row labels | Headers use same font/color as content | Make headers bolder, colored, or add accent bar |
| No visual boundary between sections | Sections separated only by thin dividers | Add section background tints or spacing |
| All icons same color | Every icon is .secondary gray | Assign semantic colors per section |
| Sections run together visually | No color or weight change at section boundaries | Add colored section headers or dividers |
11e. Interactive vs. Static Contrast
Can users instantly identify which elements are tappable?
| Check | What to Look For | Fix |
|---|
| Buttons look like labels | Navigation rows with no chevron/color distinction | Add .blue text or chevron indicator |
| Pickers look like static text | Picker values in same color as labels | Use accent color for picker values |
| Destructive actions blend in | "Clear History" looks like "Activity History" | Use .red for destructive, accent for navigation |
| Toggle rows vs info rows | Both look identical except for the toggle | Add subtle leading tint or icon color |
11f. Opacity Consistency
Group elements by role and check if similar elements use matching opacities:
| Role | Elements | Opacities Found | Consistent? |
|---|
| Subtitles | Row descriptions, section footers | 70%, 85%, .secondary | No — standardize to .secondary |
| Backgrounds | Section tints, hover states | 6%, 8%, 45% | Check if intentional variation |
| Shadows | Card shadows, text shadows | 10%, 20%, 30%, 35% | Acceptable range |
| Borders | Card borders, row separators | 15%, 20%, 30% | Narrow to 2 values |
Flag: Same-role elements with >20% opacity variance → "Inconsistent opacity"
11g. Semantic Drift
Does the same color mean different things in different parts of the view?
| Color | Location A | Meaning A | Location B | Meaning B | Drift? |
|---|
.blue | Sidebar | "Own" phase | Dashboard | Primary action | Minor |
.orange | Sidebar | "Dispose Of" phase | Dashboard | Import/Export | Yes — different semantic |
Flag: Same color with clearly different meanings in adjacent or related areas.
11h. Light/Dark Mode Delta
For each element, note whether color/opacity changes between modes:
| Check | What to Look For | Fix |
|---|
Hardcoded .white or .black | Won't adapt to mode switch | Use .primary, .background, semantic colors |
| Hex colors without dark variant | Color(hex: "#FFFFFF") in both modes | Use Color(.systemBackground) or asset catalog |
| Shadows invisible in dark mode | Color.black.opacity(0.1) disappears | Use adaptive opacity or colored shadows |
| Tints that wash out | Light tints (5% opacity) invisible on dark backgrounds | Increase dark mode opacity (e.g., 3% light → 8% dark) |
Automated Detection for Light/Dark Mode Delta
grep -n "Color\.white\|Color\.black\|\.white)\|\.black)" <view_file>.swift | grep -v "//.*white\|//.*black"
grep -n 'Color(hex:\|Color("#\|Color(red:' <view_file>.swift
grep -n "shadow.*\.black.*opacity.*0\.[0-1]" <view_file>.swift
grep -n "\.opacity(0\.0[1-8])" <view_file>.swift
11i. Contrast Pairs (WCAG AA)
Check text-on-background combinations:
| Pair | Ratio Needed | Common Failures |
|---|
| Body text on background | 4.5:1 | .secondary on .systemGroupedBackground in light mode |
| White text on colored cards | 4.5:1 | White on .yellow or .cyan (low contrast) |
| Caption text on tinted backgrounds | 4.5:1 | .tertiary on subtle tints |
| Interactive text on background | 3:1 (large text) | .blue on dark backgrounds can be low |
11j. Design System Compliance
Compare actual color usage against project rules:
- Read CLAUDE.md for palette restrictions (e.g., "never use green", "use AccessibleColor.sf3aYellow instead of .yellow")
- Read design system files for approved colors
- Flag any color not in the approved palette
- Flag any use of restricted colors
Automated Detection for Design System Color Compliance
grep -n "\.green\|Color\.green\|foregroundStyle(\.green)\|foregroundColor(\.green)" <view_file>.swift
grep -n "\.yellow\b" <view_file>.swift | grep -v "sf3aYellow\|AccessibleColor\|semantic.*warning"
grep -oh "\.\(blue\|red\|green\|yellow\|orange\|purple\|pink\|cyan\|teal\|indigo\|gray\|mint\|brown\)" <view_file>.swift \
| sort -u
grep -n "Color(red:\|Color(hex:\|Color(\"" <view_file>.swift
Safe patterns (do NOT flag):
Adaptive View Profile
The View Profile is a persistent file that grows with each audit, enabling cross-view consistency checks for both color (Domain 11) and component usage (Domain 9). Stored at .agents/ui-enhancer-radar/view-profile.md.
On first audit of a project:
- Check for
.agents/ui-enhancer-radar/view-profile.md — if it doesn't exist, create it
- Record the Color Inventory Table, opacity conventions, and semantic color map from this audit
- Record which shared components the view uses and which optional parameters it enables
- Note the project's palette rules from CLAUDE.md
On subsequent audits:
- Load the View Profile
- Color comparison: Flag views that deviate from established color/opacity conventions
- Component comparison: Flag views that don't enable features most sibling views use
- Update the profile with any new patterns discovered
- If a previously recorded convention has changed in the majority of views, update the convention (not the outlier)
View Profile format (.agents/ui-enhancer-radar/view-profile.md):
# UI Enhancer View Profile
*Last updated: [date] | Views audited: [count]*
## Project Palette
[Colors from CLAUDE.md or design system]
## Color Conventions
| Role | Standard Color | Standard Opacity | Views Using |
|---|---|---|---|
| Section header icon | Semantic per section | 100% | DashboardView, ToolsView |
| Row subtitle | .secondary | 100% | SettingsView, ItemDetailView |
| Card shadow | sectionColor | 20% resting | DashboardView |
## Semantic Color Map
| Color | Meaning | Consistent Across Views? |
|---|---|---|
| .blue | Primary actions, Own phase | Yes |
| .orange | Dispose Of, data flow | Yes |
## Component Usage
| Component | Parameter | Enabled In | Not Enabled In | Adoption |
|---|---|---|---|---|
| ContentIllustratedHeader | showThemeToggle | Dashboard, Tools, Reports, MyProducts, StuffScout, LegacyWishes | Settings, Archive | 75% |
| ContentIllustratedHeader | showHelp | Dashboard, Tools, Reports | Settings, Archive, MyProducts | 50% |
| ContentIllustratedHeader | solidBackground | Dashboard | All others | 12% (intentional — dashboard only) |
| SheetContainer | showHelp | AddItem, StuffScout, Backup | Restore, Export | 60% |
## Detected Patterns
| Pattern | Views Using | Views Missing | Notes |
|---|---|---|---|
| Keyboard Done toolbar | All form views | — | Universal |
| Pull-to-refresh | Dashboard, MyProducts | Reports, Archive | Data views only |
| Empty state handling | MyProducts, Dashboard | Loans, Locations | Gap — should add |
## Refinement History
| Date | View | Change | Kept? | Notes |
|---|---|---|---|---|
| 2026-03-22 | DashboardView | VStack spacing 24→16→12pt | Kept 12pt | User wanted tighter |
| 2026-03-22 | DashboardView | Solid header background | Kept | More punch in light mode |
| 2026-03-22 | DashboardView | Quick Stats collapsed padding -8pt | Kept | Closer to MY STUFF |
Refinement History records what was tried during the refinement loop (Phase 7f) — both kept and reverted changes. This serves two purposes:
- If the user returns and says "I liked the spacing we tried last time," the history shows what values were used
- It reveals patterns — if the user consistently asks for tighter spacing, future audits should start with tighter recommendations
Domain 12: iPad Sheet Sizing enumerate-required
Goal: For every .sheet(isPresented:) and .sheet(item:) call in the codebase, verify that tall presented content (Form / List / ScrollView) has been given an iPad sizing mechanism. Catches the caller-side blind spot that Domain 9's "Sheet pattern" check leaves — a sheet can use no house-style container AND no Apple sizing API, and still compile, pass all other domains, and quietly truncate to a floating ~540×620pt form sheet on iPad.
Why this is cross-file: the defect is a relationship between the caller (a .sheet { ... } closure somewhere) and the callee (the presented view's body). Neither file is wrong in isolation. Single-file grep cannot detect it; the domain needs to enumerate call sites, classify presented-view bodies, and check for any of four sizing mechanisms.
Reference: references/domain-12-ipad-sheet-sizing.md — full heuristic, exclusions list, finding format, acceptance criteria.
Recognized sizing mechanisms (any one of these satisfies the check):
- House-style container (
SheetContainer or project equivalent) that applies sizing internally
.presentationSizing(.page) on the sheet closure (iOS 18+)
.presentationDetents([.large]) on the sheet closure (iOS 17 fallback)
- Project convenience modifier (e.g.,
.iPadPageSheet()) that wraps one of the above
Detection steps:
- Enumerate every
.sheet(isPresented:|item:) call: grep -rn --include="*.swift" -E "\.sheet\(isPresented:|\.sheet\(item:" Sources/
- For each, identify the presented view's type (top-level expression in the closure)
- Read that view's
body — skip if it starts with a recognized sizing container; skip if exclusions match (pickers, scanners, share sheets — see reference)
- Check if the sheet closure has any sizing modifier (
.presentationSizing, .presentationDetents, project modifier)
- If the body is tall (Form / List / ScrollView at the top, or
NavigationStack wrapping one of those) AND no sizing mechanism is present → flag
Project-convention awareness: learns house-style container + modifier names from .radar-suite/conventions.yaml if present, or from grepping CLAUDE.md / Sources/Views/Components/ for a View extension that applies presentationSizing(.page) or presentationDetents([.large]). Falls back to Apple-API-only checks if no convention is found.
Severity default: 🟢 MEDIUM (user-visible iPad UX regression, not a crash). Elevates to 🟡 HIGH when any of: app is actively iPad-facing, 5+ sites share the issue (systemic), or the presented view is a critical flow (add-item, settings root, legal/compliance sheet).
Borderline cases (NavigationStack { VStack { Header; mainContent } } where mainContent is a conditional tree) are flagged as medium-confidence with a one-line synopsis of the tallest branch — not batched into the high-confidence fix set by default.
Output row format (per finding):
| File:line | State binding | Presented view (body shape) | Why flagged | Confidence | Suggested fix |
|---|
Sources/Views/Lists/RMAListView.swift:210 | $showingAddRMA | RMAFormView (NavigationStack { Form }) | Tall content, no sizing container, no sizing modifier | High | Append .iPadPageSheet() (project convention) or .presentationSizing(.page) (Apple default) inside the sheet closure |
Exclusions (skip without flagging): Photos/document pickers, scanners, share sheets, confirmation dialogs, splash screens, chooser views, single-action confirmations. Full list in the reference file.
Do NOT delegate Domain 12 to Explore subagents. Run the grep + Read passes directly. Enumeration is the point; a subagent can silently drop findings.
Domain 13: Button Hit Region enumerate-required
Goal: For every Button { } label: { } call site, detect the three-factor interaction bug where .buttonStyle(.plain) + a manually drawn trailing chevron.right + Form/List context collapses the iPad hit region to the chevron alone. The rest of the row becomes visually tappable but functionally dead — users tap and nothing happens.
Why this is three-factor: no single factor is wrong. .buttonStyle(.plain) is legal. Manual chevrons are legal. Buttons inside Forms are legal. The interaction of the three — on iPad specifically — is what breaks. Single-file pattern matchers can't detect interactions.
Reference: references/domain-13-button-hit-region.md — full heuristic, exclusions, both fix options, acceptance criteria.
Detection steps:
- Enumerate every
Button { } label: { } in Sources/ (skip Button("Text", action:) form — no custom label, no bug)
- Check label closure for manually drawn trailing chevron:
Image(systemName: "chevron.right") or "chevron.forward" (ignore DisclosureGroup custom implementations)
- Check Button's modifier chain for
.buttonStyle(.plain) or .buttonStyle(.borderless) — other styles don't have this bug
- Check for Form/List context: structural walk for
Form { ... } / List { ... } ancestor, OR modifier indicators on the Button or its parent Section (.listRowBackground, .listRowInsets, .listRowSeparator)
- Check label closure for
.contentShape(Rectangle()) or .contentShape(.rect) — if present, author explicitly fixed the hit region; skip
- When all four trigger and no content-shape escape, flag
Recommended fixes (offer both):
- Fix A (preferred per HIG): remove the decorative chevron. Card modifiers (
.actionCard(), .destructiveCard(), colored row backgrounds) already read as tappable. HIG says chevrons are for NavigationLinks, not Buttons.
- Fix B: keep the chevron, add
.contentShape(Rectangle()) on the label's outer HStack. Preserves visual treatment but guarantees hit region.
Severity: 🟡 HIGH default (user-visible interaction failure — button looks broken). Elevates to 🔴 CRITICAL for buttons on critical paths (add-item, save, export, payment), systemic cases (5+ sites in same view hierarchy), or accessibility failures confirmed via VoiceOver.
Exclusions:
NavigationLink { } label: — chevrons here are system-managed
DisclosureGroup — chevron is a control, intentional
.buttonStyle(.bordered), .borderedProminent, or any system style other than .plain / .borderless
- Toolbar item Buttons
- macOS-only views (
#if os(macOS) wrapping the whole body)
- Animated expand/collapse chevrons (
.rotationEffect(.degrees(isExpanded ? 90 : 0)))
Output row format:
| File:line | Button action | Label synopsis | Button style | Context | Fix A (preferred) | Fix B |
|---|
Sources/Features/Settings/Views/BackupDataSheet.swift:193 | createBackup() | icon + 2-line text + chevron | .plain | Section in Form via SheetContainer | Remove chevron | Add .contentShape(Rectangle()) |
Pattern sweep follow-up: this bug is rarely isolated. After a first fix, grep the full codebase for the same shape (same card modifier + same chevron + same button style). Offer batch application to consistent patterns.
Do NOT delegate Domain 13 to Explore subagents. Three-factor reasoning requires holding the full label closure, the modifier chain, and the Form/List context simultaneously. A subagent can lose one of the three factors and produce either false positives or false negatives.
Domain 14: Silent Picker enumerate-required
Goal: For every Picker(...) call site, detect the two-factor interaction bug where a menu-style Picker with no explicit .pickerStyle(...) sits in a custom (non-Form/List) container — a DisclosureGroup body, a ScrollView+VStack screen, or a card with cleared row chrome. The picker renders as an enabled pop-up button but its menu never presents on tap. The user taps and nothing happens; the accessibility tree reports a healthy AXPopUpButton. No crash, no warning, no anti-pattern token.
Why this is two-factor: a style-less Picker is idiomatic. Custom containers are legal. The interaction — automatic menu presentation inside cleared-row-chrome / non-Form containers — is what fails, at runtime only.
Cousin of Domain 13, not a duplicate: both yield a control that's visually present and accessibility-enabled but dead on tap. But Domain 13 is Button + chevron + .buttonStyle(.plain) collapsing the hit region (tap misses); Domain 14 is Picker + no .pickerStyle + custom container failing to present the menu (tap lands, nothing opens). Domain 13's detector greps Button/chevron/buttonStyle and walks straight past Picker — which is exactly why this shipped with Domain 13 already in place.
Reference: references/domain-14-silent-picker.md — full heuristic, container classification, false-positive list, on-device verify protocol, acceptance criteria.
Detection steps:
- Enumerate every
Picker(...) in Sources/ (both Picker("Title", selection:) and Picker(selection:) { } label: { } forms)
- Skip any Picker with an explicit
.pickerStyle(...) of any kind — the style is the fix
- Reject
DatePicker / ColorPicker / PhotosPicker false positives (the bare Picker( grep matches them)
- Classify the container: genuine
Form/List row with intact chrome → skip; DisclosureGroup body, ScrollView+VStack screen, or cleared-row-chrome / custom-card context → candidate
- Emit as a ⚠️ candidate, NOT a confirmed bug — static analysis proves the risk, not the deadness
Candidates require on-device verification. Build, run, tap each flagged picker. Menu opens → false positive (some custom containers do present correctly), note and leave. Menu does not open → confirmed, apply Fix A.
Recommended fixes (offer both):
- Fix A (preferred): add
.pickerStyle(.menu) — one line, lowest risk, verified to restore presentation.
- Fix B (only for nested tiers): rebuild on
Menu { Button… } label: { … } — needed only when a picker reveals a sub-picker (two-tier), which Picker can't express cleanly.
Severity: 🟡 HIGH default (control ignores taps — reads as broken). Elevates to 🔴 CRITICAL on critical paths (add-item, edit-item, save, payment) — a dead control in the primary add/edit flow is a textbook App Store Guideline 2.1 rejection ("a control that does nothing") — or when 3+ pickers in one view hierarchy share the pattern.
Exclusions:
- Any explicit
.pickerStyle(...) (.menu, .segmented, .wheel, .inline, .radioGroup, custom)
Picker in a genuine Form/List row with intact chrome
DatePicker, ColorPicker, PhotosPicker
- Already rebuilt on
Menu { }
Output row format:
| File:line | Picker | Style | Container | Status | Fix A | Fix B |
|---|
Sources/Views/Forms/WarrantyFormView+BasicSections.swift:193 | "Acquired From" | none (menu) | DisclosureGroup/VStack, cleared chrome | ⚠️ verify on device | + .pickerStyle(.menu) | Menu { } rebuild |
Pattern sweep follow-up: this bug travels in packs (origin case: 7 sites across 4 files). After the first confirmed fix, sweep for the same shape: Picker + no .pickerStyle + (cleared-chrome modifier OR DisclosureGroup body OR ScrollView/VStack screen).
Do NOT delegate Domain 14's candidate confirmation to static reasoning. The container classification can be delegated, but "is the menu actually dead?" requires a real tap on a real device/sim. Emit candidates; verify on-device before asserting any picker is broken.
Phase 6: Generate Report
Report Structure
## UI Enhancer Report: [ViewName]
*UI Enhancer v[version] | [date]*
### Focus: [from interview — e.g., "General polish" or "Specific: too much wasted space"]
### Screenshot Analysis
- Content-to-chrome ratio: X%
- First content element at: Ypt from top
- Primary action visibility: [Obvious / Hidden / Ambiguous]
- 3-second test: [Pass / Fail — what draws the eye]
### Findings
| # | Domain | Finding | Severity | Effort | Recommendation |
|---|--------|---------|----------|--------|---------------|
| 1 | Space | 45% of screen is header chrome | Critical | Medium | Merge photo into title row |
| 2 | Hierarchy | Date text dominates over item title | High | Small | Replace with status circle |
| 3 | Dark Mode | 3 hardcoded Color.white references | Medium | Trivial | Replace with semantic colors |
| ... | ... | ... | ... | ... | ... |
### UX Score
| Domain | Score | Notes |
|--------|-------|-------|
| 1. Space Efficiency | 4/10 | Content starts at 410pt |
| 2. Visual Hierarchy | 6/10 | Date text too dominant |
| 3. Information Density | 7/10 | Good density, some redundancy |
| 4. Interaction Patterns | 5/10 | Combined buttons, gesture-only deletes |
| 5. Accessibility | 8/10 | Good labels, some fixed fonts |
| 6. HIG Compliance | 7/10 | Minor deviations |
| 7. Dark Mode | 6/10 | Some hardcoded colors |
| 8. Performance Impact | 8/10 | Clean structure |
| 9. Design System Compliance | 7/10 | Mostly compliant |
| 10. Competitive Comparison | N/A | (skipped — no competitor screenshot provided) |
| 11. Color Audit | 5/10 | Monochromatic sections, inconsistent opacities |
| 12. iPad Sheet Sizing | 9/10 | Uses .iPadPageSheet() convention |
| 13. Button Hit Region | 6/10 | One .plain + chevron in Form section needs .contentShape(Rectangle()) |
| 14. Silent Picker | 7/10 | One menu Picker in a DisclosureGroup card lacks .pickerStyle(.menu) — verify on device |
| **Overall** | **6.4/10** | (Domain 10 excluded from average when N/A) |
### Before/After ASCII Mockup
BEFORE:
┌─────────────────────────┐
│ [Nav Bar] │
│ [Photo Row - 56pt] │
│ [Title Row - 64pt] │
│ [Tab Chips - 40pt] │
│ [Action Buttons - 32pt] │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ [Content starts here] │
│ ... │
└─────────────────────────┘
Content ratio: 39%
AFTER:
┌─────────────────────────┐
│ [Nav Bar] │
│ [Photo+Title - 64pt] │
│ [Chips + Buttons - 40pt]│
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ [Content starts here] │
│ ... │
│ ... │
│ ... │
└─────────────────────────┘
Content ratio: 63%
### Space Budget (Before/After)
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| Header chrome | [N]pt | [N]pt | -[N]pt |
| Search/filters | [N]pt | [N]pt | -[N]pt |
| Section headers (×[count]) | [N]pt | [N]pt | -[N]pt |
| Content starts at | [Y]pt | [Y]pt | -[N]pt |
| Content-to-chrome ratio | [X]% | [X]% | +[N]% |
| Sections | [N] | [N] | -[N] |
| Feature cards/buttons | [N] | [N] | -[N] |
**Net space recovered:** ~[N]pt
### Implementation Priority
**Quick Wins (do first):**
1. [Finding] — [exact code change]
**Medium Effort:**
2. [Finding] — [approach]
**Nice-to-Have:**
3. [Finding] — [approach]
Phase 6b: Content & Identity Preservation Check (MANDATORY before removing UI)
When any finding recommends removing or replacing a UI element, check whether it contained informational text OR visual identity elements that serve a purpose beyond decoration.
What to check
For each element being removed, ask:
- Does it contain explanatory text (descriptions, subtitles, instructions)?
- Does it contain status information (counts, states, labels)?
- Would a first-time user lose context about what this screen does?
- Does it contain branding elements (app icon, section icon, colored backgrounds) that establish visual identity?
- Does it contribute to visual consistency across the app (same component used in multiple views)?
- Is it tagged
[PRESERVE] from the Design Intent interview?
If text content would be lost, the text must be relocated, not deleted. Propose one of these options to the user:
If visual identity would be lost (icons, colors, branded backgrounds), route to Phase 6c (Element Compaction) instead of removing — compaction preserves identity at reduced size.
Relocation options
| Option | When to use | Example |
|---|
Help button (?) | Descriptive text that experienced users don't need | Toolbar ? button → popover with description |
Info icon (i) | Context that's useful but not essential to scan | Small ℹ️ next to a title, expands on tap |
| First-visit only | Onboarding text that should disappear after learning | Show once via @AppStorage, hide after first visit |
| Nav bar subtitle | Short taglines (< 40 chars) | .navigationSubtitle("Identify & Appraise") |
| Tooltip / help text | Secondary info on macOS | .help("Identify and value antiques...") |
| Keep as-is | The text is truly decorative and losing it is fine | Marketing copy repeated elsewhere |
How to present
After listing findings but before implementation, flag any content at risk:
questions:
[
{
"question": "Removing [element] would also remove the text '[text]'. Where should this information go?",
"header": "Content",
"options": [
{"label": "Drop it (Recommended)", "description": "The text isn't needed — users understand from context"},
{"label": "Help button (?)", "description": "Add a toolbar help button that shows this on tap"},
{"label": "First-visit only", "description": "Show on first use, hide after"},
{"label": "Keep the element", "description": "Don't remove this element after all"}
],
"multiSelect": false
}
]
If "Keep the element" — remove that finding from the playbook and adjust space savings estimates.
Skip this check when
- The removed element contained only a title that's already in the nav bar
- The removed element contained only color/icon decoration with no text
- The text is displayed elsewhere on the same screen (e.g., in a banner below)
Phase 6c: Element Compaction (MANDATORY when recommending removal of visual elements)
When a finding recommends removing a decorative or branding element for space efficiency, the user may want to preserve the element's visual identity at a smaller footprint. Always offer compaction as an alternative to removal.
Cross-View Consistency Check (run before compaction decisions)
Before recommending removal or compaction of any visual element, check whether it's part of a cross-view pattern:
- Grep the codebase for the component name (e.g.,
ContentIllustratedHeader, SheetHeader, custom component names)
- Count how many views use the same component
- If used in 3+ views, flag it as a consistency pattern:
⚠️ Cross-view pattern detected: [ComponentName] is used in [N] views:
- ViewA.swift (line X)
- ViewB.swift (line Y)
- ViewC.swift (line Z)
Removing it from this view would break visual consistency.
Recommendation: Compact (not remove), or apply the change across all [N] views.
If a consistency pattern is detected:
- Default to Compact instead of Remove
- If the user chooses Remove, warn: "This will make [ViewName] visually inconsistent with [N] other views that use [ComponentName]. Apply the same change to all, or just this one?"
- Offer: "Apply to all [N] views" / "Just this view" / "Cancel"
When to trigger
This check runs when ANY finding recommends removing:
- Illustrated headers (ContentIllustratedHeader, custom banners)
- Branded sections with icons, backgrounds, or imagery
- Photo rows, hero images, or visual feature cards
- Any element the user may consider part of the view's visual identity
- Any element tagged
[PRESERVE] during the Design Intent interview
What to ask
For each element recommended for removal, present compaction as the default:
questions:
[
{
"question": "The [element] uses ~[N]pt. How would you like to handle it?",
"header": "Element",
"options": [
{"label": "Compact (Recommended)", "description": "Preserve visual identity at reduced size (~[M]pt savings)"},
{"label": "Remove entirely", "description": "Maximum space savings (~[N]pt recovered)"},
{"label": "Keep as-is", "description": "No change to this element"}
],
"multiSelect": false
}
]
Compaction techniques by element type
| Element Type | Full Size | Compaction Techniques | Target Size |
|---|
| Illustrated header (icon + title + subtitle + background) | ~100-140pt | Inline icon (28pt) + title only, reduce background height, drop subtitle | ~44-56pt |
| Section header (decorative circle + title) | ~40-48pt | Smaller circle (18pt), reduce font, tighten padding | ~28-32pt |
| Photo/hero row | ~80-120pt | Thumbnail (40pt) inline with title instead of full-width | ~44pt |
| Status banner/card | ~60-80pt | Compact badge or chip instead of card | ~28-36pt |
| Tip/hint section | ~60-100pt | Collapsible disclosure, or single-line with (i) | ~20-44pt |
| Feature card | ~80-120pt | Reduce padding, smaller icon, tighter text | ~48-64pt |
How to generate compact code
When "Compact" is selected, apply these reductions in order until target height is reached:
- Reduce icon size — e.g., 48pt → 28pt, 32pt → 20pt
- Inline layout — switch from VStack to HStack where possible
- Drop secondary text — remove subtitles, taglines, descriptions (relocate per Phase 6b if needed)
- Tighten spacing — reduce padding and VStack/HStack spacing by 30-50%
- Reduce background — shrink or remove decorative backgrounds, keep accent color as border or tint
- Simplify — remove shadows, reduce corner radius, flatten visual layers
Playbook format for compaction
When compaction is chosen, the playbook entry should show the before/after with measurements:
### Fix #N: Compact [element name]
**File:** `Sources/Views/[file].swift`
**Lines:** [range]
**Before:** (~[N]pt height)
[exact code block]
**After:** (~[M]pt height, [savings]pt saved)
[exact replacement code — compacted version]