@gntik-ai/templates 0.9.0 → 0.11.0

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,11 +1,11 @@
1
1
  'use client';
2
- import { ActivityFeed, AppSidebarFooter, AppSidebarHeader, AppSidebarNav, AppTopbar, AuthHeader, BulkActionBar, ChartCard, CodePanel, CommentThread, ConfirmDestructive, CreateApiKeyDialog, DEPLOYMENT_COLUMNS, DEPLOYMENT_DETAILS, DEPLOYMENT_EVENTS, DEPLOYMENT_ROWS, DangerZone, DataTable, DateRangeFilter, DescriptionListCard, ErrorPanel, EvaluationScorecard, Faq, FeatureGrid, FileUploadPanel, FilterBar, FirstRunEmpty, FlowBuilder, ForgotPasswordForm, FormSection, Hero, InlineCallout, InviteMembersDialog, InvoiceTable, KPI_ITEMS, KpiRow, ListSkeleton, LogViewer, MembersTable, MfaChallenge, ModelKeysList, NoResultsEmpty, NotificationMatrix, PROJECT_ACTIVITY, PageHeader, PageToolbar, PaymentMethodCard, PendingInvitations, PlanCard, PricingTable, ProfileHeader, PromptEditor, QuotaMeters, RESOURCE_CARDS, ResetPasswordForm, ResourceCardGrid, SectionHeader, SessionsDevices, SettingsRow, SignInForm, SignUpForm, SsoButtons, StatusTimeline, StickyActionBar, SystemBanner, TokenCostCard, TraceWaterfall, TrendDelta, ValidationSummary, apiKeyExpiries, apiKeyScopes, appSidebarNavGroups, appSidebarUser, appSidebarUser as appSidebarUser$1, appSidebarWorkspaces, appTopbarNotifications, firstRunSteps, formatSpanDuration, generateDemoSecret, memberRoles, noAccessSample, notificationChannels, notificationDefaults, notificationEvents, sampleDeviceSessions, sampleInvitations, sampleInvoices, sampleMembers, samplePaymentMethod, samplePlan, samplePricingTiers, sampleProfile, sampleQuotas, ssoProviders, ssoProviders as ssoProviders$1 } from "@gntik-ai/blocks";
3
- import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, matchText, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useTheme } from "@gntik-ai/ui";
2
+ import { ChartLocaleProvider, Heatmap, MiniBar, RadarChart, chartFmt } from "@gntik-ai/charts";
3
+ import { ActivityFeed, AppSidebarFooter, AppSidebarHeader, AppSidebarNav, AppTopbar, AuthHeader, BulkActionBar, ChartCard, CodePanel, CommentThread, ConfirmDestructive, CreateApiKeyDialog, DEPLOYMENT_COLUMNS, DEPLOYMENT_DETAILS, DEPLOYMENT_EVENTS, DEPLOYMENT_ROWS, DangerZone, DataTable, DateRangeFilter, DescriptionListCard, ErrorPanel, EvaluationScorecard, Faq, FeatureGrid, FileUploadPanel, FilterBar, FirstRunEmpty, FlowBuilder, ForgotPasswordForm, FormSection, Hero, InlineCallout, InviteMembersDialog, InvoiceTable, KPI_ITEMS, KpiRow, ListSkeleton, LogViewer, MembersTable, MfaChallenge, ModelKeysList, NoResultsEmpty, NotificationMatrix, PROJECT_ACTIVITY, PageHeader, PageToolbar, PaymentMethodCard, PendingInvitations, PlanCard, PricingTable, ProfileHeader, PromptEditor, QuotaMeters, RESOURCE_CARDS, ResetPasswordForm, ResourceCardGrid, SectionHeader, SessionsDevices, SettingsRow, SignInForm, SignUpForm, SsoButtons, StatusTimeline, StickyActionBar, SystemBanner, TokenCostCard, TraceWaterfall, TrendDelta, ValidationSummary, apiKeyExpiries, apiKeyScopes, appSidebarNavGroups, appSidebarUser, appSidebarUser as appSidebarUser$1, appSidebarWorkspaces, appTopbarNotifications, builderEdges, builderNodes, cleanFlowGraph, firstRunSteps, formatSpanDuration, generateDemoSecret, memberRoles, noAccessSample, notificationChannels, notificationDefaults, notificationEvents, sampleDeviceSessions, sampleInvitations, sampleInvoices, sampleMembers, samplePaymentMethod, samplePlan, samplePricingTiers, sampleProfile, sampleQuotas, ssoProviders, ssoProviders as ssoProviders$1 } from "@gntik-ai/blocks";
4
+ import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, matchText, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useOs, useTheme } from "@gntik-ai/ui";
4
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
6
  import { Fragment as Fragment$1, createElement, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
6
7
  import { ChatAnnouncer, ChatComposer, ChatLayout, ChatMessage, CitationList, Markdown, SuggestionChips, ToolCallCard } from "@gntik-ai/chat";
7
8
  import { Archive, ArrowLeft, ArrowRight, BarChart3, Bell, BellOff, BellRing, BookOpen, Braces, Building2, CalendarClock, Check, CheckCheck, CircleCheck, Clock, Code2, Columns3, Copy, Cpu, CreditCard, Database, Download, ExternalLink, FileCode, FileDiff, FileJson, FileQuestion, FileSearch, FileText, Folder, FolderKanban, FolderOpen, GitBranch, Globe, GripVertical, Hand, HardDrive, Home, KeyRound, Keyboard, LayoutGrid, LayoutTemplate, LifeBuoy, ListOrdered, ListTodo, Lock, Mail, MailOpen, MapPin, MessageSquare, MessagesSquare, Minus, Palette, Phone, Play, PlayCircle, Plug, Plus, RefreshCw, Rocket, RotateCcw, RotateCw, Rss, Save, Search, SearchX, Send, Server, ServerCrash, Shield, ShieldCheck, Square, SquarePen, Table2, ThumbsDown, ThumbsUp, Trash2, Upload, User, UserPlus, Users, Wrench, X } from "@gntik-ai/icons";
8
- import { Heatmap, MiniBar, RadarChart, chartFmt } from "@gntik-ai/charts";
9
9
  import { DiffEditor } from "@gntik-ai/editor";
10
10
  import { flushSync } from "react-dom";
11
11
  //#region src/shared/ConsoleShell.tsx
@@ -16,38 +16,42 @@ import { flushSync } from "react-dom";
16
16
  * link and user-menu items.
17
17
  */
18
18
  function ConsoleShell({ children, nav, currentHref, breadcrumbs, workspaces, currentWorkspaceId, onWorkspaceChange, onCreateWorkspace, user, usage, helpHref, helpLabel, userMenuItems, onSignOut, topbar, storageKey, sidebarLabel, collapseMode }) {
19
- return /* @__PURE__ */ jsx(SidebarLayout, {
20
- fullScreen: true,
21
- storageKey,
22
- sidebarLabel,
23
- collapseMode,
24
- sidebarHeader: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarHeader, {
25
- collapsed,
26
- workspaces,
27
- currentWorkspaceId,
28
- onWorkspaceChange,
29
- onCreateWorkspace
30
- }),
31
- sidebar: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarNav, {
32
- groups: nav,
33
- currentHref,
34
- collapsed
35
- }),
36
- sidebarFooter: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarFooter, {
37
- collapsed,
38
- user,
39
- usage,
40
- helpHref,
41
- helpLabel,
42
- userMenuItems,
43
- onSignOut
44
- }),
45
- topbar: /* @__PURE__ */ jsx(AppTopbar, {
46
- breadcrumbs,
47
- user,
48
- ...topbar
49
- }),
50
- children
19
+ const { locale } = useI18n();
20
+ return /* @__PURE__ */ jsx(ChartLocaleProvider, {
21
+ locale,
22
+ children: /* @__PURE__ */ jsx(SidebarLayout, {
23
+ fullScreen: true,
24
+ storageKey,
25
+ sidebarLabel,
26
+ collapseMode,
27
+ sidebarHeader: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarHeader, {
28
+ collapsed,
29
+ workspaces,
30
+ currentWorkspaceId,
31
+ onWorkspaceChange,
32
+ onCreateWorkspace
33
+ }),
34
+ sidebar: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarNav, {
35
+ groups: nav,
36
+ currentHref,
37
+ collapsed
38
+ }),
39
+ sidebarFooter: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarFooter, {
40
+ collapsed,
41
+ user,
42
+ usage,
43
+ helpHref,
44
+ helpLabel,
45
+ userMenuItems,
46
+ onSignOut
47
+ }),
48
+ topbar: /* @__PURE__ */ jsx(AppTopbar, {
49
+ breadcrumbs,
50
+ user,
51
+ ...topbar
52
+ }),
53
+ children
54
+ })
51
55
  });
