PROMPT Chrome Visual Testing

Documentation for Developers

Prompt: Visual Testing Vitto Avatar via Chrome Injection

Copy-paste this entire prompt at the start of a new Claude session when you need the agent to visually test, iterate on, or screenshot the Vitto Canvas 2D avatar.


Context for Agent

You are working on Vitto, a Canvas 2D animated black cat avatar. The avatar engine is a pure TypeScript library at libs/libs-anim/vitto-avatar/src/lib/engine/ inside the gspace1 NX monorepo. It renders a cat with 14 mood states (neutral, focused, cheerful, empathetic, curious, analytical, celebratory, apologetic, inspired, urgent, playful, confused, proud, sleepy), each controlling 30+ animatable properties (eyes, mouth, brows, body, particles, etc.) with smooth cubic-eased transitions.

Critical Constraint: You CANNOT Serve Files to Chrome

Your VM has no network bridge to the user's Chrome browser. localhost, 127.0.0.1, and any port you bind are unreachable from Chrome. Do not attempt to start HTTP servers — they will appear to work in your terminal but Chrome will show "ERR_CONNECTION_REFUSED."

The Workaround: Chrome JavaScript Injection into example.com

You have access to the "Claude in Chrome" extension which can execute JavaScript in browser tabs. The workflow is:

  1. Inject HTML/CSS into an example.com tab via document.write()
  2. Inject the engine as base64-encoded chunks
  3. Inject the UI harness script
  4. Take screenshots to visually verify rendering

Detailed Steps

Step 0: Build the Engine Bundle

If the bundle doesn't exist yet or if you've changed engine source files, build it:

cd /path/to/gspace1
npx esbuild libs/libs-anim/vitto-avatar/src/lib/engine/index.ts \
  --bundle --format=iife --global-name=VittoEngine \
  --outfile=dist/libs/libs-anim/vitto-avatar/engine/vitto-engine.iife.js \
  --minify --sourcemap

The output should be ~35KB at dist/libs/libs-anim/vitto-avatar/engine/vitto-engine.iife.js.

Step 1: Get Chrome Tab Context

Use tabs_context_mcp with createIfEmpty: true

Look for an existing tab on example.com or create a new one. Note the tabId.

Step 2: Navigate to example.com (if needed)

If the tab isn't already on example.com, navigate there first.

Step 3: Inject the HTML Structure

The dev harness HTML lives at:

"vitto animation/vitto-dev-harness.html"

Read it, extract everything between <style> and </style> (the CSS), and everything between <body> and the first <script> (the HTML structure). Then inject via the Chrome JS tool:

document.write("<html><head><style>...CSS HERE...</style></head><body>...HTML HERE...</body></html>");

Escaping rules:

The HTML structure includes:

Step 4: Prepare Base64 Chunks of the Engine

python3 -c "
import base64
with open('dist/libs/libs-anim/vitto-avatar/engine/vitto-engine.iife.js', 'rb') as f:
    encoded = base64.b64encode(f.read()).decode()
chunk_size = 8000
for i in range(0, len(encoded), chunk_size):
    chunk = encoded[i:i+chunk_size]
    with open(f'/tmp/b64chunk_{i // chunk_size}.txt', 'w') as out:
        out.write(chunk)
    print(f'Chunk {i // chunk_size}: {len(chunk)} chars')
print(f'Total base64 length: {len(encoded)}')
"

This typically produces 5-6 chunks of ~8000 chars each.

Step 5: Inject Base64 Chunks into Chrome

For each chunk file, read it and inject:

// First chunk — use assignment (=)
window._b64 = "...contents of b64chunk_0.txt...";
"chunk0: " + window._b64.length

// All subsequent chunks — use append (+=)
window._b64 += "...contents of b64chunk_1.txt...";
"chunk1: " + window._b64.length

// ... repeat for all chunks

Important: Each chunk injection must be a separate Chrome JS tool call. Keep each call under ~4000 chars of actual text content to stay within tool limits. If a chunk is too large, split it in half.

Verify final length matches the total from Step 4.

Step 6: Decode and Evaluate the Engine

try {
  window.VittoEngine = (new Function(
    atob(window._b64) + "; return VittoEngine;"
  ))();
  "VittoEngine keys: " + Object.keys(window.VittoEngine).join(", ");
} catch(e) {
  "ERROR: " + e.message;
}

Expected output should include: VittoRenderer, MOOD_DEFINITIONS, MOOD_NUMERIC_KEYS, DEFAULT_RENDERER_CONFIG, TAU, PI, lerp, clamp, easeInOutCubic, easeOutBack

Why new Function() instead of eval()? — eval() doesn't expose var declarations to the global scope inside Chrome extension's JS execution context. new Function() lets us explicitly return VittoEngine.

Step 7: Inject the UI Harness Script

Read the <script> section from vitto-dev-harness.html (lines 190-396). Split into 2-3 parts (~3000 chars each) and inject sequentially via Chrome JS tool.

Critical: All shared references must be stored on window.* because each Chrome JS tool call runs in a separate evaluation scope:

