import { Fragment, type VNode } from "preact" import { useEffect, useMemo, useRef, useState } from "preact/hooks" declare global { interface Window { __POND_DASHBOARD?: { publicHost: string } } } interface Bootstrap { publicHost: string } function readBootstrap(): Bootstrap { if (typeof window !== "undefined" && window.__POND_DASHBOARD) { return { publicHost: window.__POND_DASHBOARD.publicHost, } } return { publicHost: "" } } const TOKEN_KEY = "pond-dashboard-token" function loadToken(): string | null { try { return window.localStorage.getItem(TOKEN_KEY) } catch { return null } } function storeToken(t: string) { try { window.localStorage.setItem(TOKEN_KEY, t) } catch {} } function clearToken() { try { window.localStorage.removeItem(TOKEN_KEY) } catch {} } const VIEW_KEY = "pond-dashboard-view" type ProjectView = "grid" | "list" function loadView(): ProjectView { try { return window.localStorage.getItem(VIEW_KEY) === "list" ? "list" : "grid" } catch { return "grid" } } function storeView(v: ProjectView) { try { window.localStorage.setItem(VIEW_KEY, v) } catch {} } interface Me { id: string username: string isAdmin: boolean } interface DeployRow { deployId: string url: string apiUrl: string createdAt: string updatedAt: string claimedAt?: string ownerId?: string | null anonymous: boolean terminatesAt?: string expiresAt?: string terminated?: boolean title?: string description?: string isPublic?: boolean publicInspect?: boolean // Custom subdomains added via `pond domains add`. First entry is the // preferred display URL; the hash `url` is shown as secondary. domains?: string[] } function primaryUrl(d: DeployRow): string { return d.domains && d.domains.length > 0 ? d.domains[0] : d.url } function authHeaders(token: string): Record { return { authorization: `Bearer ${token}` } } async function fetchMe(token: string): Promise<{ me: Me } | { error: string; status: number }> { const r = await fetch("/api/users/me", { headers: authHeaders(token) }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "auth failed", status: r.status } // /api/users/me returns { userId, username, isAdmin } — map userId → id so // ownership checks (me.id === ownerId) work. Passing the raw body through left // me.id undefined, so every deploy looked SHARED and "Owned by you" read 0. const raw = (await r.json()) as { userId: string; username: string; isAdmin: boolean } return { me: { id: raw.userId, username: raw.username, isAdmin: raw.isAdmin } } } async function fetchDeploys(token: string): Promise<{ deploys: DeployRow[] } | { error: string }> { const r = await fetch("/api/deploys", { headers: authHeaders(token) }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "list failed" } return r.json() } async function rotateClaim(token: string, deployId: string): Promise<{ claimToken: string } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/rotate-claim-token`, { method: "POST", headers: authHeaders(token), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "rotate failed" } return r.json() } async function rotateUserToken(token: string): Promise<{ token: string } | { error: string }> { const r = await fetch("/api/users/me/rotate-token", { method: "POST", headers: authHeaders(token), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "rotate failed" } return r.json() } async function deleteDeploy(token: string, deployId: string): Promise { const r = await fetch(`/api/deploys/${deployId}`, { method: "DELETE", headers: authHeaders(token) }) return r.ok } interface InspectData { deployId: string dbBytes: number dbOpenError?: string tableCount: number totalRows: number tables: Array<{ name: string; rowCount: number; columns: number }> sourceFileCount: number } async function fetchInspect(token: string, deployId: string): Promise { const r = await fetch(`/api/deploys/${deployId}/inspect`, { headers: authHeaders(token) }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "inspect failed" } return r.json() } interface LogEntry { ts?: string level?: string message?: string [k: string]: unknown } async function fetchLogs(token: string, deployId: string): Promise<{ entries: LogEntry[] } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/logs?limit=200`, { headers: authHeaders(token) }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "logs failed" } return r.json() } async function fetchEnv( token: string, deployId: string, ): Promise<{ entries: Record } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/env`, { headers: authHeaders(token) }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "env load failed" } return r.json() } async function saveEnv( token: string, deployId: string, partial: Record, ): Promise<{ entries: Record } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/env`, { method: "PUT", headers: { "content-type": "application/json", ...authHeaders(token) }, body: JSON.stringify({ entries: partial }), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "save failed" } return r.json() } async function deleteEnvKey( token: string, deployId: string, key: string, ): Promise<{ entries: Record } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/env/${encodeURIComponent(key)}`, { method: "DELETE", headers: authHeaders(token), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "delete failed" } return r.json() } async function setVisibility( token: string, deployId: string, publicInspect: boolean, ): Promise<{ publicInspect: boolean } | { error: string }> { const r = await fetch(`/api/deploys/${deployId}/visibility`, { method: "PATCH", headers: { "content-type": "application/json", ...authHeaders(token) }, body: JSON.stringify({ publicInspect }), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "visibility update failed" } return r.json() } const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/ interface TableSample { name: string columns: Array<{ name: string; type: string }> rows: Array> rowCount: number orderBy: string } async function fetchTableSample( token: string, deployId: string, table: string, limit = 10, ): Promise { const r = await fetch(`/api/deploys/${deployId}/inspect/table/${encodeURIComponent(table)}?limit=${limit}`, { headers: authHeaders(token), }) if (!r.ok) return { error: (await r.json().catch(() => ({}))).error ?? "table sample failed" } return r.json() } function parseHashRoute(hash: string): { deployId: string | null } { const m = hash.match(/^#d\/([a-f0-9]+)/i) return { deployId: m ? m[1] : null } } function useHashRoute() { const [route, setRoute] = useState(() => typeof window !== "undefined" ? parseHashRoute(window.location.hash) : { deployId: null }, ) useEffect(() => { const onChange = () => setRoute(parseHashRoute(window.location.hash)) window.addEventListener("hashchange", onChange) return () => window.removeEventListener("hashchange", onChange) }, []) return route } function navigateTo(hash: string) { if (typeof window === "undefined") return window.location.hash = hash } export function App() { const bootstrap = useMemo(readBootstrap, []) const [token, setToken] = useState(() => loadToken()) const [me, setMe] = useState(null) const [authError, setAuthError] = useState(null) useEffect(() => { if (!token) { setMe(null) return } let cancelled = false void fetchMe(token).then((res) => { if (cancelled) return if ("error" in res) { setAuthError(`${res.error} (${res.status})`) if (res.status === 401 || res.status === 403) { clearToken() setToken(null) } } else { setMe(res.me) setAuthError(null) } }) return () => { cancelled = true } }, [token]) if (!token || !me) return ( { storeToken(t) setToken(t) }} error={authError} /> ) return ( { clearToken() setToken(null) setMe(null) }} /> ) } function SignIn({ onSubmit, error }: { onSubmit: (t: string) => void; error: string | null }) { const [t, setT] = useState("") return (
{ e.preventDefault() if (t.trim()) onSubmit(t.trim()) }} >

