diff --git a/web/package-lock.json b/web/package-lock.json index 76b17d6..63fe7b1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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", diff --git a/web/package.json b/web/package.json index b93b10e..c9f8f41 100644 --- a/web/package.json +++ b/web/package.json @@ -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" } diff --git a/web/src/agentation-overlay.ts b/web/src/agentation-overlay.ts new file mode 100644 index 0000000..f632d76 --- /dev/null +++ b/web/src/agentation-overlay.ts @@ -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)['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, + }) + ) +} diff --git a/web/src/app.ts b/web/src/app.ts index 99d0e72..c0f945a 100644 --- a/web/src/app.ts +++ b/web/src/app.ts @@ -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