feat(web): add Agentation feedback overlay for UI annotation

Mounts the Agentation React component (v3) as a thin overlay on the vanilla TS
app via a React root shim. The toolbar appears bottom-right and lets users click
any element, annotate it, and generate structured markdown for AI agent feedback.
Submissions are persisted in localStorage and POSTed to /api/ui-feedback when
the API is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-21 08:26:00 -04:00
parent dcdca5147f
commit 72addb2089
4 changed files with 133 additions and 1 deletions

74
web/package-lock.json generated
View file

@ -8,6 +8,11 @@
"name": "acb-web",
"version": "0.1.0",
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"agentation": "^3.0.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"typescript": "^5.0.0",
"vite": "^5.0.0"
}
@ -711,6 +716,48 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/agentation": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/agentation/-/agentation-3.0.2.tgz",
"integrity": "sha512-iGzBxFVTuZEIKzLY6AExSLAQH6i6SwxV4pAu7v7m3X6bInZ7qlZXAwrEqyc4+EfP4gM7z2RXBF6SF4DeH0f2lA==",
"dev": true,
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@ -815,6 +862,27 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
"dev": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.5"
}
},
"node_modules/rollup": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
@ -859,6 +927,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"dev": true
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",

View file

@ -8,6 +8,11 @@
"preview": "vite preview"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"agentation": "^3.0.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"typescript": "^5.0.0",
"vite": "^5.0.0"
}

View file

@ -0,0 +1,51 @@
// agentation-overlay.ts
// Mounts the Agentation feedback toolbar as a React overlay on the vanilla TS app.
// React is only used for this thin shim — the rest of the app remains vanilla TS.
//
// Agentation lets users click any element, annotate it, and generate structured
// markdown output that describes the UI change in terms an AI agent can act on.
// Submissions are stored in localStorage and optionally POSTed to /api/feedback
// when the backend API is available.
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Agentation } from 'agentation'
import type { Annotation } from 'agentation'
const STORAGE_KEY = 'acb:agentation:feedback'
const MAX_STORED = 50
function handleSubmit(markdown: string, annotations: Annotation[]): void {
console.log('[agentation] Feedback submitted')
// Persist locally
const existing: Array<{ markdown: string; annotations: Annotation[]; submittedAt: number }> =
JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]')
existing.push({ markdown, annotations, submittedAt: Date.now() })
localStorage.setItem(STORAGE_KEY, JSON.stringify(existing.slice(-MAX_STORED)))
// POST to the API if available (non-blocking, best-effort)
const apiBase = (window as unknown as Record<string, string>)['ACB_API_BASE'] ?? '/api'
fetch(`${apiBase}/ui-feedback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ markdown, annotations, submitted_at: new Date().toISOString() }),
}).catch(() => {
// API not available yet — localStorage fallback is sufficient
})
}
export function initAgentation(): void {
const container = document.createElement('div')
container.id = 'agentation-root'
document.body.appendChild(container)
const root = ReactDOM.createRoot(container)
// Cast through ElementType so createElement accepts the props without JSX support
root.render(
React.createElement(Agentation as React.ElementType, {
onSubmit: handleSubmit,
copyToClipboard: true,
})
)
}

View file

@ -1,5 +1,6 @@
// Main SPA entry point with routing
import { router } from './router';
import { initAgentation } from './agentation-overlay';
import { renderHomePage } from './pages/home';
import { renderLeaderboardPage } from './pages/leaderboard';
import { renderMatchesPage } from './pages/matches';
@ -781,10 +782,11 @@ function renderNotFoundPage(): void {
`;
}
// Start the router
// Start the router and mount the Agentation feedback overlay
document.addEventListener('DOMContentLoaded', () => {
updateActiveNavLink();
router.start();
initAgentation();
});
// Update nav on initial load