@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.cjs CHANGED
@@ -2298,6 +2298,322 @@ function TestimonialGrid({ items, columns = "3", testId }) {
2298
2298
  i
2299
2299
  )) });
2300
2300
  }
2301
+ function NavItemLink({ item, onNavigate }) {
2302
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2303
+ reactRouterDom.NavLink,
2304
+ {
2305
+ to: item.to,
2306
+ end: item.end,
2307
+ onClick: onNavigate,
2308
+ "data-testid": tid("sidebar-nav", item.id),
2309
+ className: ({ isActive }) => react.cn(
2310
+ "group flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
2311
+ isActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground"
2312
+ ),
2313
+ children: [
2314
+ /* @__PURE__ */ jsxRuntime.jsx(item.icon, { className: "size-[18px] shrink-0" }),
2315
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: item.label }),
2316
+ item.arbi && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-3.5 text-sidebar-primary", "aria-label": "Powered by ARBI" })
2317
+ ]
2318
+ }
2319
+ );
2320
+ }
2321
+ function SidebarBody({
2322
+ sections,
2323
+ footerItems,
2324
+ logo,
2325
+ logoHref,
2326
+ logoLabel,
2327
+ sidebarFooter,
2328
+ onNavigate
2329
+ }) {
2330
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2331
+ logo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-16 items-center border-b border-sidebar-border px-5", children: logoHref ? /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: logoHref, "aria-label": logoLabel, onClick: onNavigate, children: logo }) : logo }),
2332
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex-1 space-y-6 overflow-y-auto px-3 py-5", children: sections.map((section) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2333
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-sidebar-foreground/55", children: section.heading }),
2334
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: section.items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(NavItemLink, { item, onNavigate }, item.id)) })
2335
+ ] }, section.heading)) }),
2336
+ (footerItems?.length || sidebarFooter) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-sidebar-border px-3 py-3", children: [
2337
+ footerItems?.map((item) => /* @__PURE__ */ jsxRuntime.jsx(NavItemLink, { item, onNavigate }, item.id)),
2338
+ sidebarFooter
2339
+ ] })
2340
+ ] });
2341
+ }
2342
+ function AppShell({
2343
+ sections,
2344
+ footerItems,
2345
+ logo,
2346
+ logoHref,
2347
+ logoLabel,
2348
+ sidebarFooter,
2349
+ topbar,
2350
+ rightRail,
2351
+ children,
2352
+ mainClassName,
2353
+ testIdPrefix = "app"
2354
+ }) {
2355
+ const [open, setOpen] = react$1.useState(false);
2356
+ const location = reactRouterDom.useLocation();
2357
+ react$1.useEffect(() => {
2358
+ setOpen(false);
2359
+ }, [location.pathname]);
2360
+ const body = (onNavigate) => /* @__PURE__ */ jsxRuntime.jsx(
2361
+ SidebarBody,
2362
+ {
2363
+ sections,
2364
+ footerItems,
2365
+ logo,
2366
+ logoHref,
2367
+ logoLabel,
2368
+ sidebarFooter,
2369
+ onNavigate
2370
+ }
2371
+ );
2372
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2373
+ "div",
2374
+ {
2375
+ className: "flex h-screen overflow-hidden bg-background",
2376
+ "data-testid": `${testIdPrefix}-shell`,
2377
+ children: [
2378
+ /* @__PURE__ */ jsxRuntime.jsx(
2379
+ "aside",
2380
+ {
2381
+ className: "hidden h-full w-64 shrink-0 flex-col border-r border-sidebar-border bg-sidebar lg:flex",
2382
+ "data-testid": `${testIdPrefix}-sidebar`,
2383
+ children: body()
2384
+ }
2385
+ ),
2386
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
2387
+ "div",
2388
+ {
2389
+ className: "fixed inset-0 z-50 lg:hidden",
2390
+ "data-testid": `${testIdPrefix}-sidebar-mobile`,
2391
+ children: [
2392
+ /* @__PURE__ */ jsxRuntime.jsx(
2393
+ "div",
2394
+ {
2395
+ className: "absolute inset-0 bg-black/60 backdrop-blur-sm",
2396
+ onClick: () => setOpen(false),
2397
+ "aria-hidden": true
2398
+ }
2399
+ ),
2400
+ /* @__PURE__ */ jsxRuntime.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: [
2401
+ /* @__PURE__ */ jsxRuntime.jsx(
2402
+ "button",
2403
+ {
2404
+ type: "button",
2405
+ onClick: () => setOpen(false),
2406
+ 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",
2407
+ "aria-label": "Close menu",
2408
+ "data-testid": `${testIdPrefix}-sidebar-close`,
2409
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-5" })
2410
+ }
2411
+ ),
2412
+ body(() => setOpen(false))
2413
+ ] })
2414
+ ]
2415
+ }
2416
+ ),
2417
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2418
+ /* @__PURE__ */ jsxRuntime.jsxs(
2419
+ "header",
2420
+ {
2421
+ className: "flex h-16 shrink-0 items-center gap-4 border-b border-border bg-card/80 px-6 backdrop-blur",
2422
+ "data-testid": `${testIdPrefix}-topbar`,
2423
+ children: [
2424
+ /* @__PURE__ */ jsxRuntime.jsx(
2425
+ "button",
2426
+ {
2427
+ type: "button",
2428
+ onClick: () => setOpen(true),
2429
+ className: "flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground lg:hidden",
2430
+ "data-testid": `${testIdPrefix}-nav-toggle`,
2431
+ "aria-label": "Open menu",
2432
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Menu, { className: "size-5" })
2433
+ }
2434
+ ),
2435
+ topbar
2436
+ ]
2437
+ }
2438
+ ),
2439
+ /* @__PURE__ */ jsxRuntime.jsx(
2440
+ "main",
2441
+ {
2442
+ className: react.cn("flex-1 overflow-y-auto", mainClassName),
2443
+ "data-testid": `${testIdPrefix}-main`,
2444
+ children
2445
+ }
2446
+ )
2447
+ ] }),
2448
+ rightRail
2449
+ ]
2450
+ }
2451
+ );
2452
+ }
2453
+ var LiveDataContext = react$1.createContext(false);
2454
+ function useLiveDataActive() {
2455
+ return react$1.useContext(LiveDataContext);
2456
+ }
2457
+ function LiveDataProvider({ children }) {
2458
+ return /* @__PURE__ */ jsxRuntime.jsx(LiveDataContext.Provider, { value: true, children });
2459
+ }
2460
+ function WidgetPlaceholder({
2461
+ label,
2462
+ icon: Icon = lucideReact.Sparkles,
2463
+ testId
2464
+ }) {
2465
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2466
+ "div",
2467
+ {
2468
+ 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",
2469
+ "data-testid": testId,
2470
+ children: [
2471
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }) }),
2472
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2473
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: label }),
2474
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1", children: "\xB7 live in the app" })
2475
+ ] })
2476
+ ]
2477
+ }
2478
+ );
2479
+ }
2480
+ function useActiveWorkspaceId() {
2481
+ const { workspaceId, fallbackWorkspaceId } = useConnection();
2482
+ return workspaceId ?? fallbackWorkspaceId ?? void 0;
2483
+ }
2484
+ function timeAgo(iso) {
2485
+ if (!iso) return "";
2486
+ const then = new Date(iso).getTime();
2487
+ if (Number.isNaN(then)) return "";
2488
+ const s = Math.max(0, Math.round((Date.now() - then) / 1e3));
2489
+ if (s < 60) return "just now";
2490
+ const m = Math.round(s / 60);
2491
+ if (m < 60) return `${m}m ago`;
2492
+ const h = Math.round(m / 60);
2493
+ if (h < 24) return `${h}h ago`;
2494
+ return `${Math.round(h / 24)}d ago`;
2495
+ }
2496
+ var METRIC_SOURCES = {
2497
+ documents: { label: "Documents", icon: lucideReact.FileText },
2498
+ conversations: { label: "Agent threads", icon: lucideReact.Bot }
2499
+ };
2500
+ var asMetricSource = (s) => s in METRIC_SOURCES ? s : "documents";
2501
+ function AgentActivityLive({
2502
+ title,
2503
+ limit,
2504
+ testId
2505
+ }) {
2506
+ const wsId = useActiveWorkspaceId();
2507
+ const { data, isLoading } = react.useConversations(wsId);
2508
+ const rows = [...data ?? []].sort((a, b) => +new Date(b.updated_at ?? 0) - +new Date(a.updated_at ?? 0)).slice(0, limit);
2509
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Card, { variant: "elevated", className: "rounded-xl p-5", "data-testid": testId, children: [
2510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
2511
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bot, { className: "size-4 text-muted-foreground" }),
2512
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: title })
2513
+ ] }),
2514
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", "data-testid": `${testId}-loading`, children: "Loading agent threads\u2026" }) : rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", "data-testid": `${testId}-empty`, children: "No agent threads yet." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-2", "data-testid": `${testId}-list`, children: rows.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2515
+ "li",
2516
+ {
2517
+ className: "flex items-center justify-between gap-3",
2518
+ "data-testid": `${testId}-item-${i}`,
2519
+ children: [
2520
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 truncate", children: [
2521
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessagesSquare, { className: "size-3.5 shrink-0 text-muted-foreground" }),
2522
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm text-foreground", children: c.title?.trim() || "Untitled thread" })
2523
+ ] }),
2524
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: timeAgo(c.updated_at) })
2525
+ ]
2526
+ },
2527
+ c.external_id ?? i
2528
+ )) })
2529
+ ] });
2530
+ }
2531
+ function LiveMetricInner({
2532
+ label,
2533
+ source,
2534
+ accent,
2535
+ testId
2536
+ }) {
2537
+ const wsId = useActiveWorkspaceId();
2538
+ const docs = react.useDocuments(wsId, { enabled: source === "documents" });
2539
+ const convos = react.useConversations(wsId, { enabled: source === "conversations" });
2540
+ const query = source === "documents" ? docs : convos;
2541
+ const count = Array.isArray(query.data) ? query.data.length : 0;
2542
+ return /* @__PURE__ */ jsxRuntime.jsx(
2543
+ MetricCard,
2544
+ {
2545
+ label: label || METRIC_SOURCES[source].label,
2546
+ value: query.isLoading ? "\u2014" : String(count),
2547
+ accent,
2548
+ icon: METRIC_SOURCES[source].icon,
2549
+ testId
2550
+ }
2551
+ );
2552
+ }
2553
+ function AgentActivityBlock({
2554
+ title,
2555
+ limit,
2556
+ testId
2557
+ }) {
2558
+ return useLiveDataActive() ? /* @__PURE__ */ jsxRuntime.jsx(AgentActivityLive, { title, limit, testId }) : /* @__PURE__ */ jsxRuntime.jsx(WidgetPlaceholder, { label: String(title || "Agent activity"), icon: lucideReact.Bot, testId });
2559
+ }
2560
+ function LiveMetricBlock({
2561
+ label,
2562
+ source,
2563
+ accent,
2564
+ testId
2565
+ }) {
2566
+ const src = asMetricSource(source);
2567
+ return useLiveDataActive() ? /* @__PURE__ */ jsxRuntime.jsx(LiveMetricInner, { label, source: src, accent, testId }) : /* @__PURE__ */ jsxRuntime.jsx(
2568
+ WidgetPlaceholder,
2569
+ {
2570
+ label: String(label || METRIC_SOURCES[src].label),
2571
+ icon: METRIC_SOURCES[src].icon,
2572
+ testId
2573
+ }
2574
+ );
2575
+ }
2576
+ function createLiveWidgets(kit) {
2577
+ const { blockTestId } = kit;
2578
+ const AgentActivityWidget = {
2579
+ label: "Agent activity (live)",
2580
+ fields: {
2581
+ title: { type: "text", label: "Title" },
2582
+ limit: { type: "number", label: "Rows", min: 1, max: 8 }
2583
+ },
2584
+ defaultProps: { title: "Recent agent activity", limit: 4 },
2585
+ render: ({ id, title, limit }) => /* @__PURE__ */ jsxRuntime.jsx(AgentActivityBlock, { title, limit, testId: blockTestId(id) })
2586
+ };
2587
+ const LiveMetricWidget = {
2588
+ label: "Live metric",
2589
+ fields: {
2590
+ label: { type: "text", label: "Label" },
2591
+ source: {
2592
+ type: "select",
2593
+ label: "Source",
2594
+ options: [
2595
+ { label: "Documents", value: "documents" },
2596
+ { label: "Agent threads", value: "conversations" }
2597
+ ]
2598
+ },
2599
+ accent: {
2600
+ type: "radio",
2601
+ label: "Accent",
2602
+ options: [
2603
+ { label: "Default", value: "default" },
2604
+ { label: "Primary", value: "primary" },
2605
+ { label: "Success", value: "success" }
2606
+ ]
2607
+ }
2608
+ },
2609
+ defaultProps: { label: "", source: "documents", accent: "primary" },
2610
+ render: ({ id, label, source, accent }) => /* @__PURE__ */ jsxRuntime.jsx(LiveMetricBlock, { label, source, accent, testId: blockTestId(id) })
2611
+ };
2612
+ return {
2613
+ AgentActivity: AgentActivityWidget,
2614
+ LiveMetric: LiveMetricWidget
2615
+ };
2616
+ }
2301
2617
 