Part 1 should create: renderer, mood buttons, mouse tracking, FPS counter Part 2 should create: auto-cycle, behavior toggles, property sliders Part 3 should create: state inspector, syncSliders function, setInterval update loop

Step 8: Screenshot and Verify

Take a screenshot to verify the harness is working:

computer action: screenshot

You should see:

Changing Moods for Testing

Via JS tool:

window._renderer.setMood('cheerful');
document.querySelectorAll('.mood-btn').forEach(b => b.classList.toggle('active', b.dataset.mood === 'cheerful'));
document.getElementById('moodBadge').textContent = 'cheerful';

Force-completing transitions (bypasses the 1fps throttle):

var target = VittoEngine.MOOD_DEFINITIONS['urgent'];
var s = window._renderer.getState();
s.mood = 'urgent';
s.tT = 1.0;
for (var k of VittoEngine.MOOD_NUMERIC_KEYS) {
  if (target[k] !== undefined) s[k] = target[k];
}
s.bg = [...target.bg];

Wait 2 seconds, then screenshot. The canvas will re-render on the next RAF tick.

Zooming into the Canvas

Use the zoom action to get a close-up of Vitto:

computer action: zoom, region: [5, 260, 280, 530]

(Adjust coordinates based on actual canvas position in the viewport.)

File Locations Reference

File Path Purpose
Engine source libs/libs-anim/vitto-avatar/src/lib/engine/ TypeScript source (single source of truth)
Engine entry libs/libs-anim/vitto-avatar/src/lib/engine/index.ts Bundle entry point
Built bundle dist/libs/libs-anim/vitto-avatar/engine/vitto-engine.iife.js ~35KB IIFE bundle
Dev harness "vitto animation/vitto-dev-harness.html" UI shell (zero engine code)
NX project libs/libs-anim/vitto-avatar/project.json Has bundle-engine target
Moods libs/libs-anim/vitto-avatar/src/lib/engine/moods.ts All 14 mood definitions
Renderer libs/libs-anim/vitto-avatar/src/lib/engine/renderer.ts Core render loop + transitions
Drawing libs/libs-anim/vitto-avatar/src/lib/engine/drawing/ background.ts, body.ts, face.ts, head.ts, effects.ts, shapes.ts
Behaviors libs/libs-anim/vitto-avatar/src/lib/engine/behaviors/ breathing.ts, saccades.ts
Types libs/libs-anim/vitto-avatar/src/lib/engine/types.ts MoodProperties, AnimState, DrawContext, RendererConfig
Math utils libs/libs-anim/vitto-avatar/src/lib/engine/math.ts lerp, clamp, easeInOutCubic, easeOutBack
Angular component libs/libs-anim/vitto-avatar/src/lib/components/vitto-avatar/ .ts, .html, .scss
Mood service libs/libs-anim/vitto-avatar/src/lib/services/vitto-mood.service.ts Signal-based mood management

Engine Architecture (for context)

VittoRenderer (renderer.ts)
  ├── constructor(canvas) → starts RAF loop
  ├── setMood(key) → begins interpolated transition
  ├── render(timestamp) → per-frame pipeline:
  │     ├── updateTransition(dt) → cubic ease interpolation across 30+ properties
  │     ├── updateBlink(dt) → periodic blink cycle
  │     ├── updateBreathing(state, dt) → sinusoidal breath offset
  │     ├── updateSaccades(state, dt) → micro eye movements
  │     └── Draw pipeline:
  │           ├── drawBackground(dc) → gradient from mood bg color
  │           ├── drawShadow(dc) → ellipse shadow under body
  │           ├── drawTail(dc) → animated bezier tail
  │           ├── drawBody(dc) → oval body with breathing
  │           └── drawHead(dc) → orchestrates:
  │                 ├── drawEyes, drawNose, drawMouth
  │                 ├── drawWhiskers, drawBlush, drawBrows
  │                 └── drawParticles (sparkles, hearts, ?, !, zzz, sweat)
  ├── updateMousePosition(x, y) → gaze tracking
  ├── getState() → read-only state snapshot (dev tooling)
  ├── overrideProperty(key, value) → manual property override (dev tooling)
  └── destroy() → cleanup RAF + resources

14 Moods Quick Reference

Mood Key Visual Features
neutral Round eyes, slight smile, golden bg
focused Narrow eyes, tight mouth, cool blue bg
cheerful Happy squinted eyes, big smile, blush, warm bg
empathetic Soft eyes, gentle smile, purple bg
curious Wide eyes, raised brow, tilted head, teal bg
analytical Narrow focused eyes, neutral mouth, steel bg
celebratory Star eyes, huge smile, sparkles + bounce, pink bg
apologetic Downcast eyes, frown, sweat drops, muted bg
inspired Wide bright eyes, open mouth, sparkles, bright bg
urgent Very wide eyes, open mouth, exclamation marks, orange bg
playful Asymmetric brows, sly smile, bouncy body, fun bg
confused Uneven eyes, wavy mouth, question marks, muted bg
proud Confident eyes, smirk, chin up, warm golden bg
sleepy Half-closed eyes, flat mouth, zzz particles, dark bg