diff --git a/index.html b/index.html index 212261c..f207281 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ @media (prefers-reduced-motion: reduce) { .app-loading-gem { animation: none; transform: rotate(45deg); } } - + diff --git a/package.json b/package.json index 516e840..9f0a157 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.89.0-changelog", + "version": "0.90.0-agenturls", "private": true, "type": "module", "scripts": { diff --git a/src/AgentPlatform.jsx b/src/AgentPlatform.jsx index 2040120..9488232 100644 --- a/src/AgentPlatform.jsx +++ b/src/AgentPlatform.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { api } from './api.js'; import { copyToast } from './toast.js'; @@ -27,6 +27,36 @@ export function AgentBrowse({ auth, setRoute }) { }, [tag, q]); useEffect(() => { load(); }, [load]); + // shareable agent URLs: #/agents/ opens that agent's modal (bookmark/share/refresh-safe) + const didInit = useRef(false); + const openClass = useCallback((c) => { + setOpen(c.id); + if (c.slug) window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/agents/${c.slug}`); + }, []); + const closeClass = useCallback(() => { + setOpen(null); + if (window.location.hash !== '#/agents') window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/agents`); + load(); + }, [load]); + useEffect(() => { + if (didInit.current || !loaded) return; + didInit.current = true; + const slug = window.location.hash.replace(/^#\/?/, '').split('/')[1]; + if (slug) { const hit = classes.find((x) => x.slug === slug); if (hit) setOpen(hit.id); } + }, [loaded, classes]); + useEffect(() => { + const onHash = () => { + const parts = window.location.hash.replace(/^#\/?/, '').split('/'); + if (parts[0] !== 'agents') return; + if (!parts[1]) { setOpen(null); return; } + const hit = classes.find((x) => x.slug === parts[1]); + if (hit) setOpen(hit.id); + }; + window.addEventListener('hashchange', onHash); + window.addEventListener('popstate', onHash); + return () => { window.removeEventListener('hashchange', onHash); window.removeEventListener('popstate', onHash); }; + }, [classes]); + return ( @@ -52,7 +82,7 @@ export function AgentBrowse({ auth, setRoute }) { ))} {loaded && classes.map((c) => ( - setOpen(c.id)}> + openClass(c)}> {classEmoji(c)} {c.name}{c.kind} @@ -93,7 +123,7 @@ export function AgentBrowse({ auth, setRoute }) { )} - {open && { setOpen(null); load(); }} />} + {open && } ); } diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index 3d84d8a..09a326d 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -10,7 +10,7 @@ import { api } from './api.js'; const Menu = ({ size = 20 }) => (); const X = ({ size = 20 }) => (); -const RELEASE = '0.89.0-changelog'; +const RELEASE = '0.90.0-agenturls'; const NAV = [ { id: 'home', label: 'Tavern' }, @@ -27,8 +27,9 @@ const ROUTE_TITLES = { function useHashRoute() { const normalize = () => { - const raw = window.location.hash.replace(/^#\/?/, ''); - return VALID_ROUTES.includes(raw) ? raw : 'home'; + // first path segment is the route; the rest (e.g. #/agents/) is a sub-path + const base = window.location.hash.replace(/^#\/?/, '').split('/')[0]; + return VALID_ROUTES.includes(base) ? base : 'home'; }; const [route, setRouteState] = useState(normalize); useEffect(() => {