a11y/ux: modals close on Escape (0.75.0)

- the agent ClassModal and the booth 'instance created' modal could only be
  dismissed by clicking the backdrop or the X — no Escape, a standard
  keyboard/UX expectation
- add a keydown(Escape) -> close to both (ClassModal listens while mounted;
  the booth modal attaches the listener only while newInst is set)
- verified live: agent modal opens then closes on Escape
This commit is contained in:
artheru 2026-06-10 11:05:16 +08:00
parent 8542a98500
commit 9636545fb7
4 changed files with 10 additions and 3 deletions

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0e0a14" />
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
<meta name="version" content="0.74.0-lean-dep" />
<meta name="version" content="0.75.0-esc" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@ -1,6 +1,6 @@
{
"name": "amerc-site",
"version": "0.74.0-lean-dep",
"version": "0.75.0-esc",
"private": true,
"type": "module",
"scripts": {

View File

@ -105,6 +105,7 @@ function ClassModal({ id, auth, onClose }) {
const [err, setErr] = useState('');
const load = useCallback(() => api(`/agents/classes/${id}`).then(setData).catch((e) => setErr(e.message)), [id]);
useEffect(() => { load(); }, [load]);
useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]);
const subscribe = async () => { try { const r = await api(`/agents/classes/${id}/subscribe`, { method: 'POST' }); setSub(r.token); } catch (e) { setErr(e.message); } };
if (!data) return null;
const c = data.class;
@ -232,6 +233,12 @@ export function BoothDashboard({ auth, setRoute }) {
const [form, setForm] = useState({ name: '', tags: '', description: '', kind: 'codex', command: 'codex --yolo', visibility: 'public' });
const [newInst, setNewInst] = useState(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!newInst) return;
const onKey = (e) => { if (e.key === 'Escape') setNewInst(null); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [newInst]);
const load = useCallback(async () => {
setErr('');

View File

@ -10,7 +10,7 @@ import { api } from './api.js';
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
const RELEASE = '0.74.0-lean-dep';
const RELEASE = '0.75.0-esc';
const NAV = [
{ id: 'home', label: 'Tavern' },