#!/bin/bash # # Visual SVG Validation Script # # Compares old vs new SVG files using browser-accurate rendering (Playwright) # and pixel-level comparison (ImageMagick), then generates a prompt for AI analysis. # # Usage: ./tools/validate-svg-changes.sh # set -e SVG_FILE="${1:-src/modules/bmm/docs/images/workflow-method-greenfield.svg}" TMP_DIR="/tmp/svg-validation-$$" echo "🎨 Visual SVG Validation" echo "" # Check if file exists if [ ! -f "$SVG_FILE" ]; then echo "❌ Error: SVG file not found: $SVG_FILE" exit 1 fi # Check for ImageMagick if ! command -v magick &> /dev/null; then echo "❌ ImageMagick not found" echo "" echo "Install with:" echo " brew install imagemagick" echo "" exit 1 fi echo "✓ ImageMagick found" # Check for Node.js if ! command -v node &> /dev/null; then echo "❌ Node.js not found" exit 1 fi echo "✓ Node.js found ($(node -v))" # Check for Playwright (local install) if [ ! -d "node_modules/playwright" ]; then echo "" echo "📦 Playwright not found locally" echo "Installing Playwright (local to this project, no package.json changes)..." echo "" npm install --no-save playwright echo "" echo "✓ Playwright installed" else echo "✓ Playwright found" fi echo "" echo "🔄 Rendering SVGs to PNG..." echo "" # Create temp directory mkdir -p "$TMP_DIR" # Extract old SVG from git git show HEAD:"$SVG_FILE" > "$TMP_DIR/old.svg" 2>/dev/null || { echo "❌ Could not extract old SVG from git HEAD" echo " Make sure you have uncommitted changes to compare" exit 1 } # Copy new SVG cp "$SVG_FILE" "$TMP_DIR/new.svg" # Create Node.js renderer script in project directory (so it can find node_modules) cat > "tools/render-svg-temp.js" << 'EOJS' const { chromium } = require('playwright'); const fs = require('fs'); async function renderSVG(svgPath, pngPath) { const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); const svgContent = fs.readFileSync(svgPath, 'utf8'); const widthMatch = svgContent.match(/width="([^"]+)"/); const heightMatch = svgContent.match(/height="([^"]+)"/); const width = Math.ceil(parseFloat(widthMatch[1])); const height = Math.ceil(parseFloat(heightMatch[1])); const html = ` ${svgContent} `; await page.setContent(html); await page.setViewportSize({ width, height }); await page.waitForTimeout(1000); await page.screenshot({ path: pngPath, fullPage: true }); await browser.close(); console.log(`✓ Rendered ${pngPath}`); } (async () => { await renderSVG(process.argv[2], process.argv[3]); await renderSVG(process.argv[4], process.argv[5]); })(); EOJS # Render both SVGs (run from project dir so node_modules is accessible) node tools/render-svg-temp.js \ "$TMP_DIR/old.svg" "$TMP_DIR/old.png" \ "$TMP_DIR/new.svg" "$TMP_DIR/new.png" # Clean up temp script rm tools/render-svg-temp.js echo "" echo "🔍 Comparing pixels..." echo "" # Compare using ImageMagick DIFF_OUTPUT=$(magick compare -metric AE "$TMP_DIR/old.png" "$TMP_DIR/new.png" "$TMP_DIR/diff.png" 2>&1 || true) DIFF_PIXELS=$(echo "$DIFF_OUTPUT" | awk '{print $1}') # Get image dimensions DIMENSIONS=$(magick identify -format "%wx%h" "$TMP_DIR/old.png") WIDTH=$(echo "$DIMENSIONS" | cut -d'x' -f1) HEIGHT=$(echo "$DIMENSIONS" | cut -d'x' -f2) TOTAL_PIXELS=$((WIDTH * HEIGHT)) # Calculate percentage DIFF_PERCENT=$(echo "scale=4; $DIFF_PIXELS / $TOTAL_PIXELS * 100" | bc) echo "📊 Results:" echo " Dimensions: ${WIDTH} × ${HEIGHT}" echo " Total pixels: $(printf "%'d" $TOTAL_PIXELS)" echo " Different pixels: $(printf "%'d" $DIFF_PIXELS)" echo " Difference: ${DIFF_PERCENT}%" echo "" if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then echo "✅ ESSENTIALLY IDENTICAL (< 0.01% difference)" VERDICT="essentially identical" elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then echo "⚠️ MINOR DIFFERENCES (< 0.1%)" VERDICT="minor differences detected" else echo "❌ SIGNIFICANT DIFFERENCES (≥ 0.1%)" VERDICT="significant differences detected" fi echo "" echo "📁 Output files:" echo " Old render: $TMP_DIR/old.png" echo " New render: $TMP_DIR/new.png" echo " Diff image: $TMP_DIR/diff.png" echo "" # Generate HTML comparison page cat > "$TMP_DIR/comparison.html" << 'EOHTML' SVG Comparison

