This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/scripts/audit-touch-targets.sh
Jeffrey Smith ff19a1b4d3
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-sqlite (push) Successful in 2m49s
CI/CD / test-go-pg (push) Successful in 2m51s
CI/CD / build-and-deploy (push) Successful in 49s
Feat v0.6.16 usability survey gate (#51)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-04-01 14:52:14 +00:00

147 lines
5.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# audit-touch-targets.sh — Static analysis for 44px minimum touch targets on mobile.
# Scans JS templates for clickable elements, cross-references CSS for mobile sizing.
# Usage: bash scripts/audit-touch-targets.sh
set -euo pipefail
CSS_DIR="${1:-src/css}"
JS_DIR="${2:-src/js}"
PKG_DIR="${3:-packages}"
VIOLATIONS=0
# ── Phase 1: Build a map of class -> mobile sizing from CSS ──
# For each class, record whether it has min-width/min-height >= 44px in mobile breakpoint
declare -A HAS_MOBILE_SIZE # class -> "1" if it has adequate mobile touch target
declare -A CLOSE_BUTTONS # class -> "file:line" for close-button-like elements
# Known close button classes to audit specifically
CLOSE_CLASSES=(
"sw-banner__close"
"sw-toast__close"
"sw-dialog__close"
"sw-drawer__close"
"sw-tabs__arrow"
"modal-close"
)
# Scan CSS for mobile breakpoint rules with min-height/min-width >= 44px
for f in "$CSS_DIR"/*.css "$PKG_DIR"/*/css/main.css; do
[ -f "$f" ] || continue
in_mobile=0
lineno=0
current_selector=""
while IFS= read -r line; do
lineno=$((lineno + 1))
trimmed="${line#"${line%%[![:space:]]*}"}"
# Enter mobile @media block
if [[ "$trimmed" =~ ^@media.*max-width.*768 ]]; then
in_mobile=1
continue
fi
if (( in_mobile )); then
# Track brace depth to know when we leave @media
if [[ "$trimmed" == "}" ]] && [[ -z "$current_selector" ]]; then
in_mobile=0
continue
fi
# Selector line
if [[ "$trimmed" == *"."* ]] && [[ "$trimmed" == *"{"* ]]; then
sel="${trimmed%%\{*}"
if [[ "$sel" =~ \.([a-zA-Z_][a-zA-Z0-9_-]*) ]]; then
current_selector="${BASH_REMATCH[1]}"
fi
# Check inline properties
if [[ "$trimmed" =~ min-height.*44px ]] || [[ "$trimmed" =~ min-height.*2\.75rem ]] || [[ "$trimmed" =~ min-height.*var\(--sp-12\) ]]; then
HAS_MOBILE_SIZE["$current_selector"]=1
fi
if [[ "$trimmed" =~ min-width.*44px ]] || [[ "$trimmed" =~ min-width.*2\.75rem ]]; then
HAS_MOBILE_SIZE["$current_selector"]=1
fi
[[ "$trimmed" == *"}"* ]] && current_selector=""
continue
fi
# Property line inside mobile block
if [[ -n "$current_selector" ]]; then
if [[ "$trimmed" =~ min-height.*44px ]] || [[ "$trimmed" =~ min-height.*2\.75rem ]] || [[ "$trimmed" =~ min-height.*var\(--sp-12\) ]]; then
HAS_MOBILE_SIZE["$current_selector"]=1
fi
if [[ "$trimmed" =~ min-width.*44px ]] || [[ "$trimmed" =~ min-width.*2\.75rem ]]; then
HAS_MOBILE_SIZE["$current_selector"]=1
fi
[[ "$trimmed" == "}" ]] && current_selector=""
fi
fi
done < "$f"
done
echo "╔══════════════════════════════════════════════════════════════════════╗"
echo "║ Touch Target Audit Report ║"
echo "╠══════════════════════════════════════════════════════════════════════╣"
echo ""
# ── Phase 2: Check known close-button classes ──
echo "## Close Button Classes"
echo ""
for cls in "${CLOSE_CLASSES[@]}"; do
if [[ -n "${HAS_MOBILE_SIZE[$cls]:-}" ]]; then
printf " ✓ .%-30s — has mobile touch target\n" "$cls"
else
printf " ✗ .%-30s — MISSING min-width/min-height: 44px in mobile breakpoint\n" "$cls"
VIOLATIONS=$((VIOLATIONS + 1))
fi
done
echo ""
# ── Phase 3: Scan JS for small interactive elements ──
echo "## Interactive Elements in JS Templates"
echo ""
# Find elements with onClick or <button that have small/no padding and no mobile override
for f in $(find "$JS_DIR" "$PKG_DIR" -name '*.js' -type f 2>/dev/null); do
[ -f "$f" ] || continue
lineno=0
while IFS= read -r line; do
lineno=$((lineno + 1))
# Look for onClick with a class
if [[ "$line" =~ onClick ]] && [[ "$line" =~ class.*\"([a-zA-Z_][a-zA-Z0-9_\ -]*)\" ]]; then
classes="${BASH_REMATCH[1]}"
for cls in $classes; do
# Skip known-good patterns (buttons with adequate padding)
[[ "$cls" == sw-btn* ]] && continue
[[ "$cls" == sw-menu__item* ]] && continue
[[ "$cls" == sw-dropdown__option* ]] && continue
[[ "$cls" == sw-tabs__tab* ]] && continue
# Check if this class has a mobile override
if [[ -z "${HAS_MOBILE_SIZE[$cls]:-}" ]]; then
# Only flag if it looks like a small interactive element
if [[ "$cls" == *close* ]] || [[ "$cls" == *btn* ]] || [[ "$cls" == *toggle* ]] || [[ "$cls" == *trigger* ]] || [[ "$cls" == *action* ]] || [[ "$cls" == *clear* ]] || [[ "$cls" == *delete* ]] || [[ "$cls" == *remove* ]]; then
printf " ? %-50s — .%s (no mobile min-size found)\n" "$(basename "$f"):$lineno" "$cls"
fi
fi
done
fi
done < "$f"
done
echo ""
echo "╚══════════════════════════════════════════════════════════════════════╝"
echo ""
echo "Summary: $VIOLATIONS known violations in close-button classes"
echo ""
if (( VIOLATIONS > 0 )); then
echo "RESULT: FAIL — $VIOLATIONS close button(s) need min-width/min-height: 44px in @media (max-width: 768px)"
exit 1
else
echo "RESULT: PASS — all known interactive elements have adequate mobile touch targets"
exit 0
fi