@arbidocs/blocks 0.3.114 → 0.3.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { create } from 'zustand';
2
2
  import { persist } from 'zustand/middleware';
3
3
  import { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
4
- import { MessageSquare, Cpu, Code, Cloud, Layers, Target, Award, MapPin, Phone, Mail, Lock, Globe, Heart, Star, Rocket, Shield, Zap, Sparkles, Gavel, Scale, Bell, Activity, BarChart3, DollarSign, TrendingUp, Info, AlertCircle, CheckCircle, Clock, Calendar, Briefcase, Users, Folder, FileText, RotateCcw, Palette, Search, ArrowUpRight, ArrowDownRight, ImageIcon, ArrowRight, Quote, ChevronDown, XCircle, AlertTriangle, CheckCircle2, Check, ArrowLeft, Loader2, ChevronUp, BrainCircuit, Bot, LayoutGrid, FileJson, Save, Download, ImagePlus, Upload, Link2 } from 'lucide-react';
5
- import { Input, Card, CardHeader, CardTitle, CardContent, useArbi, ArbiProvider, useAiTask, useWorkspaceDocs as useWorkspaceDocs$1, useSemanticSearch as useSemanticSearch$1, cn, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Button, Avatar, AvatarImage, AvatarFallback, AiMarkdown, Badge, Switch, useConfigs, useAgents, Checkbox, Tabs as Tabs$1, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider, useDocuments, useThumbnails } from '@arbidocs/react';
4
+ import { MessageSquare, Cpu, Code, Cloud, Layers, Target, Award, MapPin, Phone, Mail, Lock, Globe, Heart, Star, Rocket, Shield, Zap, Sparkles, Gavel, Scale, Bell, Activity, BarChart3, DollarSign, TrendingUp, Info, AlertCircle, CheckCircle, Clock, Calendar, Briefcase, Users, Folder, FileText, RotateCcw, Palette, Search, ArrowUpRight, ArrowDownRight, ImageIcon, ArrowRight, Quote, ChevronDown, XCircle, AlertTriangle, CheckCircle2, Check, X, Menu, ArrowLeft, Loader2, ChevronUp, BrainCircuit, Bot, LayoutGrid, FileJson, Save, Download, ImagePlus, Upload, Link2, MessagesSquare } from 'lucide-react';
5
+ import { Input, Card, CardHeader, CardTitle, CardContent, useArbi, ArbiProvider, useAiTask, useWorkspaceDocs as useWorkspaceDocs$1, useSemanticSearch as useSemanticSearch$1, cn, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Button, Avatar, AvatarImage, AvatarFallback, AiMarkdown, Badge, Switch, useConfigs, useAgents, Checkbox, Tabs as Tabs$1, TabsList, TabsTrigger, TabsContent, Separator, ArbiWebSocketProvider, useDocuments, useThumbnails, useConversations } from '@arbidocs/react';
6
6
  export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, redlineStats, textToArtifact, toRedlineMarkdown } from '@arbidocs/react';
7
7
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
8
  import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';
9
9
  import { AgGridReact } from 'ag-grid-react';
10
10
  import { themeQuartz, AllCommunityModule } from 'ag-grid-community';
11
- import { Link, useParams } from 'react-router-dom';
11
+ import { useLocation, Link, useParams, NavLink } from 'react-router-dom';
12
12
  import { Puck, Render } from '@measured/puck';
13
13
 
14
14
  // src/prompts.ts
@@ -2297,6 +2297,322 @@ function TestimonialGrid({ items, columns = "3", testId }) {
2297
2297
  i
2298
2298
  )) });
2299
2299
  }
