From 83774a627daa88533817258485884d879de6220d Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 3 Aug 2026 06:53:24 +0000 Subject: [PATCH] chore(dashboard): drop the Moshpit TLDs panel, point at the pit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was a second control panel over data this app does not own. Every resolver, the DNS bridge and TronBrowser ask `pit.moshcode.sh` — the moshcode registry is the one being served, and these tables are its ancestor. So the panel edited a copy: nested alias, exemption and key-pin forms, one row per ending, with no filter and nowhere to see what a name actually publishes. The pit has a DNS Records tab now, with the records themselves rather than a pointer, so this is not a removal with nothing behind it. The tab strip links straight out to it, and `/dashboard/tlds` — a real bookmarkable URL — redirects rather than quietly opening the Domains tab and looking like a rotted bookmark. The /api/moshpit endpoints stay: PitSearch on the public /pit page claims endings through them, and they are a public surface something outside this repo may be calling. Co-Authored-By: Claude Opus 5 (1M context) --- app/dashboard/[[...tab]]/page.tsx | 29 +- components/MoshpitTlds.tsx | 499 ------------------------------ 2 files changed, 22 insertions(+), 506 deletions(-) delete mode 100644 components/MoshpitTlds.tsx diff --git a/app/dashboard/[[...tab]]/page.tsx b/app/dashboard/[[...tab]]/page.tsx index 1348d47..64a811e 100644 --- a/app/dashboard/[[...tab]]/page.tsx +++ b/app/dashboard/[[...tab]]/page.tsx @@ -8,13 +8,23 @@ import { filterSignupsByStatus, type WaitlistStatus, } from "@/lib/waitlist-filter"; -import MoshpitTlds from "@/components/MoshpitTlds"; - // Tab values double as URL slugs: /dashboard/ (the default "page" tab lives at // bare /dashboard). Keep this in sync with the tab buttons below. -const TABS = ["page", "videos", "waitlist", "auctions", "webhooks", "affiliates", "dns", "tlds"] as const; +const TABS = ["page", "videos", "waitlist", "auctions", "webhooks", "affiliates", "dns"] as const; type Tab = (typeof TABS)[number]; +/** + * Where the Moshpit namespace is actually operated. + * + * The endings and names live in the moshcode registry — `pit.moshcode.sh` is + * what every resolver, the DNS bridge and TronBrowser ask, and this app's copy + * of the tables was never the one being served. The dashboard tab that edited + * them from here was therefore a second, worse control panel over data it did + * not own, and it is gone; `/dashboard/tlds` sends you to the real one rather + * than silently opening a different tab. + */ +const PIT_RECORDS_URL = "https://app.moshcode.sh/pit/records"; + type Org = { id: string; name: string }; type Team = { id: string; name: string; org_id: string; org_name: string; role: string }; type Project = { id: string; name: string; team_id: string; team_name: string }; @@ -48,6 +58,13 @@ export default function Dashboard() { const slug = Array.isArray(params?.tab) ? params.tab[0] : (params?.tab as string | undefined); const [tab, setTabState] = useState((TABS as readonly string[]).includes(slug || "") ? (slug as Tab) : "page"); + // /dashboard/tlds was a real, bookmarkable URL. Falling through to the + // Domains tab would look like the bookmark rotting; this says where the + // namespace went. + useEffect(() => { + if (slug === "tlds" && typeof window !== "undefined") window.location.replace(PIT_RECORDS_URL); + }, [slug]); + // Switch tab and reflect it in the address bar (replaceState keeps in-page state // and avoids a server round-trip; the bare /dashboard is the "page" tab). const setTab = (t: Tab) => { @@ -116,12 +133,10 @@ export default function Dashboard() { - + Moshpit DNS ↗ - {tab === "tlds" ? ( - - ) : tab === "dns" ? ( + {tab === "dns" ? ( say(m, false)} onOk={(m) => say(m, true)} /> ) : tab === "affiliates" ? ( say(m, false)} onOk={(m) => say(m, true)} /> diff --git a/components/MoshpitTlds.tsx b/components/MoshpitTlds.tsx deleted file mode 100644 index 1d134db..0000000 --- a/components/MoshpitTlds.tsx +++ /dev/null @@ -1,499 +0,0 @@ -"use client"; - -import { useCallback, useEffect, useState } from "react"; - -/** - * Operator controls for the TLDs you hold. - * - * The alias and exemption APIs already existed but had no UI, which made them - * effectively operator-only-if-you-can-write-curl. Pointing `.agentic` at - * `.agent` is exactly the kind of thing an operator wants to do on a whim and - * undo just as fast, so it belongs on a page. - */ - -type Tld = { - tld: string; - owner_email: string | null; - alias_of: string | null; - created_at: string; -}; - -type PinKind = "tls" | "mtp"; - -type Pin = { - tld: string; - pin: string; - kind: PinKind; - note: string | null; - created_at: string; -}; - -type PinDraft = { - kind: PinKind; - pin: string; - note: string; -}; - -const emptyPinDraft = (): PinDraft => ({ kind: "tls", pin: "", note: "" }); - -const formatCreatedAt = (value: string) => { - const sqliteUtc = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value); - const date = new Date(sqliteUtc ? `${value.replace(" ", "T")}Z` : value); - return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); -}; - -const tldRowId = (tld: string) => `moshpit-tld-${tld}`; -const pinPanelId = (tld: string) => `moshpit-pins-${tld}`; - -export default function MoshpitTlds() { - const [tlds, setTlds] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [note, setNote] = useState(null); - const [busy, setBusy] = useState(null); - - const [claim, setClaim] = useState(""); - const [aliasTarget, setAliasTarget] = useState>({}); - const [exempt, setExempt] = useState>({}); - const [exemptDraft, setExemptDraft] = useState>({}); - const [pins, setPins] = useState>>({}); - const [pinDrafts, setPinDrafts] = useState>>({}); - - const load = useCallback(async () => { - setError(null); - try { - const res = await fetch("/api/moshpit/tlds?mine=1", { cache: "no-store" }); - if (res.status === 401) { - setError("Sign in to manage your TLDs."); - setTlds([]); - return; - } - const json = (await res.json().catch(() => ({}))) as { tlds?: Tld[]; error?: string }; - if (json.error) setError(json.error); - setTlds(json.tlds ?? []); - } catch (e) { - setError(e instanceof Error ? e.message : "Could not load your TLDs"); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - void load(); - }, [load]); - - // Exemptions are per-TLD and only meaningful once an alias is set, so they're - // fetched lazily rather than up front for every name you hold. - const loadExempt = useCallback(async (tld: string) => { - const res = await fetch(`/api/moshpit/tlds/${tld}/exempt`, { cache: "no-store" }); - const json = (await res.json().catch(() => ({}))) as { labels?: string[]; exempt?: string[] }; - setExempt((prev) => ({ ...prev, [tld]: json.labels ?? json.exempt ?? [] })); - }, []); - - const loadPins = useCallback(async (tld: string) => { - const key = `pins:load:${tld}`; - setBusy(key); - setError(null); - try { - const res = await fetch(`/api/moshpit/tlds/${tld}/pins`, { cache: "no-store" }); - const json = (await res.json().catch(() => ({}))) as { pins?: Pin[]; error?: string }; - if (!res.ok) { - setError(json.error || `Could not load key pins (${res.status})`); - return; - } - setPins((prev) => ({ ...prev, [tld]: json.pins ?? [] })); - } catch (e) { - setError(e instanceof Error ? e.message : "Could not load key pins"); - } finally { - setBusy(null); - } - }, []); - - const run = useCallback( - async (key: string, fn: () => Promise, okMessage: string) => { - setBusy(key); - setNote(null); - setError(null); - try { - const res = await fn(); - const json = (await res.json().catch(() => ({}))) as { error?: string }; - if (!res.ok) { - setError(json.error || `Request failed (${res.status})`); - return false; - } - setNote(okMessage); - await load(); - return true; - } catch (e) { - setError(e instanceof Error ? e.message : "Request failed"); - return false; - } finally { - setBusy(null); - } - }, - [load], - ); - - const register = async () => { - const tld = claim.trim().replace(/^\./, ""); - if (!tld) return; - const ok = await run( - "claim", - () => - fetch("/api/moshpit/tlds", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ tld }), - }), - `Registered .${tld}`, - ); - if (ok) setClaim(""); - }; - - const publishPin = async (tld: string) => { - const draft = pinDrafts[tld] ?? emptyPinDraft(); - const pin = draft.pin.trim(); - if (!pin) return; - - const ok = await run( - `pin:add:${tld}`, - () => - fetch(`/api/moshpit/tlds/${tld}/pins`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ pin, kind: draft.kind, note: draft.note.trim() }), - }), - `Published ${draft.kind.toUpperCase()} key for .${tld}`, - ); - if (!ok) return; - - setPinDrafts((prev) => ({ ...prev, [tld]: emptyPinDraft() })); - await loadPins(tld); - }; - - const withdrawPin = async (tld: string, entry: Pin) => { - const confirmed = window.confirm( - `Withdraw this ${entry.kind.toUpperCase()} key from .${tld}? Connections using it may stop working.`, - ); - if (!confirmed) return; - - const ok = await run( - `pin:remove:${tld}:${entry.pin}`, - () => - fetch(`/api/moshpit/tlds/${tld}/pins?pin=${encodeURIComponent(entry.pin)}`, { - method: "DELETE", - }), - `Withdrew ${entry.kind.toUpperCase()} key from .${tld}`, - ); - if (ok) await loadPins(tld); - }; - - if (loading) return