52
56
  }
53
57
  //#endregion
@@ -1938,15 +1942,24 @@ const flowBuilderBreadcrumbs = [{
1938
1942
  * '@gntik-ai/flow/styles.css' once for the React Flow canvas.
1939
1943
  */
1940
1944
  function FlowBuilderPage(props) {
1941
- const { title = flowBuilderContent.title, status = flowBuilderContent.status, nodes, edges, palette, onRun, consoleEntries, defaultConsoleEntries, onConsoleChange, onConsoleClear, onSave, onPublish, nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus, onNodeSelect, codePanel, breadcrumbs = flowBuilderBreadcrumbs, height = 640, fullScreen = true } = props;
1945
+ const { title = flowBuilderContent.title, status = flowBuilderContent.status, nodes = builderNodes, edges = builderEdges, palette, onRun, consoleEntries, defaultConsoleEntries, onConsoleChange, onConsoleClear, onSave, onPublish, nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus, onNodeSelect, graph, onGraphChange, codePanel, breadcrumbs = flowBuilderBreadcrumbs, height = 640, fullScreen = true } = props;
1942
1946
  const [saving, setSaving] = useState(false);
1943
- const [savedNote, setSavedNote] = useState(flowBuilderContent.lastSaved);
1947
+ const [initialGraph] = useState(() => ({
1948
+ nodes,
1949
+ edges
1950
+ }));
1951
+ const [latestGraph, setLatestGraph] = useState();
1952
+ const [saveState, setSaveState] = useState("initial");
1953
+ const revision = useRef(0);
1954
+ const { t } = useI18n();
1955
+ const savedNote = saveState === "initial" ? flowBuilderContent.lastSaved : t(saveState === "unsaved" ? "actionBar.unsaved" : "actionBar.saved");
1944
1956
  const save = async () => {
1945
1957
  setSaving(true);
1958
+ const savedRevision = revision.current;
1946
1959
  try {
1947
- await onSave?.();
1948
- setSavedNote("All changes saved");
1949
- } finally {
1960
+ await onSave?.(cleanFlowGraph(graph ?? latestGraph ?? initialGraph));
1961
+ if (revision.current === savedRevision) setSaveState("saved");
1962
+ } catch {} finally {
1950
1963
  setSaving(false);
1951
1964
  }
1952
1965
  };
@@ -1994,6 +2007,13 @@ function FlowBuilderPage(props) {
1994
2007
  title,
1995
2008
  nodes,
1996
2009
  edges,
2010
+ graph,
2011
+ onGraphChange: (next, change) => {
2012
+ revision.current += 1;
2013
+ setLatestGraph(next);
2014
+ setSaveState("unsaved");
2015
+ onGraphChange?.(next, change);
2016
+ },
1997
2017
  palette,
1998
2018
  onRun,
1999
2019
  consoleEntries,
@@ -2025,7 +2045,7 @@ const meta$60 = {
2025
2045
  family: "Builders",
2026
2046
  priority: "P3",
2027
2047
  status: "experimental",
2028
- description: "Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode=\"run\", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import '@gntik-ai/flow/styles.css' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode=\"run\" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode=\"run\" nodeStatus={{step:\"running\"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.",
2048
+ description: "onSave(graph) receives the graph on screen (controlled graph, latest reported edit, or initial starter graph), without selected, dragging or measured node fields. Zero-argument handlers remain valid. Optional graph and onGraphChange pass through to FlowBuilder. Committed edits show actionBar.unsaved, a resolved save shows actionBar.saved, and a rejected save retains unsaved status and releases loading. Edits during a pending save remain unsaved. examples/SaveGraph.tsx shows a controlled graph and onSave={(graph) => localStorage.setItem(\"workflow-builder-example\", JSON.stringify(graph))}; hosts own persistence. Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode=\"run\", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import '@gntik-ai/flow/styles.css' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode=\"run\" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode=\"run\" nodeStatus={{step:\"running\"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.",
2029
2049
  layout: "CanvasLayout",
2030
2050
  blocks: ["flow-builder", "code-panel"]
2031
2051
  };
@@ -2983,6 +3003,33 @@ const meta$29 = {
2983
3003
  blocks: ["reset-password-form"]
2984
3004
  };
2985
3005
  //#endregion
3006
+ //#region src/resource-detail/CustomTabs.tsx
3007
+ function CustomTabs({ tabs, defaultTab, onTabChange, label }) {
3008
+ const [selected, setSelected] = useState(defaultTab);
3009
+ const value = tabs.find((tab) => tab.value === selected)?.value ?? tabs[0]?.value ?? null;
3010
+ if (tabs.length === 0) return null;
3011
+ return /* @__PURE__ */ jsxs(Tabs, {
3012
+ value,
3013
+ onValueChange: (next) => {
3014
+ const tab = tabs.find((tab) => tab.value === next);
3015
+ if (tab) {
3016
+ setSelected(tab.value);
3017
+ onTabChange?.(tab.value);
3018
+ }
3019
+ },
3020
+ children: [/* @__PURE__ */ jsx(TabsList, {
3021
+ "aria-label": label,
3022
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(TabsTab, {
3023
+ value: tab.value,
3024
+ children: tab.label
3025
+ }, tab.value))
3026
+ }), tabs.map((tab) => /* @__PURE__ */ jsx(TabsPanel, {
3027
+ value: tab.value,
3028
+ children: tab.content
3029
+ }, tab.value))]
3030
+ });
3031
+ }
3032
+ //#endregion
2986
3033
  //#region src/resource-detail/data.ts
2987
3034
  const detailResource = {
2988
3035
  name: "orders-api",
@@ -3034,23 +3081,23 @@ const detailDangerActions = [{
3034
3081
  //#endregion
3035
3082
  //#region src/resource-detail/Page.tsx
3036
3083
  const TABS = [
3037
- {
3038
- value: "overview",
3039
- label: "Overview"
3040
- },
3041
- {
3042
- value: "activity",
3043
- label: "Activity"
3044
- },
3045
- {
3046
- value: "settings",
3047
- label: "Settings"
3048
- }
3084
+ "overview",
3085
+ "activity",
3086
+ "settings"
3049
3087
  ];
3050
- /** Resource detail: editable tabs by default, or explicit-only Overview when readOnly is true. */
3088
+ /**
3089
+ * Resource detail: editable tabs by default, or explicit-only Overview when readOnly is true.
3090
+ * Pass tabs to replace the built-in sections with caller labels, values and content; readOnly
3091
+ * does not filter custom tabs. An omitted or unknown defaultTab selects the first custom tab.
3092
+ * shell={null} renders only Page and its header. The host owns the main landmark and must
3093
+ * provide ChartLocaleProvider when its custom chart content needs a locale. Breadcrumbs
3094
+ * render in the header in this mode: pass a trail or [] to avoid the editable sample trail.
3095
+ * Omitted or object shells and omitted tabs preserve the existing default output.
3096
+ */
3051
3097
  function ResourceDetailPage(props) {
3098
+ const { t } = useI18n();
3052
3099
  const readOnly = props.readOnly ?? false;
3053
- const { name = readOnly ? "" : detailResource.name, description = readOnly ? "" : detailResource.description, status = readOnly ? "" : detailResource.status, breadcrumbs = readOnly ? [] : detailBreadcrumbs, meta: metaProp, actions: actionsProp, details: detailsProp, overviewTitle = "Configuration", overviewDescription = readOnly ? "" : "Settings of this resource.", historyTitle = "Status history", events = readOnly ? [] : detailEvents, activity = readOnly ? [] : detailActivity, dangerActions = readOnly ? [] : detailDangerActions, defaultTab = "overview", onTabChange, onEditDetails, onSaveSettings, onDangerAction, shell } = props;
3100
+ const { name = readOnly ? "" : detailResource.name, description = readOnly ? "" : detailResource.description, status = readOnly ? "" : detailResource.status, breadcrumbs = readOnly ? [] : detailBreadcrumbs, meta: metaProp, actions: actionsProp, details: detailsProp, overviewTitle = "Configuration", overviewDescription = readOnly ? "" : "Settings of this resource.", historyTitle = "Status history", events = readOnly ? [] : detailEvents, activity = readOnly ? [] : detailActivity, dangerActions = readOnly ? [] : detailDangerActions, defaultTab = "overview", onEditDetails, onSaveSettings, onDangerAction, shell, labels } = props;
3054
3101
  const meta = metaProp ?? (readOnly ? [] : detailMeta);
3055
3102
  const actions = actionsProp ?? (readOnly ? [] : [{
3056
3103
  label: "View logs",
@@ -3073,140 +3120,150 @@ function ResourceDetailPage(props) {
3073
3120
  description
3074
3121
  });
3075
3122
  const dirty = settings.name !== saved.name || settings.description !== saved.description;
3076
- const tabs = readOnly ? TABS.filter((t) => t.value === "overview") : TABS;
3077
- return /* @__PURE__ */ jsx(ConsoleShell, {
3078
- currentHref: "/deployments",
3079
- breadcrumbs,
3080
- ...shell,
3081
- children: /* @__PURE__ */ jsx(Page, {
3082
- width: "wide",
3083
- header: /* @__PURE__ */ jsx(PageHeader, {
3084
- breadcrumbs: null,
3085
- title: name,
3086
- description,
3087
- status,
3088
- meta,
3089
- actions,
3090
- tabs: null
3091
- }),
3092
- children: /* @__PURE__ */ jsxs(Tabs, {
3093
- value: readOnly ? "overview" : tab,
3094
- onValueChange: (v) => {
3095
- const next = tabs.find((t) => t.value === v)?.value ?? "overview";
3096
- setTab(next);
3097
- onTabChange?.(next);
3098
- },
3099
- children: [
3100
- /* @__PURE__ */ jsx(TabsList, {
3101
- "aria-label": "Resource sections",
3102
- children: tabs.map((t) => /* @__PURE__ */ jsx(TabsTab, {
3103
- value: t.value,
3104
- children: t.label
3105
- }, t.value))
3106
- }),
3107
- /* @__PURE__ */ jsx(TabsPanel, {
3108
- value: "overview",
3109
- children: /* @__PURE__ */ jsxs(Grid, {
3110
- cols: historyEvents === void 0 ? 1 : {
3111
- base: 1,
3112
- lg: 5
3113
- },
3114
- gap: 6,
3115
- align: "start",
3116
- children: [/* @__PURE__ */ jsx(DescriptionListCard, {
3117
- className: historyEvents === void 0 ? void 0 : "lg:col-span-3",
3118
- titleAs: "h2",
3119
- title: overviewTitle,
3120
- description: overviewDescription,
3121
- items: details,
3122
- onEdit: onEditDetails
3123
- }), historyEvents !== void 0 && /* @__PURE__ */ jsx(Section, {
3124
- role: readOnly ? "group" : void 0,
3125
- variant: "card",
3126
- padding: "md",
3127
- title: historyTitle,
3128
- className: "lg:col-span-2",
3129
- children: /* @__PURE__ */ jsx(StatusTimeline, {
3130
- events: historyEvents,
3131
- maxVisible: readOnly ? void 0 : 4
3132
- })
3133
- })]
3134
- })
3135
- }),
3136
- !readOnly && /* @__PURE__ */ jsx(TabsPanel, {
3137
- value: "activity",
3138
- children: /* @__PURE__ */ jsx(Section, {
3123
+ const tabs = (readOnly ? TABS.filter((value) => value === "overview") : TABS).map((value) => ({
3124
+ value,
3125
+ label: labels?.[value] ?? t(`resourceDetail.${value}`)
3126
+ }));
3127
+ const content = /* @__PURE__ */ jsx(Page, {
3128
+ width: "wide",
3129
+ header: /* @__PURE__ */ jsx(PageHeader, {
3130
+ as: shell === null ? "div" : void 0,
3131
+ breadcrumbs: shell === null ? breadcrumbs : null,
3132
+ title: name,
3133
+ description,
3134
+ status,
3135
+ meta,
3136
+ actions,
3137
+ tabs: null
3138
+ }),
3139
+ children: props.tabs !== void 0 ? /* @__PURE__ */ jsx(CustomTabs, {
3140
+ tabs: props.tabs,
3141
+ defaultTab: props.defaultTab,
3142
+ onTabChange: props.onTabChange,
3143
+ label: labels?.sections ?? t("resourceDetail.sections")
3144
+ }) : /* @__PURE__ */ jsxs(Tabs, {
3145
+ value: readOnly ? "overview" : tab,
3146
+ onValueChange: (v) => {
3147
+ const next = tabs.find((t) => t.value === v)?.value ?? "overview";
3148
+ setTab(next);
3149
+ if (props.tabs === void 0) props.onTabChange?.(next);
3150
+ },
3151
+ children: [
3152
+ /* @__PURE__ */ jsx(TabsList, {
3153
+ "aria-label": labels?.sections ?? t("resourceDetail.sections"),
3154
+ children: tabs.map((t) => /* @__PURE__ */ jsx(TabsTab, {
3155
+ value: t.value,
3156
+ children: t.label
3157
+ }, t.value))
3158
+ }),
3159
+ /* @__PURE__ */ jsx(TabsPanel, {
3160
+ value: "overview",
3161
+ children: /* @__PURE__ */ jsxs(Grid, {
3162
+ cols: historyEvents === void 0 ? 1 : {
3163
+ base: 1,
3164
+ lg: 5
3165
+ },
3166
+ gap: 6,
3167
+ align: "start",
3168
+ children: [/* @__PURE__ */ jsx(DescriptionListCard, {
3169
+ className: historyEvents === void 0 ? void 0 : "lg:col-span-3",
3170
+ titleAs: "h2",
3171
+ title: overviewTitle,
3172
+ description: overviewDescription,
3173
+ items: details,
3174
+ onEdit: onEditDetails
3175
+ }), historyEvents !== void 0 && /* @__PURE__ */ jsx(Section, {
3176
+ role: readOnly ? "group" : void 0,
3139
3177
  variant: "card",
3140
3178
  padding: "md",
3141
- title: "Activity",
3142
- description: "Changes made to this resource by people and automations.",
3143
- children: /* @__PURE__ */ jsx(ActivityFeed, {
3144
- items: activity,
3145
- label: "Activity, newest first"
3179
+ title: historyTitle,
3180
+ className: "lg:col-span-2",
3181
+ children: /* @__PURE__ */ jsx(StatusTimeline, {
3182
+ events: historyEvents,
3183
+ maxVisible: readOnly ? void 0 : 4
3146
3184
  })
3185
+ })]
3186
+ })
3187
+ }),
3188
+ !readOnly && /* @__PURE__ */ jsx(TabsPanel, {
3189
+ value: "activity",
3190
+ children: /* @__PURE__ */ jsx(Section, {
3191
+ variant: "card",
3192
+ padding: "md",
3193
+ title: "Activity",
3194
+ description: "Changes made to this resource by people and automations.",
3195
+ children: /* @__PURE__ */ jsx(ActivityFeed, {
3196
+ items: activity,
3197
+ label: "Activity, newest first"
3147
3198
  })
3148
- }),
3149
- !readOnly && /* @__PURE__ */ jsx(TabsPanel, {
3150
- value: "settings",
3151
- children: /* @__PURE__ */ jsxs(Stack, {
3152
- gap: 8,
3153
- children: [/* @__PURE__ */ jsx("form", {
3154
- noValidate: true,
3155
- onSubmit: (e) => {
3156
- e.preventDefault();
3157
- setSaved(settings);
3158
- onSaveSettings?.(settings);
3159
- },
3160
- children: /* @__PURE__ */ jsx(FormSection, {
3161
- title: "General",
3162
- description: "Name and description shown across the console.",
3163
- actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
3164
- variant: "ghost",
3165
- disabled: !dirty,
3166
- onClick: () => setSettings(saved),
3167
- children: "Discard"
3168
- }), /* @__PURE__ */ jsx(Button, {
3169
- type: "submit",
3170
- disabled: !dirty,
3171
- children: "Save changes"
3172
- })] }),
3173
- children: /* @__PURE__ */ jsxs(Stack, {
3174
- gap: 4,
3175
- children: [/* @__PURE__ */ jsxs(Field, { children: [
3176
- /* @__PURE__ */ jsx(FieldLabel, {
3177
- required: true,
3178
- children: "Name"
3179
- }),
3180
- /* @__PURE__ */ jsx(Input, {
3181
- required: true,
3182
- value: settings.name,
3183
- onValueChange: (v) => setSettings((s) => ({
3184
- ...s,
3185
- name: v
3186
- }))
3187
- }),
3188
- /* @__PURE__ */ jsx(FieldDescription, { children: "Lowercase letters, numbers and dashes." })
3189
- ] }), /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Description" }), /* @__PURE__ */ jsx(Textarea, {
3190
- rows: 3,
3191
- value: settings.description,
3199
+ })
3200
+ }),
3201
+ !readOnly && /* @__PURE__ */ jsx(TabsPanel, {
3202
+ value: "settings",
3203
+ children: /* @__PURE__ */ jsxs(Stack, {
3204
+ gap: 8,
3205
+ children: [/* @__PURE__ */ jsx("form", {
3206
+ noValidate: true,
3207
+ onSubmit: (e) => {
3208
+ e.preventDefault();
3209
+ setSaved(settings);
3210
+ onSaveSettings?.(settings);
3211
+ },
3212
+ children: /* @__PURE__ */ jsx(FormSection, {
3213
+ title: "General",
3214
+ description: "Name and description shown across the console.",
3215
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
3216
+ variant: "ghost",
3217
+ disabled: !dirty,
3218
+ onClick: () => setSettings(saved),
3219
+ children: "Discard"
3220
+ }), /* @__PURE__ */ jsx(Button, {
3221
+ type: "submit",
3222
+ disabled: !dirty,
3223
+ children: "Save changes"
3224
+ })] }),
3225
+ children: /* @__PURE__ */ jsxs(Stack, {
3226
+ gap: 4,
3227
+ children: [/* @__PURE__ */ jsxs(Field, { children: [
3228
+ /* @__PURE__ */ jsx(FieldLabel, {
3229
+ required: true,
3230
+ children: "Name"
3231
+ }),
3232
+ /* @__PURE__ */ jsx(Input, {
3233
+ required: true,
3234
+ value: settings.name,
3192
3235
  onValueChange: (v) => setSettings((s) => ({
3193
3236
  ...s,
3194
- description: v
3237
+ name: v
3195
3238
  }))
3196
- })] })]
3197
- })
3239
+ }),
3240
+ /* @__PURE__ */ jsx(FieldDescription, { children: "Lowercase letters, numbers and dashes." })
3241
+ ] }), /* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Description" }), /* @__PURE__ */ jsx(Textarea, {
3242
+ rows: 3,
3243
+ value: settings.description,
3244
+ onValueChange: (v) => setSettings((s) => ({
3245
+ ...s,
3246
+ description: v
3247
+ }))
3248
+ })] })]
3198
3249
  })
3199
- }), /* @__PURE__ */ jsx(DangerZone, {
3200
- description: "These actions affect everyone who uses this resource.",
3201
- actions: dangerActions,
3202
- onConfirm: onDangerAction
3203
- })]
3204
- })
3250
+ })
3251
+ }), /* @__PURE__ */ jsx(DangerZone, {
3252
+ description: "These actions affect everyone who uses this resource.",
3253
+ actions: dangerActions,
3254
+ onConfirm: onDangerAction
3255
+ })]
3205
3256
  })
3206
- ]
3207
- })
3257
+ })
3258
+ ]
3208
3259
  })
3209
3260
  });
3261
+ return shell === null ? content : /* @__PURE__ */ jsx(ConsoleShell, {
3262
+ currentHref: "/deployments",
3263
+ breadcrumbs,
3264
+ ...shell,
3265
+ children: content
3266
+ });
3210
3267
  }
3211
3268
  //#endregion
3212
3269
  //#region src/resource-detail/template.meta.ts
@@ -3215,7 +3272,7 @@ const meta$30 = {
3215
3272
  family: "Resources",
3216
3273
  priority: "P1",
3217
3274
  status: "beta",
3218
- description: "Resource detail with editable Overview, Activity and Settings tabs by default; optional readOnly (boolean, default false) uses only explicit header and detail props and one Overview tab, with optional history (readonly StatusEvent[], default undefined): omitted hides the history card, [] shows the named empty state, and events render exactly as supplied without invented actors. Read-only ignores activity, events, dangerActions, defaultTab, onSaveSettings and onDangerAction; explicit actions and onEditDetails are honored. The Resource sections tablist retains Tab, arrow, Home/End and Enter/Space keyboard navigation; history is a named group containing the Status history list or empty-state region. Uses token styling in dark (default), light and high_contrast. Examples: runtime-with-transition maps state and lastTransitionAt to one actor-free event, runtime-without-transition maps null to [], and realtime-configuration and postgres-table-detail omit history. ConsoleShell retains sample frame defaults unless explicit shell props are supplied, as in the examples.",
3275
+ description: "Resource detail with editable Overview, Activity and Settings tabs by default; optional readOnly (boolean, default false) uses only explicit header and detail props and one Overview tab, with optional history (readonly StatusEvent[], default undefined): omitted hides the history card, [] shows the named empty state, and events render exactly as supplied without invented actors. Read-only ignores activity, events, dangerActions, defaultTab, onSaveSettings and onDangerAction; explicit actions and onEditDetails are honored. The Resource sections tablist retains Tab, arrow, Home/End and Enter/Space keyboard navigation; history is a named group containing the Status history list or empty-state region. Uses token styling in dark (default), light and high_contrast. Examples: runtime-with-transition maps state and lastTransitionAt to one actor-free event, runtime-without-transition maps null to [], and realtime-configuration and postgres-table-detail omit history. Optional labels?: Partial<ResourceDetailLabels> overrides overview, activity, settings and sections (all strings). Unset keys use I18nProvider (en/es); no provider keeps English. Only the tabs and tablist are localized by this template; fixtures and other section text are unchanged. readOnly keeps only the localized overview tab. See the Catalog labels live preview. ConsoleShell retains sample frame defaults when shell is omitted or an object. shell={null} renders only Page and PageHeader, with breadcrumbs in the header; pass breadcrumbs or [] to avoid the editable sample trail. The host owns main and provides ChartLocaleProvider for custom chart locales. Optional tabs?: { value: string; label: string; content: ReactNode }[] replaces the built-in tabs in caller order, even with readOnly; [] hides the tab set. With custom tabs, defaultTab and onTabChange use string values; an omitted or unknown defaultTab selects the first tab. Without tabs they retain ResourceDetailTab values. Custom tabs use the same Base UI keyboard navigation and panel mounting. See the embedded-custom-tabs live preview for shell-less composition inside a host main.",
3219
3276
  layout: "SidebarLayout + Page",
3220
3277
  blocks: [
3221
3278
  "app-sidebar",
@@ -13264,7 +13321,7 @@ const shortcutGroups = [
13264
13321
  {
13265
13322
  id: "palette",
13266
13323
  description: "Open the command palette",
13267
- keys: ["⌘", "K"]
13324
+ keys: ["mod", "K"]
13268
13325
  },
13269
13326
  {
13270
13327
  id: "help",
@@ -13274,13 +13331,13 @@ const shortcutGroups = [
13274
13331
  {
13275
13332
  id: "sidebar",
13276
13333
  description: "Collapse or expand the sidebar",
13277
- keys: ["⌘", "B"]
13334
+ keys: ["mod", "B"]
13278
13335
  },
13279
13336
  {
13280
13337
  id: "theme",
13281
13338
  description: "Switch theme",
13282
13339
  keys: [
13283
- "⌘",
13340
+ "mod",
13284
13341
  "⇧",
13285
13342
  "L"
13286
13343
  ]
@@ -13355,12 +13412,12 @@ const shortcutGroups = [
13355
13412
  {
13356
13413
  id: "save",
13357
13414
  description: "Save changes",
13358
- keys: ["⌘", "S"]
13415
+ keys: ["mod", "S"]
13359
13416
  },
13360
13417
  {
13361
13418
  id: "submit",
13362
13419
  description: "Submit a form or comment",
13363
- keys: ["⌘", "↵"]
13420
+ keys: ["mod", "↵"]
13364
13421
  },
13365
13422
  {
13366
13423
  id: "cancel",
@@ -13370,18 +13427,28 @@ const shortcutGroups = [
13370
13427
  {
13371
13428
  id: "undo",
13372
13429
  description: "Undo",
13373
- keys: ["⌘", "Z"]
13430
+ keys: ["mod", "Z"]
13374
13431
  }
13375
13432
  ]
13376
13433
  }
13377
13434
  ];
13378
- /** Groups with only the shortcuts whose description or keys match the query. */
13379
- function filterShortcuts(groups, query, locale) {
13435
+ /** Groups matching descriptions, titles or keys; mod accepts both Command and Ctrl spellings. */
13436
+ function filterShortcuts(groups, query, locale, os = "undetermined") {
13380
13437
  const q = query.trim().toLowerCase();
13381
13438
  if (!q) return [...groups];
13439
+ const modifier = os === "macos" || os === "ios" ? "⌘" : "Ctrl";
13440
+ const alias = modifier === "⌘" ? "Ctrl" : "⌘";
13441
+ const matchesKeys = (shortcut) => {
13442
+ const keys = [...shortcut.keys, ...shortcut.then ?? []];
13443
+ return [
13444
+ "mod",
13445
+ modifier,
13446
+ alias
13447
+ ].some((mod) => keys.map((key) => key === "mod" ? mod : key).join(" ").toLowerCase() === q);
13448
+ };
13382
13449
  return groups.map((g) => ({
13383
13450
  ...g,
13384
- shortcuts: g.shortcuts.filter((s) => matchText(s.description, query, { locale }) || matchText(g.title, query, { locale }) || [...s.keys, ...s.then ?? []].join(" ").toLowerCase() === q)
13451
+ shortcuts: g.shortcuts.filter((s) => matchText(s.description, query, { locale }) || matchText(g.title, query, { locale }) || matchesKeys(s))
13385
13452
  })).filter((g) => g.shortcuts.length > 0);
13386
13453
  }
13387
13454
  //#endregion
@@ -13389,9 +13456,10 @@ function filterShortcuts(groups, query, locale) {
13389
13456
  /** Searchable shortcut sheet shared by the page and the dialog. */
13390
13457
  function ShortcutSheet({ groups, headingLevel = 2, columns = 2, autoFocus }) {
13391
13458
  const { locale } = useI18n();
13459
+ const os = useOs();
13392
13460
  const [query, setQuery] = useState("");
13393
13461
  const countId = useId();
13394
- const visible = filterShortcuts(groups, query, locale);
13462
+ const visible = filterShortcuts(groups, query, locale, os);
13395
13463
  const total = visible.reduce((n, g) => n + g.shortcuts.length, 0);
13396
13464
  return /* @__PURE__ */ jsxs(Stack, {
13397
13465
  gap: 5,