2300
+ function NavItemLink({ item, onNavigate }) {
2301
+ return /* @__PURE__ */ jsxs(
2302
+ NavLink,
2303
+ {
2304
+ to: item.to,
2305
+ end: item.end,
2306
+ onClick: onNavigate,
2307
+ "data-testid": tid("sidebar-nav", item.id),
2308
+ className: ({ isActive }) => cn(
2309
+ "group flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
2310
+ isActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground"
2311
+ ),
2312
+ children: [
2313
+ /* @__PURE__ */ jsx(item.icon, { className: "size-[18px] shrink-0" }),
2314
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label }),
2315
+ item.arbi && /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5 text-sidebar-primary", "aria-label": "Powered by ARBI" })
2316
+ ]
2317
+ }
2318
+ );
2319
+ }
2320
+ function SidebarBody({
2321
+ sections,
2322
+ footerItems,
2323
+ logo,
2324
+ logoHref,
2325
+ logoLabel,
2326
+ sidebarFooter,
2327
+ onNavigate
2328
+ }) {
2329
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2330
+ logo && /* @__PURE__ */ jsx("div", { className: "flex h-16 items-center border-b border-sidebar-border px-5", children: logoHref ? /* @__PURE__ */ jsx(Link, { to: logoHref, "aria-label": logoLabel, onClick: onNavigate, children: logo }) : logo }),
2331
+ /* @__PURE__ */ jsx("nav", { className: "flex-1 space-y-6 overflow-y-auto px-3 py-5", children: sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
2332
+ /* @__PURE__ */ jsx("p", { className: "px-3 pb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-sidebar-foreground/55", children: section.heading }),
2333
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: section.items.map((item) => /* @__PURE__ */ jsx(NavItemLink, { item, onNavigate }, item.id)) })
2334
+ ] }, section.heading)) }),
2335
+ (footerItems?.length || sidebarFooter) && /* @__PURE__ */ jsxs("div", { className: "border-t border-sidebar-border px-3 py-3", children: [
2336
+ footerItems?.map((item) => /* @__PURE__ */ jsx(NavItemLink, { item, onNavigate }, item.id)),
2337
+ sidebarFooter
2338
+ ] })
2339
+ ] });
2340
+ }
2341
+ function AppShell({
2342
+ sections,
2343
+ footerItems,
2344
+ logo,
2345
+ logoHref,
2346
+ logoLabel,
2347
+ sidebarFooter,
2348
+ topbar,
2349
+ rightRail,
2350
+ children,
2351
+ mainClassName,
2352
+ testIdPrefix = "app"
2353
+ }) {
2354
+ const [open, setOpen] = useState(false);
2355
+ const location = useLocation();
2356
+ useEffect(() => {
2357
+ setOpen(false);
2358
+ }, [location.pathname]);
2359
+ const body = (onNavigate) => /* @__PURE__ */ jsx(
2360
+ SidebarBody,
2361
+ {
2362
+ sections,
2363
+ footerItems,
2364
+ logo,
2365
+ logoHref,
2366
+ logoLabel,
2367
+ sidebarFooter,
2368
+ onNavigate
2369
+ }
2370
+ );
2371
+ return /* @__PURE__ */ jsxs(
2372
+ "div",
2373
+ {
2374
+ className: "flex h-screen overflow-hidden bg-background",
2375
+ "data-testid": `${testIdPrefix}-shell`,
2376
+ children: [
2377
+ /* @__PURE__ */ jsx(
2378
+ "aside",
2379
+ {
2380
+ className: "hidden h-full w-64 shrink-0 flex-col border-r border-sidebar-border bg-sidebar lg:flex",
2381
+ "data-testid": `${testIdPrefix}-sidebar`,
2382
+ children: body()
2383
+ }
2384
+ ),
2385
+ open && /* @__PURE__ */ jsxs(
2386
+ "div",
2387
+ {
2388
+ className: "fixed inset-0 z-50 lg:hidden",
2389
+ "data-testid": `${testIdPrefix}-sidebar-mobile`,
2390
+ children: [
2391
+ /* @__PURE__ */ jsx(
2392
+ "div",
2393
+ {
2394
+ className: "absolute inset-0 bg-black/60 backdrop-blur-sm",
2395
+ onClick: () => setOpen(false),
2396
+ "aria-hidden": true
2397
+ }
2398
+ ),
2399
+ /* @__PURE__ */ jsxs("aside", { className: "absolute inset-y-0 left-0 flex w-64 max-w-[82%] flex-col border-r border-sidebar-border bg-sidebar shadow-xl", children: [
2400
+ /* @__PURE__ */ jsx(
2401
+ "button",
2402
+ {
2403
+ type: "button",
2404
+ onClick: () => setOpen(false),
2405
+ className: "absolute right-3 top-4 z-10 flex size-8 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
2406
+ "aria-label": "Close menu",
2407
+ "data-testid": `${testIdPrefix}-sidebar-close`,
2408
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
2409
+ }
2410
+ ),
2411
+ body(() => setOpen(false))
2412
+ ] })
2413
+ ]
2414
+ }
2415
+ ),
2416
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2417
+ /* @__PURE__ */ jsxs(
2418
+ "header",
2419
+ {
2420
+ className: "flex h-16 shrink-0 items-center gap-4 border-b border-border bg-card/80 px-6 backdrop-blur",
2421
+ "data-testid": `${testIdPrefix}-topbar`,
2422
+ children: [
2423
+ /* @__PURE__ */ jsx(
2424
+ "button",
2425
+ {
2426
+ type: "button",
2427
+ onClick: () => setOpen(true),
2428
+ className: "flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground lg:hidden",
2429
+ "data-testid": `${testIdPrefix}-nav-toggle`,
2430
+ "aria-label": "Open menu",
2431
+ children: /* @__PURE__ */ jsx(Menu, { className: "size-5" })
2432
+ }
2433
+ ),
2434
+ topbar
2435
+ ]
2436
+ }
2437
+ ),
2438
+ /* @__PURE__ */ jsx(
2439
+ "main",
2440
+ {
2441
+ className: cn("flex-1 overflow-y-auto", mainClassName),
2442
+ "data-testid": `${testIdPrefix}-main`,
2443
+ children
2444
+ }
2445
+ )
2446
+ ] }),
2447
+ rightRail
2448
+ ]
2449
+ }
2450
+ );
2451
+ }
2452
+ var LiveDataContext = createContext(false);
2453
+ function useLiveDataActive() {
2454
+ return useContext(LiveDataContext);
2455
+ }
2456
+ function LiveDataProvider({ children }) {
2457
+ return /* @__PURE__ */ jsx(LiveDataContext.Provider, { value: true, children });
2458
+ }
2459
+ function WidgetPlaceholder({
2460
+ label,
2461
+ icon: Icon = Sparkles,
2462
+ testId
2463
+ }) {
2464
+ return /* @__PURE__ */ jsxs(
2465
+ "div",
2466
+ {
2467
+ className: "flex items-center gap-3 rounded-xl border border-dashed border-border bg-muted/30 px-5 py-6 text-sm text-muted-foreground",
2468
+ "data-testid": testId,
2469
+ children: [
2470
+ /* @__PURE__ */ jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "size-4" }) }),
2471
+ /* @__PURE__ */ jsxs("span", { children: [
2472
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
2473
+ /* @__PURE__ */ jsx("span", { className: "ml-1", children: "\xB7 live in the app" })
2474
+ ] })
2475
+ ]
2476
+ }
2477
+ );
2478
+ }
2479
+ function useActiveWorkspaceId() {
2480
+ const { workspaceId, fallbackWorkspaceId } = useConnection();
2481
+ return workspaceId ?? fallbackWorkspaceId ?? void 0;
2482
+ }
2483
+ function timeAgo(iso) {
2484
+ if (!iso) return "";
2485
+ const then = new Date(iso).getTime();
2486
+ if (Number.isNaN(then)) return "";
2487
+ const s = Math.max(0, Math.round((Date.now() - then) / 1e3));
2488
+ if (s < 60) return "just now";
2489
+ const m = Math.round(s / 60);
2490
+ if (m < 60) return `${m}m ago`;
2491
+ const h = Math.round(m / 60);
2492
+ if (h < 24) return `${h}h ago`;
2493
+ return `${Math.round(h / 24)}d ago`;
2494
+ }
2495
+ var METRIC_SOURCES = {
2496
+ documents: { label: "Documents", icon: FileText },
2497
+ conversations: { label: "Agent threads", icon: Bot }
2498
+ };
2499
+ var asMetricSource = (s) => s in METRIC_SOURCES ? s : "documents";
2500
+ function AgentActivityLive({
2501
+ title,
2502
+ limit,
2503
+ testId
2504
+ }) {
2505
+ const wsId = useActiveWorkspaceId();
2506
+ const { data, isLoading } = useConversations(wsId);
2507
+ const rows = [...data ?? []].sort((a, b) => +new Date(b.updated_at ?? 0) - +new Date(a.updated_at ?? 0)).slice(0, limit);
2508
+ return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: "rounded-xl p-5", "data-testid": testId, children: [
2509
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
2510
+ /* @__PURE__ */ jsx(Bot, { className: "size-4 text-muted-foreground" }),
2511
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-foreground", children: title })
2512
+ ] }),
2513
+ isLoading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": `${testId}-loading`, children: "Loading agent threads\u2026" }) : rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": `${testId}-empty`, children: "No agent threads yet." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", "data-testid": `${testId}-list`, children: rows.map((c, i) => /* @__PURE__ */ jsxs(
2514
+ "li",
2515
+ {
2516
+ className: "flex items-center justify-between gap-3",
2517
+ "data-testid": `${testId}-item-${i}`,
2518
+ children: [
2519
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 truncate", children: [
2520
+ /* @__PURE__ */ jsx(MessagesSquare, { className: "size-3.5 shrink-0 text-muted-foreground" }),
2521
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-foreground", children: c.title?.trim() || "Untitled thread" })
2522
+ ] }),
2523
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: timeAgo(c.updated_at) })
2524
+ ]
2525
+ },
2526
+ c.external_id ?? i
2527
+ )) })
2528
+ ] });
2529
+ }
2530
+ function LiveMetricInner({
2531
+ label,
2532
+ source,
2533
+ accent,
2534
+ testId
2535
+ }) {
2536
+ const wsId = useActiveWorkspaceId();
2537
+ const docs = useDocuments(wsId, { enabled: source === "documents" });
2538
+ const convos = useConversations(wsId, { enabled: source === "conversations" });
2539
+ const query = source === "documents" ? docs : convos;
2540
+ const count = Array.isArray(query.data) ? query.data.length : 0;
2541
+ return /* @__PURE__ */ jsx(
2542
+ MetricCard,
2543
+ {
2544
+ label: label || METRIC_SOURCES[source].label,
2545
+ value: query.isLoading ? "\u2014" : String(count),
2546
+ accent,
2547
+ icon: METRIC_SOURCES[source].icon,
2548
+ testId
2549
+ }
2550
+ );
2551
+ }
2552
+ function AgentActivityBlock({
2553
+ title,
2554
+ limit,
2555
+ testId
2556
+ }) {
2557
+ return useLiveDataActive() ? /* @__PURE__ */ jsx(AgentActivityLive, { title, limit, testId }) : /* @__PURE__ */ jsx(WidgetPlaceholder, { label: String(title || "Agent activity"), icon: Bot, testId });
2558
+ }
2559
+ function LiveMetricBlock({
2560
+ label,
2561
+ source,
2562
+ accent,
2563
+ testId
2564
+ }) {
2565
+ const src = asMetricSource(source);
2566
+ return useLiveDataActive() ? /* @__PURE__ */ jsx(LiveMetricInner, { label, source: src, accent, testId }) : /* @__PURE__ */ jsx(
2567
+ WidgetPlaceholder,
2568
+ {
2569
+ label: String(label || METRIC_SOURCES[src].label),
2570
+ icon: METRIC_SOURCES[src].icon,
2571
+ testId
2572
+ }
2573
+ );
2574
+ }
2575
+ function createLiveWidgets(kit) {
2576
+ const { blockTestId } = kit;
2577
+ const AgentActivityWidget = {
2578
+ label: "Agent activity (live)",
2579
+ fields: {
2580
+ title: { type: "text", label: "Title" },
2581
+ limit: { type: "number", label: "Rows", min: 1, max: 8 }
2582
+ },
2583
+ defaultProps: { title: "Recent agent activity", limit: 4 },
2584
+ render: ({ id, title, limit }) => /* @__PURE__ */ jsx(AgentActivityBlock, { title, limit, testId: blockTestId(id) })
2585
+ };
2586
+ const LiveMetricWidget = {
2587
+ label: "Live metric",
2588
+ fields: {
2589
+ label: { type: "text", label: "Label" },
2590
+ source: {
2591
+ type: "select",
2592
+ label: "Source",
2593
+ options: [
2594
+ { label: "Documents", value: "documents" },
2595
+ { label: "Agent threads", value: "conversations" }
2596
+ ]
2597
+ },
2598
+ accent: {
2599
+ type: "radio",
2600
+ label: "Accent",
2601
+ options: [
2602
+ { label: "Default", value: "default" },
2603
+ { label: "Primary", value: "primary" },
2604
+ { label: "Success", value: "success" }
2605
+ ]
2606
+ }
2607
+ },
2608
+ defaultProps: { label: "", source: "documents", accent: "primary" },
2609
+ render: ({ id, label, source, accent }) => /* @__PURE__ */ jsx(LiveMetricBlock, { label, source, accent, testId: blockTestId(id) })
2610
+ };
2611
+ return {
2612
+ AgentActivity: AgentActivityWidget,
2613
+ LiveMetric: LiveMetricWidget
2614
+ };
2615
+ }
2300
2616
 