2302
2618
  // src/studio/publish.ts
2303
2619
  function blobToDataUrl(blob) {
@@ -2376,7 +2692,8 @@ function StudioEditor({
2376
2692
  live,
2377
2693
  initialSlug,
2378
2694
  testIdPrefix = "studio",
2379
- publishAsset
2695
+ publishAsset,
2696
+ resetSeam = false
2380
2697
  }) {
2381
2698
  const arbi = react.useArbi();
2382
2699
  const materialize = react$1.useMemo(
@@ -2407,6 +2724,14 @@ function StudioEditor({
2407
2724
  cancelled = true;
2408
2725
  };
2409
2726
  }, [slug, live]);
2727
+ react$1.useEffect(() => {
2728
+ if (!resetSeam) return;
2729
+ const w = window;
2730
+ w.__studioResetSlug = (s) => persistence.deletePage(arbi, live, s);
2731
+ return () => {
2732
+ delete w.__studioResetSlug;
2733
+ };
2734
+ }, [resetSeam, arbi, live, persistence]);
2410
2735
  const persist4 = react$1.useCallback(
2411
2736
  async (toSave) => {
2412
2737
  setSaving(true);
@@ -2563,34 +2888,36 @@ function PageRenderer({
2563
2888
  live,
2564
2889
  slug: slugProp,
2565
2890
  emptyState,
2566
- testIdPrefix = "studio"
2891
+ testIdPrefix = "studio",
2892
+ liveData = false,
2893
+ initialData,
2894
+ applyTheme = true
2567
2895
  }) {
2568
2896
  const params = reactRouterDom.useParams();
2569
2897
  const slug = slugProp ?? params.slug ?? "home";
2570
2898
  const arbi = react.useArbi();
2571
- const [data, setData] = react$1.useState(null);
2572
- const [loading, setLoading] = react$1.useState(true);
2899
+ const [data, setData] = react$1.useState(initialData ?? null);
2900
+ const [loading, setLoading] = react$1.useState(!initialData);
2573
2901
  react$1.useEffect(() => {
2574
2902
  let cancelled = false;
2575
- setLoading(true);
2903
+ setLoading(!initialData);
2576
2904
  void (async () => {
2577
- const [{ data: published }, { data: theme }] = await Promise.all([
2578
- persistence.loadPublished(arbi, live, slug),
2579
- persistence.loadTheme(arbi, live)
2580
- ]);
2905
+ const theme = applyTheme ? (await persistence.loadTheme(arbi, live)).data : void 0;
2581
2906
  if (cancelled) return;
2582
- const page = published ?? (await persistence.loadPage(arbi, live, slug)).data;
2907
+ const page = initialData ?? (await persistence.loadPublished(arbi, live, slug)).data ?? (await persistence.loadPage(arbi, live, slug)).data;
2583
2908
  if (cancelled) return;
2584
- applyThemeVars(
2585
- theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
2586
- );
2909
+ if (applyTheme) {
2910
+ applyThemeVars(
2911
+ theme ? { ...defaultTheme, ...theme, colors: { ...defaultTheme.colors, ...theme.colors } } : defaultTheme
2912
+ );
2913
+ }
2587
2914
  setData(page);
2588
2915
  setLoading(false);
2589
2916
  })();
2590
2917
  return () => {
2591
2918
  cancelled = true;
2592
2919
  };
2593
- }, [arbi, live, slug, persistence, defaultTheme]);
2920
+ }, [arbi, live, slug, persistence, defaultTheme, initialData, applyTheme]);
2594
2921
  if (loading) {
2595
2922
  return /* @__PURE__ */ jsxRuntime.jsx(
2596
2923
  "div",
@@ -2616,7 +2943,8 @@ function PageRenderer({
2616
2943
  }
2617
2944
  );
2618
2945
  }
2619
- return /* @__PURE__ */ jsxRuntime.jsx("main", { "data-testid": `page-${testIdPrefix}-render`, children: /* @__PURE__ */ jsxRuntime.jsx(puck.Render, { config, data }) });
2946
+ const rendered = /* @__PURE__ */ jsxRuntime.jsx(puck.Render, { config, data });
2947
+ return /* @__PURE__ */ jsxRuntime.jsx("main", { "data-testid": `page-${testIdPrefix}-render`, children: liveData ? /* @__PURE__ */ jsxRuntime.jsx(LiveDataProvider, { children: rendered }) : rendered });
2620
2948
  }
2621
2949
 
2622
2950
  // src/studio/persistence.ts
@@ -2692,6 +3020,26 @@ function createStudioPersistence(cfg) {
2692
3020
  function loadPublished(arbi, live, slug) {
2693
3021
  return readPage(arbi, live, publishedKey(slug), publishedFile(slug));
2694
3022
  }
3023
+ async function deletePage(arbi, live, slug) {
3024
+ try {
3025
+ localStorage.removeItem(pageKey(slug));
3026
+ localStorage.removeItem(publishedKey(slug));
3027
+ } catch {
3028
+ }
3029
+ if (!canUseArbi(arbi, live)) return;
3030
+ try {
3031
+ await arbi.selectWorkspace(WORKSPACE_ID);
3032
+ const docs = await arbi.documents.list();
3033
+ const names = [pageFile(slug), publishedFile(slug)];
3034
+ const ids = docs.filter(
3035
+ (d) => (d.folder ?? "").includes(FOLDER) && names.some((n) => (d.file_name ?? "").endsWith(n))
3036
+ ).map((d) => d.external_id);
3037
+ if (ids.length) await arbi.documents.delete(ids);
3038
+ console.info(`[studio] deleted ${ids.length} doc(s) for "${slug}" from ARBI`);
3039
+ } catch (e) {
3040
+ console.warn(`[studio] ARBI delete failed for "${slug}"`, e);
3041
+ }
3042
+ }
2695
3043
  async function saveTheme(arbi, live, theme) {
2696
3044
  try {
2697
3045
  localStorage.setItem(THEME_KEY, JSON.stringify(theme));
@@ -2728,7 +3076,7 @@ function createStudioPersistence(cfg) {
2728
3076
  return null;
2729
3077
  }
2730
3078
  }
2731
- return { savePage, loadPage, savePublished, loadPublished, saveTheme, loadTheme };
3079
+ return { savePage, loadPage, savePublished, loadPublished, deletePage, saveTheme, loadTheme };
2732
3080
  }
2733
3081
  function AiTextField({
2734
3082
  value,
@@ -2968,7 +3316,7 @@ function ImageFieldControl({
2968
3316
  {
2969
3317
  src: thumb,
2970
3318
  alt: d.file_name ?? "",
2971
- className: "aspect-square w-full object-cover"
3319
+ className: "aspect-square w-full bg-muted object-contain"
2972
3320
  }
2973
3321
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex aspect-square w-full items-center justify-center bg-muted text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "size-4" }) })
2974
3322
  },
@@ -4204,6 +4552,13 @@ function createSiteBlocks(kit) {
4204
4552
  TestimonialGrid: TestimonialGridBlock
4205
4553
  };
4206
4554
  }
4555
+ var emptySlot2 = [];
4556
+ var METRIC_COLS = {
4557
+ "2": "sm:grid-cols-2",
4558
+ "3": "sm:grid-cols-2 lg:grid-cols-3",
4559
+ "4": "sm:grid-cols-2 lg:grid-cols-4"
4560
+ };
4561
+ var METRIC_GAP = { sm: "gap-3", md: "gap-4", lg: "gap-6" };
4207
4562
  function createArbiBlocksConfig(opts = {}) {
4208
4563
  const kit = createBlockKit(opts.aiFields, opts.imageField, opts.eyebrowClassName);
4209
4564
  const { textField, textareaField, boolRadio, iconField, resolveIcon, blockTestId } = kit;
@@ -4241,16 +4596,19 @@ function createArbiBlocksConfig(opts = {}) {
4241
4596
  fields: {
4242
4597
  title: textField("Title"),
4243
4598
  eyebrow: textField("Eyebrow"),
4244
- description: textareaField("Description", void 0, "description")
4599
+ description: textareaField("Description", void 0, "description"),
4600
+ // `actions` is a slot so the top band can host real buttons/badges authored
4601
+ // as blocks (drop a Button in), while the interactive body stays bespoke.
4602
+ actions: { type: "slot", allow: ["Button", "Badge"] }
4245
4603
  },
4246
- defaultProps: { title: "Page title" },
4247
- // `actions` (ReactNode) is deferred for v1 — see module notes.
4248
- render: ({ id, title, eyebrow, description }) => /* @__PURE__ */ jsxRuntime.jsx(
4604
+ defaultProps: { title: "Page title", actions: emptySlot2 },
4605
+ render: ({ id, title, eyebrow, description, actions: Actions }) => /* @__PURE__ */ jsxRuntime.jsx(
4249
4606
  PageHeader,
4250
4607
  {
4251
4608
  title,
4252
4609
  eyebrow,
4253
4610
  description,
4611
+ actions: /* @__PURE__ */ jsxRuntime.jsx(Actions, { className: "flex items-center gap-2" }),
4254
4612
  testId: blockTestId(id)
4255
4613
  }
4256
4614
  )
@@ -4260,16 +4618,18 @@ function createArbiBlocksConfig(opts = {}) {
4260
4618
  fields: {
4261
4619
  title: textField("Title"),
4262
4620
  description: textareaField("Description", void 0, "description"),
4263
- icon: iconField()
4621
+ icon: iconField(),
4622
+ // `action` is a slot so an empty state can offer a real call-to-action button.
4623
+ action: { type: "slot", allow: ["Button"] }
4264
4624
  },
4265
- defaultProps: { title: "Nothing here yet" },
4266
- // `action` (ReactNode) is deferred for v1 — see module notes.
4267
- render: ({ id, title, description, icon }) => /* @__PURE__ */ jsxRuntime.jsx(
4625
+ defaultProps: { title: "Nothing here yet", action: emptySlot2 },
4626
+ render: ({ id, title, description, icon, action: Action }) => /* @__PURE__ */ jsxRuntime.jsx(
4268
4627
  EmptyState,
4269
4628
  {
4270
4629
  title,
4271
4630
  description,
4272
4631
  icon: resolveIcon(icon),
4632
+ action: /* @__PURE__ */ jsxRuntime.jsx(Action, {}),
4273
4633
  testId: blockTestId(id)
4274
4634
  }
4275
4635
  )
@@ -4322,6 +4682,42 @@ function createArbiBlocksConfig(opts = {}) {
4322
4682
  }
4323
4683
  )
4324
4684
  };
4685
+ const MetricRowBlock = {
4686
+ label: "Metric row",
4687
+ fields: {
4688
+ columns: {
4689
+ type: "radio",
4690
+ label: "Columns",
4691
+ options: [
4692
+ { label: "2", value: "2" },
4693
+ { label: "3", value: "3" },
4694
+ { label: "4", value: "4" }
4695
+ ]
4696
+ },
4697
+ gap: {
4698
+ type: "radio",
4699
+ label: "Gap",
4700
+ options: [
4701
+ { label: "S", value: "sm" },
4702
+ { label: "M", value: "md" },
4703
+ { label: "L", value: "lg" }
4704
+ ]
4705
+ },
4706
+ // Static KPI tiles and (when registered) live metric widgets both belong in a band.
4707
+ items: { type: "slot", allow: ["MetricCard", "LiveMetric"] }
4708
+ },
4709
+ defaultProps: { columns: "3", gap: "md", items: emptySlot2 },
4710
+ render: ({ id, columns, gap, items: Items }) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": blockTestId(id), children: /* @__PURE__ */ jsxRuntime.jsx(
4711
+ Items,
4712
+ {
4713
+ className: react.cn(
4714
+ "grid grid-cols-1",
4715
+ METRIC_COLS[columns] ?? METRIC_COLS["3"],
4716
+ METRIC_GAP[gap] ?? METRIC_GAP.md
4717
+ )
4718
+ }
4719
+ ) })
4720
+ };
4325
4721
  const DataTableBlockConfig = {
4326
4722
  label: "Data table",
4327
4723
  fields: {
@@ -4520,11 +4916,13 @@ function createArbiBlocksConfig(opts = {}) {
4520
4916
  };
4521
4917
  const website = createWebsiteBlocks(kit);
4522
4918
  const site = createSiteBlocks(kit);
4919
+ const widgets = opts.liveWidgets ? createLiveWidgets(kit) : {};
4523
4920
  const components = {
4524
4921
  SectionHeading: SectionHeadingBlock,
4525
4922
  PageHeader: PageHeaderBlock,
4526
4923
  EmptyState: EmptyStateBlock,
4527
4924
  MetricCard: MetricCardBlock,
4925
+ MetricRow: MetricRowBlock,
4528
4926
  DataTable: DataTableBlockConfig,
4529
4927
  Section: SectionBlock,
4530
4928
  Card: CardBlock,
@@ -4532,7 +4930,8 @@ function createArbiBlocksConfig(opts = {}) {
4532
4930
  Badge: BadgeBlock,
4533
4931
  Separator: SeparatorBlock,
4534
4932
  ...website,
4535
- ...site
4933
+ ...site,
4934
+ ...widgets
4536
4935
  };
4537
4936
  const config = {
4538
4937
  categories: {
@@ -4566,7 +4965,8 @@ function createArbiBlocksConfig(opts = {}) {
4566
4965
  Commerce: { title: "Commerce", components: ["Pricing"] },
4567
4966
  Forms: { title: "Forms", components: ["ContactForm"] },
4568
4967
  Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
4569
- Data: { title: "Data", components: ["MetricCard", "DataTable"] },
4968
+ Data: { title: "Data", components: ["MetricRow", "MetricCard", "DataTable"] },
4969
+ ...opts.liveWidgets ? { Widgets: { title: "Widgets", components: ["AgentActivity", "LiveMetric"] } } : {},
4570
4970
  Primitives: { title: "Primitives", components: ["Button", "Badge", "Separator"] }
4571
4971
  },
4572
4972
  components,
@@ -5090,6 +5490,7 @@ Object.defineProperty(exports, "toRedlineMarkdown", {
5090
5490
  exports.ASSET_REF_PREFIX = ASSET_REF_PREFIX;
5091
5491
  exports.Accordion = Accordion;
5092
5492
  exports.AgentsPanel = AgentsPanel;
5493
+ exports.AppShell = AppShell;
5093
5494
  exports.AssetResolverProvider = AssetResolverProvider;
5094
5495
  exports.AvatarBlock = AvatarBlock;
5095
5496
  exports.Banner = Banner;
@@ -5111,6 +5512,7 @@ exports.GridView = GridView;
5111
5512
  exports.Hero = Hero;
5112
5513
  exports.LegalArbiProvider = LegalArbiProvider;
5113
5514
  exports.ListBlock = ListBlock;
5515
+ exports.LiveDataProvider = LiveDataProvider;
5114
5516
  exports.LogoCloud = LogoCloud;
5115
5517
  exports.MediaText = MediaText;
5116
5518
  exports.MetricCard = MetricCard;
@@ -5135,6 +5537,7 @@ exports.ThemeEditor = ThemeEditor;
5135
5537
  exports.Toolbar = Toolbar;
5136
5538
  exports.VerticalBuilder = VerticalBuilder;
5137
5539
  exports.VideoEmbed = VideoEmbed;
5540
+ exports.WidgetPlaceholder = WidgetPlaceholder;
5138
5541
  exports.applyStoredTheme = applyStoredTheme;
5139
5542
  exports.applyThemeVars = applyThemeVars;
5140
5543
  exports.asAssetRef = asAssetRef;
@@ -5146,6 +5549,7 @@ exports.createArbiBlocksConfig = createArbiBlocksConfig;
5146
5549
  exports.createArbiMaterializer = createArbiMaterializer;
5147
5550
  exports.createBlockKit = createBlockKit;
5148
5551
  exports.createImageField = createImageField;
5552
+ exports.createLiveWidgets = createLiveWidgets;
5149
5553
  exports.createModuleRegistry = createModuleRegistry;
5150
5554
  exports.createModuleStore = createModuleStore;
5151
5555
  exports.createPromptLibrary = createPromptLibrary;
@@ -5174,6 +5578,7 @@ exports.useAssetResolverActive = useAssetResolverActive;
5174
5578
  exports.useConnection = useConnection;
5175
5579
  exports.useFirmAiTask = useFirmAiTask;
5176
5580
  exports.useImageGen = useImageGen;
5581
+ exports.useLiveDataActive = useLiveDataActive;
5177
5582
  exports.useSemanticSearch = useSemanticSearch;
5178
5583
  exports.useWorkspaceDocs = useWorkspaceDocs;
5179
5584
  //# sourceMappingURL=index.cjs.map