Pond

Dashboard

Paste your account API token.{" "} pond login --api … writes it to{" "} ~/.pond/credentials.json.

{error ? (
{error}
) : null} setT((e.target as HTMLInputElement).value)} />
) } function Workspace({ me, token, bootstrap, onSignOut, }: { me: Me token: string bootstrap: Bootstrap onSignOut: () => void }) { const route = useHashRoute() const [deploys, setDeploys] = useState([]) const [loading, setLoading] = useState(true) const [err, setErr] = useState(null) const [reloadKey, setReloadKey] = useState(0) const [flash, setFlash] = useState(null) const [view, setView] = useState(() => loadView()) function chooseView(v: ProjectView) { setView(v) storeView(v) } useEffect(() => { let cancelled = false setLoading(true) void fetchDeploys(token).then((res) => { if (cancelled) return if ("error" in res) setErr(res.error) else { setDeploys(res.deploys) setErr(null) } setLoading(false) }) return () => { cancelled = true } }, [token, reloadKey]) async function handleRotateClaim(d: DeployRow) { const res = await rotateClaim(token, d.deployId) if ("error" in res) { setErr(res.error) return } await navigator.clipboard.writeText(res.claimToken).catch(() => {}) setFlash(`New claim token copied to clipboard for ${d.deployId.slice(0, 8)}…`) setTimeout(() => setFlash(null), 4000) } async function handleRotateUserToken() { if (!confirm("Rotate your account API token? You'll lose access until you save the new one.")) return const res = await rotateUserToken(token) if ("error" in res) { setErr(res.error) return } await navigator.clipboard.writeText(res.token).catch(() => {}) setFlash("New API token copied. The old one stays valid for 5 minutes.") storeToken(res.token) setTimeout(() => setFlash(null), 6000) } async function handleDelete(d: DeployRow) { if (!confirm(`Delete deploy ${d.deployId}? This is irreversible.`)) return const ok = await deleteDeploy(token, d.deployId) if (!ok) { setErr("delete failed") return } setReloadKey((k) => k + 1) } const stats = useMemo(() => { const live = deploys.filter((d) => !d.terminated && !d.anonymous).length const anon = deploys.filter((d) => d.anonymous && !d.terminated).length const mine = deploys.filter((d) => d.ownerId === me.id).length return { total: deploys.length, live, anon, mine } }, [deploys, me.id]) const activeDeploy = useMemo( () => (route.deployId ? (deploys.find((d) => d.deployId.startsWith(route.deployId!)) ?? null) : null), [route.deployId, deploys], ) if (route.deployId && !loading) { if (activeDeploy) { return ( setErr(null)} onBack={() => navigateTo("")} onRotateClaim={() => void handleRotateClaim(activeDeploy)} onDelete={async () => { await handleDelete(activeDeploy) navigateTo("") }} onSignOut={onSignOut} /> ) } // deployId in URL but no matching deploy loaded — fall through to list } return (

pond Dashboard

{me.username} {me.isAdmin ? ( admin ) : null} · {deploys.length} {deploys.length === 1 ? "deploy" : "deploys"}

{flash ? (
{flash}
) : null} {err ? (
{err}
) : null}
0} muted={stats.anon === 0} />