2301
2617
  // src/studio/publish.ts
2302
2618
  function blobToDataUrl(blob) {
@@ -2375,7 +2691,8 @@ function StudioEditor({
2375
2691
  live,
2376
2692
  initialSlug,
2377
2693
  testIdPrefix = "studio",
2378
- publishAsset
2694
+ publishAsset,
2695
+ resetSeam = false
2379
2696
  }) {
2380
2697
  const arbi = useArbi();
2381
2698
  const materialize = useMemo(
@@ -2406,6 +2723,14 @@ function StudioEditor({
2406
2723
  cancelled = true;
2407
2724
  };
2408
2725
  }, [slug, live]);
2726
+ useEffect(() => {
2727
+ if (!resetSeam) return;
2728
+ const w = window;
2729
+ w.__studioResetSlug = (s) => persistence.deletePage(arbi, live, s);
2730
+ return () => {
2731
+ delete w.__studioResetSlug;
2732
+ };
2733
+ }, [resetSeam, arbi, live, persistence]);
2409
2734
  const persist4 = useCallback(
2410
2735
  async (toSave) => {
2411
2736
  setSaving(true);
@@ -2562,34 +2887,36 @@ function PageRenderer({
2562
2887
  live,
2563
2888
  slug: slugProp,
2564
2889
  emptyState,
2565
- testIdPrefix = "studio"
2890
+ testIdPrefix = "studio",
2891
+ liveData = false,
2892
+ initialData,
2893
+ applyTheme = true
2566
2894
  }) {
2567
2895
  const params = useParams();
2568
2896
  const slug = slugProp ?? params.slug ?? "home";
2569
2897
  const arbi = useArbi();
2570
- const [data, setData] = useState(null);
2571
- const [loading, setLoading] = useState(true);
2898
+ const [data, setData] = useState(initialData ?? null);
2899
+ const [loading, setLoading] = useState(!initialData);
2572
2900
  useEffect(() => {
2573
2901
  let cancelled = false;
2574
- setLoading(true);
2902
+ setLoading(!initialData);
2575
2903
  void (async () => {
2576
- const [{ data: published }, { data: theme }] = await Promise.all([
2577
- persistence.loadPublished(arbi, live, slug),
2578
- persistence.loadTheme(arbi, live)
2579
- ]);
2904
+ const theme = applyTheme ? (await persistence.loadTheme(arbi, live)).data : void 0;
2580
2905
  if (cancelled) return;
2581
- const page = published ?? (await persistence.loadPage(arbi, live, slug)).data;
2906
+ const page = initialData ?? (await persistence.loadPublished(arbi, live, slug)).data ?? (await persistence.loadPage(arbi, live, slug)).data;
2582
2907
  if (cancelled) return;
2583
- applyThemeVars(
2584
- theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
2585
- );
2908
+ if (applyTheme) {
2909
+ applyThemeVars(
2910
+ theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
2911
+ );
2912
+ }
2586
2913
  setData(page);
2587
2914
  setLoading(false);
2588
2915
  })();
2589
2916
  return () => {
2590
2917
  cancelled = true;
2591
2918
  };
2592
- }, [arbi, live, slug, persistence, defaultTheme]);
2919
+ }, [arbi, live, slug, persistence, defaultTheme, initialData, applyTheme]);
2593
2920
  if (loading) {
2594
2921
  return /* @__PURE__ */ jsx(
2595
2922
  "div",
@@ -2615,7 +2942,8 @@ function PageRenderer({
2615
2942
  }
2616
2943
  );
2617
2944
  }
2618
- return /* @__PURE__ */ jsx("main", { "data-testid": `page-${testIdPrefix}-render`, children: /* @__PURE__ */ jsx(Render, { config, data }) });
2945
+ const rendered = /* @__PURE__ */ jsx(Render, { config, data });
2946
+ return /* @__PURE__ */ jsx("main", { "data-testid": `page-${testIdPrefix}-render`, children: liveData ? /* @__PURE__ */ jsx(LiveDataProvider, { children: rendered }) : rendered });
2619
2947
  }
2620
2948
 
2621
2949
  // src/studio/persistence.ts
@@ -2691,6 +3019,26 @@ function createStudioPersistence(cfg) {
2691
3019
  function loadPublished(arbi, live, slug) {
2692
3020
  return readPage(arbi, live, publishedKey(slug), publishedFile(slug));
2693
3021
  }
3022
+ async function deletePage(arbi, live, slug) {
3023
+ try {
3024
+ localStorage.removeItem(pageKey(slug));
3025
+ localStorage.removeItem(publishedKey(slug));
3026
+ } catch {
3027
+ }
3028
+ if (!canUseArbi(arbi, live)) return;
3029
+ try {
3030
+ await arbi.selectWorkspace(WORKSPACE_ID);
3031
+ const docs = await arbi.documents.list();
3032
+ const names = [pageFile(slug), publishedFile(slug)];
3033
+ const ids = docs.filter(
3034
+ (d) => (d.folder ?? "").includes(FOLDER) && names.some((n) => (d.file_name ?? "").endsWith(n))
3035
+ ).map((d) => d.external_id);
3036
+ if (ids.length) await arbi.documents.delete(ids);
3037
+ console.info(`[studio] deleted ${ids.length} doc(s) for "${slug}" from ARBI`);
3038
+ } catch (e) {
3039
+ console.warn(`[studio] ARBI delete failed for "${slug}"`, e);
3040
+ }
3041
+ }
2694
3042
  async function saveTheme(arbi, live, theme) {
2695
3043
  try {
2696
3044
  localStorage.setItem(THEME_KEY, JSON.stringify(theme));
@@ -2727,7 +3075,7 @@ function createStudioPersistence(cfg) {
2727
3075
  return null;
2728
3076
  }
2729
3077
  }
2730
- return { savePage, loadPage, savePublished, loadPublished, saveTheme, loadTheme };
3078
+ return { savePage, loadPage, savePublished, loadPublished, deletePage, saveTheme, loadTheme };
2731
3079
  }
2732
3080
  function AiTextField({
2733
3081
  value,
@@ -2967,7 +3315,7 @@ function ImageFieldControl({
2967
3315
  {
2968
3316
  src: thumb,
2969
3317
  alt: d.file_name ?? "",
2970
- className: "aspect-square w-full object-cover"
3318
+ className: "aspect-square w-full bg-muted object-contain"
2971
3319
  }
2972
3320
  ) : /* @__PURE__ */ jsx("span", { className: "flex aspect-square w-full items-center justify-center bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(ImagePlus, { className: "size-4" }) })
2973
3321
  },
@@ -3127,7 +3475,7 @@ function Section({
3127
3475
  ] });
3128
3476
  }
3129
3477
  var iconMap = {
3130
- FileText,
3478
+ FileText: FileText,
3131
3479
  Folder,
3132
3480
  Users,
3133
3481
  Briefcase,
@@ -4203,6 +4551,13 @@ function createSiteBlocks(kit) {
4203
4551
  TestimonialGrid: TestimonialGridBlock
4204
4552
  };
4205
4553
  }
4554
+ var emptySlot2 = [];
4555
+ var METRIC_COLS = {
4556
+ "2": "sm:grid-cols-2",
4557
+ "3": "sm:grid-cols-2 lg:grid-cols-3",
4558
+ "4": "sm:grid-cols-2 lg:grid-cols-4"
4559
+ };
4560
+ var METRIC_GAP = { sm: "gap-3", md: "gap-4", lg: "gap-6" };
4206
4561
  function createArbiBlocksConfig(opts = {}) {
4207
4562
  const kit = createBlockKit(opts.aiFields, opts.imageField, opts.eyebrowClassName);
4208
4563
  const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
@@ -4240,16 +4595,19 @@ function createArbiBlocksConfig(opts = {}) {
4240
4595
  fields: {
4241
4596
  title: textField("Title"),
4242
4597
  eyebrow: textField("Eyebrow"),
4243
- description: textareaField("Description", void 0, "description")
4598
+ description: textareaField("Description", void 0, "description"),
4599
+ // `actions` is a slot so the top band can host real buttons/badges authored
4600
+ // as blocks (drop a Button in), while the interactive body stays bespoke.
4601
+ actions: { type: "slot", allow: ["Button", "Badge"] }
4244
4602
  },
4245
- defaultProps: { title: "Page title" },
4246
- // `actions` (ReactNode) is deferred for v1 — see module notes.
4247
- render: ({ id, title, eyebrow, description }) => /* @__PURE__ */ jsx(
4603
+ defaultProps: { title: "Page title", actions: emptySlot2 },
4604
+ render: ({ id, title, eyebrow, description, actions: Actions }) => /* @__PURE__ */ jsx(
4248
4605
  PageHeader,
4249
4606
  {
4250
4607
  title,
4251
4608
  eyebrow,
4252
4609
  description,
4610
+ actions: /* @__PURE__ */ jsx(Actions, { className: "flex items-center gap-2" }),
4253
4611
  testId: blockTestId(id)
4254
4612
  }
4255
4613
  )
@@ -4259,16 +4617,18 @@ function createArbiBlocksConfig(opts = {}) {
4259
4617
  fields: {
4260
4618
  title: textField("Title"),
4261
4619
  description: textareaField("Description", void 0, "description"),
4262
- icon: iconField()
4620
+ icon: iconField(),
4621
+ // `action` is a slot so an empty state can offer a real call-to-action button.
4622
+ action: { type: "slot", allow: ["Button"] }
4263
4623
  },
4264
- defaultProps: { title: "Nothing here yet" },
4265
- // `action` (ReactNode) is deferred for v1 — see module notes.
4266
- render: ({ id, title, description, icon }) => /* @__PURE__ */ jsx(
4624
+ defaultProps: { title: "Nothing here yet", action: emptySlot2 },
4625
+ render: ({ id, title, description, icon, action: Action }) => /* @__PURE__ */ jsx(
4267
4626
  EmptyState,
4268
4627
  {
4269
4628
  title,
4270
4629
  description,
4271
4630
  icon: resolveIcon(icon),
4631
+ action: /* @__PURE__ */ jsx(Action, {}),
4272
4632
  testId: blockTestId(id)
4273
4633
  }
4274
4634
  )
@@ -4321,6 +4681,42 @@ function createArbiBlocksConfig(opts = {}) {
4321
4681
  }
4322
4682
  )
4323
4683
  };
4684
+ const MetricRowBlock = {
4685
+ label: "Metric row",
4686
+ fields: {
4687
+ columns: {
4688
+ type: "radio",
4689
+ label: "Columns",
4690
+ options: [
4691
+ { label: "2", value: "2" },
4692
+ { label: "3", value: "3" },
4693
+ { label: "4", value: "4" }
4694
+ ]
4695
+ },
4696
+ gap: {
4697
+ type: "radio",
4698
+ label: "Gap",
4699
+ options: [
4700
+ { label: "S", value: "sm" },
4701
+ { label: "M", value: "md" },
4702
+ { label: "L", value: "lg" }
4703
+ ]
4704
+ },
4705
+ // Static KPI tiles and (when registered) live metric widgets both belong in a band.
4706
+ items: { type: "slot", allow: ["MetricCard", "LiveMetric"] }
4707
+ },
4708
+ defaultProps: { columns: "3", gap: "md", items: emptySlot2 },
4709
+ render: ({ id, columns, gap, items: Items }) => /* @__PURE__ */ jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsx(
4710
+ Items,
4711
+ {
4712
+ className: cn(
4713
+ "grid grid-cols-1",
4714
+ METRIC_COLS[columns] ?? METRIC_COLS["3"],
4715
+ METRIC_GAP[gap] ?? METRIC_GAP.md
4716
+ )
4717
+ }
4718
+ ) })
4719
+ };
4324
4720
  const DataTableBlockConfig = {
4325
4721
  label: "Data table",
4326
4722
  fields: {
@@ -4519,11 +4915,13 @@ function createArbiBlocksConfig(opts = {}) {
4519
4915
  };
4520
4916
  const website = createWebsiteBlocks(kit);
4521
4917
  const site = createSiteBlocks(kit);
4918
+ const widgets = opts.liveWidgets ? createLiveWidgets(kit) : {};
4522
4919
  const components = {
4523
4920
  SectionHeading: SectionHeadingBlock,
4524
4921
  PageHeader: PageHeaderBlock,
4525
4922
  EmptyState: EmptyStateBlock,
4526
4923
  MetricCard: MetricCardBlock,
4924
+ MetricRow: MetricRowBlock,
4527
4925
  DataTable: DataTableBlockConfig,
4528
4926
  Section: SectionBlock,
4529
4927
  Card: CardBlock,
@@ -4531,7 +4929,8 @@ function createArbiBlocksConfig(opts = {}) {
4531
4929
  Badge: BadgeBlock,
4532
4930
  Separator: SeparatorBlock,
4533
4931
  ...website,
4534
- ...site
4932
+ ...site,
4933
+ ...widgets
4535
4934
  };
4536
4935
  const config = {
4537
4936
  categories: {
@@ -4565,7 +4964,8 @@ function createArbiBlocksConfig(opts = {}) {
4565
4964
  Commerce: { title: "Commerce", components: ["Pricing"] },
4566
4965
  Forms: { title: "Forms", components: ["ContactForm"] },
4567
4966
  Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
4568
- Data: { title: "Data", components: ["MetricCard", "DataTable"] },
4967
+ Data: { title: "Data", components: ["MetricRow", "MetricCard", "DataTable"] },
4968
+ ...opts.liveWidgets ? { Widgets: { title: "Widgets", components: ["AgentActivity", "LiveMetric"] } } : {},
4569
4969
  Primitives: { title: "Primitives", components: ["Button", "Badge", "Separator"] }
4570
4970
  },
4571
4971
  components,
@@ -5050,6 +5450,6 @@ function VerticalBuilder({
5050
5450
  ] });
5051
5451
  }
5052
5452
 
5053
- export { ASSET_REF_PREFIX, Accordion, AgentsPanel, AssetResolverProvider, AvatarBlock, Banner, BlockImage, CTABanner, Callout, Columns, ConnectionProvider, ContactForm, Container, DataTable, DataTableBlock, EmptyState, FAQ, FeatureGrid, Footer, Gallery, GridView, Hero, LegalArbiProvider, ListBlock, LogoCloud, MediaText, MetricCard, ModuleManager, Navbar, PageHeader, PageRenderer, PricingTable, RichTextBlock, Section, SectionHeading, Spacer, StatGroup, Steps, StudioEditor, THEME_STYLE_ID, Tabs, TeamGrid, Testimonial, TestimonialGrid, ThemeEditor, Toolbar, VerticalBuilder, VideoEmbed, applyStoredTheme, applyThemeVars, asAssetRef, assetRefId, clearThemeVars, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createArbiMaterializer, createBlockKit, createImageField, createModuleRegistry, createModuleStore, createPromptLibrary, createSiteBlocks, createStudioPersistence, createThemeStore, createVerticalExport, createWebsiteBlocks, daysUntil, fmtDate, fmtDateTime, fromNow, gbp, hrs, iconMap, initials, isAssetRef, isAuthenticated, materializePageData, matterToContext, pct, tid, toEmbedUrl, toHslTriplet, useAssetResolverActive, useConnection, useFirmAiTask, useImageGen, useSemanticSearch, useWorkspaceDocs };
5453
+ export { ASSET_REF_PREFIX, Accordion, AgentsPanel, AppShell, AssetResolverProvider, AvatarBlock, Banner, BlockImage, CTABanner, Callout, Columns, ConnectionProvider, ContactForm, Container, DataTable, DataTableBlock, EmptyState, FAQ, FeatureGrid, Footer, Gallery, GridView, Hero, LegalArbiProvider, ListBlock, LiveDataProvider, LogoCloud, MediaText, MetricCard, ModuleManager, Navbar, PageHeader, PageRenderer, PricingTable, RichTextBlock, Section, SectionHeading, Spacer, StatGroup, Steps, StudioEditor, THEME_STYLE_ID, Tabs, TeamGrid, Testimonial, TestimonialGrid, ThemeEditor, Toolbar, VerticalBuilder, VideoEmbed, WidgetPlaceholder, applyStoredTheme, applyThemeVars, asAssetRef, assetRefId, clearThemeVars, createAgentSelectionStore, createAiFields, createArbiBlocksConfig, createArbiMaterializer, createBlockKit, createImageField, createLiveWidgets, createModuleRegistry, createModuleStore, createPromptLibrary, createSiteBlocks, createStudioPersistence, createThemeStore, createVerticalExport, createWebsiteBlocks, daysUntil, fmtDate, fmtDateTime, fromNow, gbp, hrs, iconMap, initials, isAssetRef, isAuthenticated, materializePageData, matterToContext, pct, tid, toEmbedUrl, toHslTriplet, useAssetResolverActive, useConnection, useFirmAiTask, useImageGen, useLiveDataActive, useSemanticSearch, useWorkspaceDocs };
5054
5454
  //# sourceMappingURL=index.js.map
5055
5455
  //# sourceMappingURL=index.js.map