🎨 SVG Visual Comparison

File: FILENAME_PLACEHOLDER

Dimensions
DIMENSIONS_PLACEHOLDER
Different Pixels
DIFF_PIXELS_PLACEHOLDER
Difference
DIFF_PERCENT_PLACEHOLDER%
Verdict
VERDICT_PLACEHOLDER

📄 Old (HEAD)

Old SVG

📝 New (Working)

New SVG

🔍 Diff (Red = Changes)

Diff
EOHTML # Determine verdict class for styling if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then VERDICT_CLASS="good" elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then VERDICT_CLASS="warning" else VERDICT_CLASS="bad" fi # Replace placeholders in HTML sed -i '' "s|FILENAME_PLACEHOLDER|$SVG_FILE|g" "$TMP_DIR/comparison.html" sed -i '' "s|DIMENSIONS_PLACEHOLDER|${WIDTH} × ${HEIGHT}|g" "$TMP_DIR/comparison.html" sed -i '' "s|DIFF_PIXELS_PLACEHOLDER|$(printf "%'d" $DIFF_PIXELS) / $(printf "%'d" $TOTAL_PIXELS)|g" "$TMP_DIR/comparison.html" sed -i '' "s|DIFF_PERCENT_PLACEHOLDER|$DIFF_PERCENT|g" "$TMP_DIR/comparison.html" sed -i '' "s|VERDICT_PLACEHOLDER|$VERDICT|g" "$TMP_DIR/comparison.html" sed -i '' "s|VERDICT_CLASS_PLACEHOLDER|$VERDICT_CLASS|g" "$TMP_DIR/comparison.html" echo "✓ Generated comparison page: $TMP_DIR/comparison.html" echo "" echo "🌐 Opening comparison in browser..." open "$TMP_DIR/comparison.html" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "🤖 AI VISUAL ANALYSIS PROMPT" echo "" echo "Copy and paste this into Gemini/Claude with the diff image attached:" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" cat << PROMPT I've made changes to an Excalidraw diagram SVG file. Please analyze the visual differences between the old and new versions. **Automated Analysis:** - Dimensions: ${WIDTH} × ${HEIGHT} pixels - Different pixels: $(printf "%'d" $DIFF_PIXELS) out of $(printf "%'d" $TOTAL_PIXELS) - Difference: ${DIFF_PERCENT}% - Verdict: ${VERDICT} **Attached Image:** The attached image shows the pixel-level diff (red = differences). **Questions:** 1. Are the differences purely anti-aliasing/rendering artifacts, or are there actual content changes? 2. If there are content changes, what specifically changed? 3. Do the changes align with the intent to remove zombie Excalidraw elements (elements marked as deleted but left in the JSON)? 4. Is this safe to commit? **Context:** - File: $SVG_FILE - Changes: Removed 191 lines of zombie JSON from Excalidraw source - Expected: Visual output should be identical (zombie elements were already marked as deleted) PROMPT echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "📎 Attach this file to your AI prompt:" echo " $TMP_DIR/diff.png" echo "" echo "💡 To open the diff image:" echo " open $TMP_DIR/diff.png" echo ""