From 399c7958ff6fa50365b49e5a2c2025f9e9426866 Mon Sep 17 00:00:00 2001 From: "Engineering (agent-engineering)" Date: Tue, 19 May 2026 19:11:38 -0700 Subject: [PATCH] feat(frontend): BeOS icons as agent activity indicators, remove background scatter --- src/frontend/FarmCanvas.jsx | 125 ++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/src/frontend/FarmCanvas.jsx b/src/frontend/FarmCanvas.jsx index 0a0feba..f0297d0 100644 --- a/src/frontend/FarmCanvas.jsx +++ b/src/frontend/FarmCanvas.jsx @@ -46,71 +46,59 @@ function spawnSparkle(x, y) { if (sparkles.length > 60) sparkles.shift(); } -// ── BeOS tile background system ────────────────────────────────── -const beosTiles = []; +// ── BeOS activity icons ────────────────────────────────────────── +const activityIcons = {}; +const ACTIVITY_ICONS = [ + 'dispatch', 'code', 'build', 'review', 'research', 'analyze', + 'write', 'security', 'monitor', 'memory', 'coach', 'heartbeat', + 'mail', 'collaborate', 'present', 'vault', 'think', 'idle', + 'blocked', 'document', 'terminal', 'package', 'network', + 'search', 'design', 'data', +]; -function loadBeosTiles() { - const tileNames = [ - 'bell', 'email', 'clock', 'folder-open', 'folder-drag-accept', - 'audio-volume-high', 'audio-volume-medium', 'audio-volume-muted', - 'bluetooth-active', 'firefox08icon', 'firebirdicon', - 'accessories-calculator', 'accessories-text-editor', - 'GameBoy_1_32x32x8', 'GameBoy 2_1_32x32x8', - 'alarm-clock-icon', 'config-users', 'Cinder-Block-icon', - 'appointment-missed', 'appointment-soon', - ]; - for (const name of tileNames) { +function loadActivityIcons() { + ACTIVITY_ICONS.forEach(name => { const img = new Image(); - img.src = `/office/beos-tiles/${name}.png`; - beosTiles.push(img); - } + img.src = `/office/beos-icons/${name}.png`; + activityIcons[name] = img; + }); } -// Track placed BeOS tile positions (computed once) -let beosPlacements = null; -let beosPlacementsComputed = false; +// Map agent state → BeOS icon name +function getActivityIcon(agent) { + const activity = agent.activity || 'idle'; -function computeBeosPlacements() { - if (beosPlacementsComputed) return; - beosPlacementsComputed = true; - beosPlacements = []; - // Sparse grid: every 4 tiles horizontally, every 3 vertically - const gridSpacingX = 4; - const gridSpacingY = 3; - for (let row = 0; row < ROWS; row += gridSpacingY) { - for (let col = 0; col < COLS; col += gridSpacingX) { - const px = col * TILE_SIZE; - const py = row * TILE_SIZE; - // Skip dirt path rows - const onHPath = (py >= 48 && py < 64) || (py >= 224 && py < 240) || (py >= 544 && py < 560); - const onVPath = (col >= 6 && col < 7) || (col >= 13 && col < 14) || (col >= 22 && col < 23); - if (onHPath || onVPath) continue; - // Skip building areas - let inBuilding = false; - for (const b of BUILDINGS) { - if (col >= b.col && col < b.col + b.w && row >= b.row && row < b.row + b.h) { - inBuilding = true; - break; - } - } - if (inBuilding) continue; - const tileIdx = Math.floor(Math.random() * beosTiles.length); - beosPlacements.push({ px, py, tileIdx }); - } - } -} + if (activity === 'idle' || activity === 'offline') return 'idle'; + if (activity === 'blocked') return 'blocked'; + if (activity === 'thinking') return 'think'; -function drawBeosTiles(ctx) { - if (!beosPlacementsComputed) computeBeosPlacements(); - if (!beosPlacements) return; - ctx.globalAlpha = 0.3; - for (const p of beosPlacements) { - const img = beosTiles[p.tileIdx]; - if (img && img.complete && img.naturalWidth > 0) { - ctx.drawImage(img, p.px, p.py, TILE_SIZE, TILE_SIZE); - } - } - ctx.globalAlpha = 1; + // activity === 'working' → check currentTask keywords + const task = (agent.currentTask || '').toLowerCase(); + + if (/\b(dispatch|rout)\b/.test(task)) return 'dispatch'; + if (/\b(code|implement|build|pr)\b/.test(task)) return /\bbuild\b/.test(task) ? 'build' : 'code'; + if (/\b(review|audit)\b/.test(task)) return 'review'; + if (/\bresearch\b/.test(task)) return 'research'; + if (/\bsearch\b/.test(task)) return 'search'; + if (/\banalyz\b/.test(task)) return 'analyze'; + if (/\b(writ|draft|content|blog)\b/.test(task)) return 'write'; + if (/\bsecurit|complianc\b/.test(task)) return 'security'; + if (/\bmonitor\b/.test(task)) return 'monitor'; + if (/\bhealth|check\b/.test(task)) return 'heartbeat'; + if (/\b(memory|index|vault|note)\b/.test(task)) return /\bvault\b/.test(task) ? 'vault' : 'memory'; + if (/\b(coach|learn|improv)\b/.test(task)) return 'coach'; + if (/\b(mail|message|communicat)\b/.test(task)) return 'mail'; + if (/\b(collaborat|pair|meeting)\b/.test(task)) return 'collaborate'; + if (/\b(present|slide|deck)\b/.test(task)) return 'present'; + if (/\b(document|spec|readme)\b/.test(task)) return 'document'; + if (/\b(terminal|cli|script)\b/.test(task)) return 'terminal'; + if (/\b(deploy|package|release)\b/.test(task)) return 'package'; + if (/\b(network|connect|api)\b/.test(task)) return 'network'; + if (/\b(design|layout|ui|css)\b/.test(task)) return 'design'; + if (/\b(data|stats)\b/.test(task)) return 'data'; + + // Default: code icon for working agents + return 'code'; } // ── Draw helpers ────────────────────────────────────────────────── @@ -401,6 +389,16 @@ function drawAgent(ctx, agent, time) { ctx.restore(); + // ── Activity icon above agent ────────────────────────────── + const iconName = getActivityIcon(agent); + const iconImg = activityIcons[iconName]; + if (iconImg && iconImg.complete && iconImg.naturalWidth > 0) { + ctx.save(); + ctx.globalAlpha = 1; + ctx.drawImage(iconImg, px - 12, py + yOffset - 60, 24, 24); + ctx.restore(); + } + // Blocked: red tint overlay if (activity === 'blocked') { ctx.fillStyle = 'rgba(255,50,50,0.3)'; @@ -519,9 +517,9 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) { } }, [onAgentClick]); - // Load BeOS tiles on mount + // Load activity icons on mount useEffect(() => { - loadBeosTiles(); + loadActivityIcons(); }, []); // Animation loop @@ -538,7 +536,6 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) { ctx.clearRect(0, 0, CANVAS_W, CANVAS_H); drawGrass(ctx); - drawBeosTiles(ctx); drawDirtPaths(ctx); for (const b of BUILDINGS) drawBuilding(ctx, b); drawCampfire(ctx, time); @@ -643,8 +640,8 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) { display: 'block', imageRendering: 'pixelated', cursor: hoveredAgent ? 'pointer' : 'default', - maxWidth: '100%', - maxHeight: '100%', + width: '100%', + height: '100%', objectFit: 'contain', }} />