From 5dedf5cdd712a83dbf95b635adf69201dc537da0 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 28 Jun 2026 16:57:29 -0500 Subject: [PATCH] =?UTF-8?q?tools(qa):=20add=20painter-xml-scan.sh=20?= =?UTF-8?q?=E2=80=94=20catches=20painterResource(non-vector=20XML)=20crash?= =?UTF-8?q?es=20(O-ONBOARD-001=20class)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/painter-xml-scan.sh | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 scripts/painter-xml-scan.sh diff --git a/scripts/painter-xml-scan.sh b/scripts/painter-xml-scan.sh new file mode 100755 index 00000000..382f03dc --- /dev/null +++ b/scripts/painter-xml-scan.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# +# CloserApp — painterResource(non-vector XML) crash scanner. +# +# WHY THIS EXISTS: Compose's `painterResource(id)` picks its loader by file extension — any `.xml` +# drawable is routed through the VectorDrawable loader, which throws at runtime: +# java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported +# the instant such a composable is rendered, if the XML's root is NOT (e.g. , +# , , , , , ...). It compiles fine and is +# invisible to unit tests — it only blows up on screen. This shipped once as O-ONBOARD-001: the app-icon +# redesign (commit 334cb07) turned ic_launcher_foreground.xml from into a , crashing +# every fresh install on the final onboarding slide + the login screen (both used +# painterResource(R.drawable.ic_launcher_foreground)). Recurring QA missed it (logged-in emulators skip +# onboarding/auth). Fix: point painterResource at the underlying raster, or use ImageVector/ImageBitmap. +# +# This scan is a cheap deterministic gate against the whole class. Exit 1 on any finding. +# +# Usage: ./scripts/painter-xml-scan.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SRC="$ROOT/app/src/main/java" +RES_GLOB="$ROOT/app/src/main/res/drawable"* + +fail=0 +found=0 + +# Collect every painterResource(R.drawable.NAME) reference in Kotlin source. +while IFS= read -r line; do + file="${line%%:*}" + rest="${line#*:}" + lineno="${rest%%:*}" + name="$(printf '%s\n' "$rest" | grep -oE 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' | head -1 | sed -E 's/.*R\.drawable\.//')" + [ -z "$name" ] && continue + + # Is there an XML drawable resource of that name in any drawable* bucket? + xml="" + for d in $RES_GLOB; do + [ -f "$d/$name.xml" ] && xml="$d/$name.xml" && break + done + [ -z "$xml" ] && continue # not an XML drawable (raster) → painterResource-safe + + # Root element of the XML drawable. + root="$(grep -m1 -oE '<(vector|bitmap|layer-list|shape|selector|adaptive-icon|animated-vector|ripple|inset|clip|rotate|scale|transition|level-list|nine-patch|drawable)' "$xml" | head -1 | tr -d '<')" + if [ "$root" != "vector" ]; then + found=$((found+1)); fail=1 + rel="${file#"$ROOT"/}" + echo "🔴 CRASH RISK $rel:$lineno painterResource(R.drawable.$name) → <$root> ($name.xml)" + echo " painterResource only loads XML or raster files; a <$root> root throws at render." + echo " Fix: reference the underlying raster, or use ImageVector.vectorResource / ImageBitmap.imageResource." + fi +done < <(grep -rREn 'painterResource\(R\.drawable\.[A-Za-z0-9_]+' "$SRC" 2>/dev/null || true) + +if [ "$fail" -eq 0 ]; then + echo "✅ painter-xml-scan: 0 painterResource() calls target a non- XML drawable." +else + echo "" + echo "❌ painter-xml-scan: $found painterResource(non-vector XML) call(s) — each is a guaranteed runtime crash on render." +fi +exit $fail