Moshpit TLDs

Loading…

; - - return ( -
-

Moshpit TLDs

-

- The endings you own. Everything under a TLD is yours — claiming .eggs gives you{" "} - scrambled.eggs without registering it separately. -

- -
- setClaim(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && void register()} - placeholder=".yourthing" - aria-label="TLD to claim" - style={{ flex: 1 }} - /> - -
- - {error ?

{error}

: null} - {note ?

{note}

: null} - - {tlds.length === 0 ? ( -

You don't hold any TLDs yet.

- ) : ( -
    - {tlds.map((t) => { - const target = aliasTarget[t.tld] ?? ""; - const labels = exempt[t.tld]; - const publishedPins = pins[t.tld]; - const pinDraft = pinDrafts[t.tld] ?? emptyPinDraft(); - return ( -
  • -
    - .{t.tld} - {t.alias_of ? ( - → points at .{t.alias_of} - ) : ( - stands on its own - )} -
    - -
    - {t.alias_of ? ( - - ) : ( - <> - setAliasTarget((p) => ({ ...p, [t.tld]: e.target.value }))} - placeholder="point at .agent" - aria-label={`Redirect .${t.tld} to`} - /> - - - )} - - - - -
    - - {labels ? ( -
    -

    - Names kept on .{t.tld} even when it redirects - {labels.length === 0 ? " — none yet." : ":"} -

    - {labels.length > 0 ? ( -
      - {labels.map((label) => ( -
    • - {label}.{t.tld}{" "} - -
    • - ))} -
    - ) : null} -
    - setExemptDraft((p) => ({ ...p, [t.tld]: e.target.value }))} - placeholder="label to hold back" - aria-label={`Hold a name back from .${t.tld}'s redirect`} - /> - -
    -
    - ) : null} - - {publishedPins ? ( -
    -
    -

    - {t.alias_of - ? `Pins published directly on .${t.tld} protect held-back names only. Names that follow the redirect use .${t.alias_of}'s pins.` - : "Publish both old and new keys during rotation, then withdraw the old one after deployment."} -

    - {t.alias_of ? ( - - ) : null} - -
    - - {publishedPins.length === 0 ? ( -

    No TLS or MTP keys published yet.

    - ) : ( -
      - {publishedPins.map((entry) => ( -
    • -
      - {entry.kind.toUpperCase()} - {entry.pin} - {formatCreatedAt(entry.created_at)} - -
      - {entry.note ?

      {entry.note}

      : null} -
    • - ))} -
    - )} - -
    - - - setPinDrafts((prev) => ({ - ...prev, - [t.tld]: { ...pinDraft, pin: e.target.value }, - })) - } - placeholder="base64 SHA-256 SPKI pin" - aria-label={`Key pin for .${t.tld}`} - spellCheck={false} - style={{ flex: "2 1 280px" }} - /> - - setPinDrafts((prev) => ({ - ...prev, - [t.tld]: { ...pinDraft, note: e.target.value }, - })) - } - placeholder="note (optional)" - aria-label={`Key note for .${t.tld}`} - maxLength={200} - style={{ flex: "1 1 180px" }} - /> - -
    -
    - ) : null} -
  • - ); - })} -
- )} -
- ); -}