Your projects

{!loading && deploys.length > 0 ? (

{deploys.length} {deploys.length === 1 ? "result" : "results"}

) : null}
{loading ? (
Loading deploys…
) : deploys.length === 0 ? (

No projects yet.

Run{" "} pond new my-app && cd my-app && pond deploy

) : view === "grid" ? (
{deploys.map((d) => ( void handleRotateClaim(d)} onDelete={() => void handleDelete(d)} /> ))}
) : (
{deploys.map((d) => ( void handleRotateClaim(d)} onDelete={() => void handleDelete(d)} /> ))}
)}
) } function KpiTile({ label, value, accent = false, muted = false, }: { label: string value: number accent?: boolean muted?: boolean }) { return (

{label}

{value}

) } function ViewToggle({ view, onChange }: { view: ProjectView; onChange: (v: ProjectView) => void }) { const btn = (v: ProjectView, label: string, icon: VNode) => ( ) const gridIcon = ( ) const listIcon = ( ) return (
{btn("grid", "Grid", gridIcon)} {btn("list", "List", listIcon)}
) } function DeployCard({ d, me, token, onRotateClaim, onDelete, }: { d: DeployRow me: Me token: string onRotateClaim: () => void onDelete: () => void }) { const isOwner = d.ownerId === me.id const age = humanAge(d.createdAt) const ideHref = `/ide/${d.deployId}#bearer=${encodeURIComponent(token)}` const heading = d.title?.trim() || "Untitled project" const shortId = d.deployId.slice(0, 8) return ( ) } // Day-by-day app-creation activity over the trailing window, bucketed from each // deploy's createdAt. Pure client-side — no extra API call. function ActivityChart({ deploys, className = "" }: { deploys: DeployRow[]; className?: string }) { const DAYS = 30 const { bars, max, total, firstLabel, midLabel } = useMemo(() => { const now = new Date() const startMs = new Date(now.getFullYear(), now.getMonth(), now.getDate() - (DAYS - 1)).getTime() const counts = new Array(DAYS).fill(0) for (const d of deploys) { const t = Date.parse(d.createdAt) if (Number.isNaN(t)) continue const dt = new Date(t) const dayMs = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()).getTime() const idx = Math.round((dayMs - startMs) / 86_400_000) if (idx >= 0 && idx < DAYS) counts[idx]++ } const bars = counts.map((c, i) => ({ c, date: new Date(startMs + i * 86_400_000) })) const fmt = (d: Date) => d.toLocaleDateString(undefined, { month: "short", day: "numeric" }) return { bars, max: Math.max(1, ...counts), total: counts.reduce((a, b) => a + b, 0), firstLabel: fmt(bars[0].date), midLabel: fmt(bars[Math.floor(DAYS / 2)].date), } }, [deploys]) return (

Activity

{total} {total === 1 ? "app" : "apps"} created · last {DAYS} days

{bars.map((b, i) => { const h = b.c === 0 ? 2 : Math.max(6, Math.round((b.c / max) * 100)) const label = `${b.date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", })}: ${b.c} ${b.c === 1 ? "app" : "apps"}` return (
0 ? "bg-emerald-500/55 hover:bg-emerald-400" : "bg-zinc-800/70 hover:bg-zinc-700" }`} style={`height:${h}%`} /> ) })}
{firstLabel} {midLabel} Today
) } // Lazy, scaled live preview of a deploy's page. The iframe src is only set once // the card scrolls near the viewport (avoids loading every deploy at once); a // fallback tile shows underneath for apps that refuse framing or haven't loaded. function LiveThumb({ url, title, fallback }: { url: string; title: string; fallback: string }) { const ref = useRef(null) const [inView, setInView] = useState(false) const [loaded, setLoaded] = useState(false) useEffect(() => { if (inView) return const el = ref.current if (!el) return if (typeof IntersectionObserver === "undefined") { setInView(true) return } const io = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting)) { setInView(true) io.disconnect() } }, { rootMargin: "300px" }, ) io.observe(el) return () => io.disconnect() }, [inView]) return (
{fallback}
{inView ? (