// React port of main.ts's `parseRoute`/`dispatch` pair (issue #240). Same two // routes, same hash shape (`#/`, `#/attach/:id`) — kept so a deep link or a // bookmark from the vanilla shell still resolves. import { useCallback, useEffect, useState } from "react"; export type Route = { kind: "catalog" } | { kind: "attach"; sessionId: string }; function parseHash(hash: string): Route { const raw = hash.replace(/^#/, ""); if (raw === "" || raw === "/") return { kind: "catalog" }; const m = raw.match(/^\/attach\/([^/?#]+)$/); if (m) return { kind: "attach", sessionId: decodeURIComponent(m[1]) }; return { kind: "catalog" }; } function routeToHash(route: Route): string { return route.kind === "catalog" ? "#/" : `#/attach/${encodeURIComponent(route.sessionId)}`; } export interface UseHashRouterResult { route: Route; navigate(route: Route): void; } export function useHashRouter(): UseHashRouterResult { const [route, setRoute] = useState(() => parseHash(location.hash)); useEffect(() => { const onHashChange = () => setRoute(parseHash(location.hash)); window.addEventListener("hashchange", onHashChange); return () => window.removeEventListener("hashchange", onHashChange); }, []); const navigate = useCallback((next: Route) => { const hash = routeToHash(next); // Setting an unchanged hash never fires `hashchange`, so the state update // that would otherwise ride on that event has to happen here instead. if (location.hash === hash) { setRoute(next); } else { location.hash = hash; } }, []); return { route, navigate }; }