diff --git a/tvos-focus/SKILL.md b/tvos-focus/SKILL.md index 09620c1..bf211f0 100644 --- a/tvos-focus/SKILL.md +++ b/tvos-focus/SKILL.md @@ -7,6 +7,15 @@ description: This skill should be used when the user asks about "tvOS focus", "A tvOS focus is spatial — the engine casts a ray from the focused element and picks the nearest focusable rectangle in the swipe direction. This is the ONLY interaction model on Apple TV. No touch, no cursor. +## Available Resources + +- `scripts/audit-tvos-focus.sh ` — run first to find SwiftUI focus APIs, default chrome hotspots, invisible controls, Spacer gaps, ScrollView traps, directional command handlers, overlays, and focus restore code. +- `references/full-guide.md` — complete tvOS focus guide with detailed examples and testing approaches. + +## Audit First + +Run `scripts/audit-tvos-focus.sh ` before editing focus code. Treat the output as review leads, not automatic bugs. Prioritize matches in this order: invisible or clear interactive views, missing custom `ButtonStyle`, `Spacer()` gaps between interactive sections, ScrollViews without focus grouping, overlays without background disabling, and missing focus restore after dismissal. + ## Gotchas (the stuff Claude gets wrong) These are real failures from production tvOS apps. Check these FIRST before writing focus code. diff --git a/tvos-focus/scripts/audit-tvos-focus.sh b/tvos-focus/scripts/audit-tvos-focus.sh new file mode 100755 index 0000000..0a41971 --- /dev/null +++ b/tvos-focus/scripts/audit-tvos-focus.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Audit a SwiftUI tvOS app for common focus navigation risk areas. +# Usage: audit-tvos-focus.sh [path-to-repo] +# Outputs a structured report of findings grouped by category. + +set -o pipefail + +REPO="${1:-.}" + +if ! command -v rg >/dev/null 2>&1; then + echo "error: ripgrep (rg) is required for this audit" >&2 + exit 1 +fi + +if [ ! -d "$REPO" ]; then + echo "error: repository path is not a directory: $REPO" >&2 + exit 1 +fi + +if [ ! -r "$REPO" ]; then + echo "error: repository path is not readable: $REPO" >&2 + exit 1 +fi + +echo "=== tvOS Focus Audit: $REPO ===" +echo "" + +echo "## 1. Focus APIs In Use" +rg -n 'FocusState|@FocusState|\.focused\(|\.focusSection\(|\.prefersDefaultFocus|\.defaultFocus|\.focusScope|\.focusEffect|\.focusable\(' \ + --type swift "$REPO" 2>/dev/null || echo " (none found)" +echo "" + +echo "## 2. Button Styles And Default Focus Chrome Hotspots" +rg -n 'ButtonStyle|\.buttonStyle\(\.plain\)|\.buttonStyle\(|@Environment\(\\\.isFocused\)|isFocused' \ + --type swift "$REPO" 2>/dev/null | head -120 || echo " (none found)" +echo "" + +echo "## 3. Invisible Or Clear Interactive Views" +rg -n 'Button\s*\{|Color\.clear|\.opacity\(0\)|\.hidden\(\)|\.frame\(width:\s*1|\.frame\(.*height:\s*1' \ + --type swift "$REPO" 2>/dev/null | head -120 || echo " (none found)" +echo "" + +echo "## 4. Spacer Gaps That May Break Spatial Navigation" +rg -n 'Spacer\(\)' --type swift "$REPO" 2>/dev/null | head -80 || echo " (none found)" +echo "" + +echo "## 5. ScrollViews That May Trap Directional Input" +rg -n 'ScrollView\s*[\(\{]|LazyHStack|LazyVStack|\.scrollTarget|\.scrollPosition' \ + --type swift "$REPO" 2>/dev/null | head -120 || echo " (none found)" +echo "" + +echo "## 6. Directional Command Handlers" +rg -n '\.onMoveCommand|\.onExitCommand|\.onPlayPauseCommand|MoveCommandDirection|pressesBegan|UIPress' \ + --type swift "$REPO" 2>/dev/null || echo " (none found)" +echo "" + +echo "## 7. Overlay And Modal Focus Capture" +rg -n 'ZStack|\.overlay\s*[\(\{]|fullScreenCover|sheet\(|popover\(|\.disabled\(|isPresented|isOverlay|showOverlay|dismiss' \ + --type swift "$REPO" 2>/dev/null | head -160 || echo " (none found)" +echo "" + +echo "## 8. Focus Restore After State Changes" +rg -n '\.onChange\(of:|DispatchQueue\.main\.async|Task\s*\{|focused[A-Za-z0-9_]*\s*=' \ + --type swift "$REPO" 2>/dev/null | head -120 || echo " (none found)" +echo "" + +echo "## 9. tvOS-Specific Files And Targets" +TVOS_FILES="$(find "$REPO" \( -name '*tvOS*' -o -name '*.xcodeproj' -o -name 'Package.swift' \) \ + -not -path '*/.*' 2>/dev/null)" +if [ -n "$TVOS_FILES" ]; then + echo "$TVOS_FILES" +else + echo " (none found)" +fi +echo "" + +echo "=== End Audit ==="