From 678da370c73b445e267b8d8ba4d8fedaec190ae4 Mon Sep 17 00:00:00 2001 From: Chris Pardy Date: Fri, 01 May 2026 11:56:09 +0000 Subject: [PATCH] feat(dashboard): scripts grouped by trigger family + lexicon targeting panel Frontend for the trigger-keyed scripts subsystem (commit a086830). The dashboard now exposes: - `/dashboard/settings/scripts` — grouped list of all scripts by trigger family (Record events, XRPC handlers, Label arrivals). The script's `id` IS its trigger string; the table makes that visible in monospace and links to the detail page. - `/dashboard/settings/scripts/new` — create form. The trigger id is composed via a `(kind, suffix)` picker (kind dropdown for the prefix family + free-text suffix for the NSID). The Lua body prefills with a `handle()` skeleton. The form accepts a `?id=` URL param so the lexicon detail page can deep-link with a candidate id pre-filled. - `/dashboard/settings/scripts/[id]` — detail/edit. The id is the PK so it's locked here; rename = delete + recreate. The header shows the cascade hint for record events ("`record.create:` fires only on this action; cascades to `record.index:` if absent."). The lexicon detail page gets a new "Scripts targeting this lexicon" panel that lists the trigger ids relevant to the lexicon's type: - Record-type lexicons show the four `record.*` slots (`record.index` / `record.create` / `record.update` / `record.delete`) with Edit/Create links. - XRPC lexicons show the matching `xrpc.{query,procedure}` slot. Existing scripts link straight to the detail page; missing rows link to the New Script page pre-filled with the trigger id. Operators get a one-click path from a lexicon to the scripts that affect it. The legacy `index_hook` editor on the lexicon page is left in place for now — operators with existing data can still see and edit the column, even though the runtime no longer reads it. A follow-up commit can drop the editor once we're confident no one relies on it. New: `web/src/types/scripts.ts` (Script, UpsertScriptBody, PatchScriptBody, TriggerKind / TriggerFamily helpers, parseTriggerId, DEFAULT_SCRIPT_BODY). Sidebar gets a Scripts nav item under Integrations (`scripts:read`-gated). Signed-off-by: Chris Pardy Signed-off-by: Trezy --- src/admin/backfill.rs | 14 ++++++++------ web/src/components/app-sidebar.tsx | 7 +++++++ web/src/lib/api.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ web/src/types/scripts.ts | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/app/dashboard/lexicons/[id]/lexicon-detail.tsx | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------- web/src/app/dashboard/settings/scripts/page.tsx | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/app/dashboard/settings/scripts/script-form.tsx | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/app/dashboard/settings/scripts/[id]/page.tsx | 11 +++++++++++ web/src/app/dashboard/settings/scripts/[id]/script-detail.tsx | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/app/dashboard/settings/scripts/new/page.tsx | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 file(s) changed, 1196 insertion(s)(+), 98 deletion(s)(-) diff --git a/src/admin/backfill.rs b/src/admin/backfill.rs --- a/src/admin/backfill.rs +++ b/src/admin/backfill.rs @@ -1349,12 +1349,14 @@ let rec_to_store = match crate::lua::run_record_event_script( state, - collection, - "create", - &uri, - did, - &rkey, - Some(&entry.value), + crate::lua::RecordEventPayload { + nsid: collection, + action: "create", + uri: &uri, + did, + rkey: &rkey, + record: Some(&entry.value), + }, ) .await { diff --git a/web/src/components/app-sidebar.tsx b/web/src/components/app-sidebar.tsx --- a/web/src/components/app-sidebar.tsx +++ b/web/src/components/app-sidebar.tsx @@ -18,6 +18,7 @@ IconApps, IconArrowUpCircle, IconArrowsShuffle, + IconCode, IconSkull, IconFlask, } from "@tabler/icons-react"; @@ -98,6 +99,12 @@ url: "/dashboard/settings/labelers", icon: IconTag, requiredPermissions: ["labelers:read"], + }, + { + title: "Scripts", + url: "/dashboard/settings/scripts", + icon: IconCode, + requiredPermissions: ["scripts:read"], }, ]; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -7,6 +7,7 @@ import type { AdminListRecordsResponse } from "@/types/records"; import type { EventsListResponse } from "@/types/events"; import type { ScriptVariableSummary } from "@/types/script-variables"; +import type { Script, UpsertScriptBody, PatchScriptBody } from "@/types/scripts"; import type { LabelerSummary } from "@/types/labelers"; import type { ApiClientSummary, @@ -37,6 +38,21 @@ export type { AdminRecord, AdminListRecordsResponse } from "@/types/records"; export type { EventLogEntry, EventsListResponse } from "@/types/events"; export type { ScriptVariableSummary } from "@/types/script-variables"; +export type { + Script, + ScriptLanguage, + TriggerKind, + TriggerFamily, + UpsertScriptBody, + PatchScriptBody, +} from "@/types/scripts"; +export { + TRIGGER_KIND_LABELS, + TRIGGER_FAMILY_LABELS, + familyOf, + parseTriggerId, + DEFAULT_SCRIPT_BODY, +} from "@/types/scripts"; export type { LabelerSummary } from "@/types/labelers"; export type { RecordLabel } from "@/types/records"; export type { @@ -782,4 +798,33 @@ method: "POST", body: JSON.stringify(body), }); +} + +// Scripts (trigger-keyed) +export function getScripts() { + return apiFetch("/admin/scripts") +} + +export function getScript(id: string) { + return apiFetch