@agent-native/core 0.145.6 → 0.145.8

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.
Files changed (95) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
  3. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
  4. package/corpus/templates/analytics/changelog/2026-08-07-analytics-no-longer-stalls-under-bursts-of-event-ingest-key-.md +6 -0
  5. package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
  6. package/corpus/templates/analytics/netlify.toml +1 -1
  7. package/corpus/templates/analytics/package.json +1 -0
  8. package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
  9. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +9 -0
  10. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +65 -10
  11. package/corpus/templates/analytics/server/lib/session-replay.ts +5 -5
  12. package/corpus/templates/analytics/server/plugins/db.ts +14 -3
  13. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
  14. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
  15. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
  16. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  17. package/corpus/templates/chat/AGENTS.md +3 -0
  18. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
  19. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
  20. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
  21. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
  22. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
  23. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
  24. package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
  25. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
  26. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
  27. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
  28. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
  29. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
  30. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
  31. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
  32. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
  33. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
  34. package/dist/client/components/ApiKeySettings.js +2 -5
  35. package/dist/client/org/TeamPage.js +4 -2
  36. package/dist/client/settings/AccountSettingsCard.js +2 -1
  37. package/dist/client/settings/AgentsSection.js +2 -1
  38. package/dist/client/settings/SecretsSection.js +3 -2
  39. package/dist/client/settings/SettingsPanel.js +6 -12
  40. package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
  41. package/dist/client/settings/SettingsSkeleton.js +12 -0
  42. package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
  43. package/dist/client/settings/index.d.ts +1 -0
  44. package/dist/client/settings/index.js +1 -0
  45. package/dist/client/use-db-sync.js +4 -0
  46. package/dist/collab/routes.d.ts +1 -1
  47. package/dist/db/client.d.ts +13 -7
  48. package/dist/db/client.js +124 -7
  49. package/dist/db/ddl-guard.js +3 -1
  50. package/dist/db/ensure-additive-columns.d.ts +2 -0
  51. package/dist/db/ensure-additive-columns.js +5 -2
  52. package/dist/db/index.d.ts +2 -2
  53. package/dist/db/index.js +2 -2
  54. package/dist/db/migrations.d.ts +11 -54
  55. package/dist/db/migrations.js +70 -0
  56. package/dist/jobs/index.d.ts +1 -0
  57. package/dist/jobs/index.js +1 -0
  58. package/dist/jobs/run-history.d.ts +4 -0
  59. package/dist/jobs/run-history.js +44 -0
  60. package/dist/notifications/routes.d.ts +3 -3
  61. package/dist/observability/routes.d.ts +3 -3
  62. package/dist/progress/routes.d.ts +1 -1
  63. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  64. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  65. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  66. package/dist/secrets/routes.d.ts +6 -6
  67. package/dist/server/agent-chat-plugin.js +6 -4
  68. package/dist/server/auth-plugin.js +5 -0
  69. package/dist/server/better-auth-instance.js +0 -61
  70. package/dist/server/better-auth-migrations.d.ts +8 -0
  71. package/dist/server/better-auth-migrations.js +179 -0
  72. package/dist/server/edge.d.ts +2 -0
  73. package/dist/server/edge.js +2 -0
  74. package/dist/server/index.d.ts +2 -0
  75. package/dist/server/index.js +2 -0
  76. package/dist/server/release-migrations.d.ts +8 -0
  77. package/dist/server/release-migrations.js +24 -0
  78. package/dist/server/sweep-runtime.d.ts +1 -1
  79. package/dist/server/sweep-runtime.js +15 -0
  80. package/dist/server/transcribe-voice.d.ts +1 -1
  81. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  82. package/dist/templates/chat/AGENTS.md +3 -0
  83. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  84. package/dist/templates/default/AGENTS.md +3 -0
  85. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  86. package/dist/templates/workspace-core/AGENTS.md +3 -0
  87. package/dist/templates/workspace-root/AGENTS.md +3 -0
  88. package/package.json +1 -1
  89. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  90. package/src/templates/chat/AGENTS.md +3 -0
  91. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  92. package/src/templates/default/AGENTS.md +3 -0
  93. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  94. package/src/templates/workspace-core/AGENTS.md +3 -0
  95. package/src/templates/workspace-root/AGENTS.md +3 -0
@@ -199,7 +199,11 @@ as an operator would use it repeatedly:
199
199
  - Use full `Card` composition when the content has a title, description, content, or actions. Do not dump complex cards into a single `CardContent`.
200
200
  - Use `ToggleGroup` for small option sets, `Switch` for binary settings, `Checkbox` for multi-select, `RadioGroup` for one-of-many, and `Slider`/inputs for numeric values.
201
201
  - For forms, prefer the app's existing shadcn form pattern. If newer `Field`, `FieldGroup`, or `InputGroup` primitives are installed or appropriate to add, use them instead of raw layout divs.
202
- - Loading states use `Skeleton`, `Progress`, `Spinner`, or the app's existing loading primitives. Empty states should have one clear next action.
202
+ - Page and section data loading uses layout-matching `Skeleton` geometry. Do
203
+ not show generic "Loading..." text for content loads; reserve `Spinner` for
204
+ brief mutations, uploads, and progress actions. Use the app's existing
205
+ loading primitive when it is a genuine design-system adapter. Empty states
206
+ should have one clear next action.
203
207
 
204
208
  ## Anti-Patterns
205
209
 
@@ -199,7 +199,11 @@ as an operator would use it repeatedly:
199
199
  - Use full `Card` composition when the content has a title, description, content, or actions. Do not dump complex cards into a single `CardContent`.
200
200
  - Use `ToggleGroup` for small option sets, `Switch` for binary settings, `Checkbox` for multi-select, `RadioGroup` for one-of-many, and `Slider`/inputs for numeric values.
201
201
  - For forms, prefer the app's existing shadcn form pattern. If newer `Field`, `FieldGroup`, or `InputGroup` primitives are installed or appropriate to add, use them instead of raw layout divs.
202
- - Loading states use `Skeleton`, `Progress`, `Spinner`, or the app's existing loading primitives. Empty states should have one clear next action.
202
+ - Page and section data loading uses layout-matching `Skeleton` geometry. Do
203
+ not show generic "Loading..." text for content loads; reserve `Spinner` for
204
+ brief mutations, uploads, and progress actions. Use the app's existing
205
+ loading primitive when it is a genuine design-system adapter. Empty states
206
+ should have one clear next action.
203
207
 
204
208
  ## Anti-Patterns
205
209
 
@@ -199,7 +199,11 @@ as an operator would use it repeatedly:
199
199
  - Use full `Card` composition when the content has a title, description, content, or actions. Do not dump complex cards into a single `CardContent`.
200
200
  - Use `ToggleGroup` for small option sets, `Switch` for binary settings, `Checkbox` for multi-select, `RadioGroup` for one-of-many, and `Slider`/inputs for numeric values.
201
201
  - For forms, prefer the app's existing shadcn form pattern. If newer `Field`, `FieldGroup`, or `InputGroup` primitives are installed or appropriate to add, use them instead of raw layout divs.
202
- - Loading states use `Skeleton`, `Progress`, `Spinner`, or the app's existing loading primitives. Empty states should have one clear next action.
202
+ - Page and section data loading uses layout-matching `Skeleton` geometry. Do
203
+ not show generic "Loading..." text for content loads; reserve `Spinner` for
204
+ brief mutations, uploads, and progress actions. Use the app's existing
205
+ loading primitive when it is a genuine design-system adapter. Empty states
206
+ should have one clear next action.
203
207
 
204
208
  ## Anti-Patterns
205
209
 
@@ -199,7 +199,11 @@ as an operator would use it repeatedly:
199
199
  - Use full `Card` composition when the content has a title, description, content, or actions. Do not dump complex cards into a single `CardContent`.
200
200
  - Use `ToggleGroup` for small option sets, `Switch` for binary settings, `Checkbox` for multi-select, `RadioGroup` for one-of-many, and `Slider`/inputs for numeric values.
201
201
  - For forms, prefer the app's existing shadcn form pattern. If newer `Field`, `FieldGroup`, or `InputGroup` primitives are installed or appropriate to add, use them instead of raw layout divs.
202
- - Loading states use `Skeleton`, `Progress`, `Spinner`, or the app's existing loading primitives. Empty states should have one clear next action.
202
+ - Page and section data loading uses layout-matching `Skeleton` geometry. Do
203
+ not show generic "Loading..." text for content loads; reserve `Spinner` for
204
+ brief mutations, uploads, and progress actions. Use the app's existing
205
+ loading primitive when it is a genuine design-system adapter. Empty states
206
+ should have one clear next action.
203
207
 
204
208
  ## Anti-Patterns
205
209
 
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Skeleton } from "@agent-native/toolkit/design-system";
2
3
  import { useState, useEffect, useCallback } from "react";
3
4
  import { agentNativePath } from "../api-path.js";
4
5
  /**
@@ -77,7 +78,7 @@ export function ApiKeySettings({ settingsPath: _settingsPath = "/settings", }) {
77
78
  };
78
79
  const pendingCount = Object.values(values).filter((v) => v.trim() !== "").length;
79
80
  if (loading) {
80
- return (_jsx("div", { style: styles.container, children: _jsx("p", { style: styles.loadingText, children: "Loading API key status..." }) }));
81
+ return (_jsxs("div", { style: styles.container, role: "status", "aria-busy": "true", "aria-label": "Loading API key status", children: [_jsx(Skeleton, { className: "h-3 w-40" }), _jsx(Skeleton, { className: "mt-3 h-8 w-full" })] }));
81
82
  }
82
83
  if (error) {
83
84
  return (_jsxs("div", { style: styles.container, children: [_jsx("p", { style: styles.errorText, children: error }), _jsx("button", { onClick: fetchStatus, style: styles.retryButton, children: "Retry" })] }));
@@ -110,10 +111,6 @@ const styles = {
110
111
  opacity: 0.6,
111
112
  margin: "0 0 16px 0",
112
113
  },
113
- loadingText: {
114
- fontSize: "13px",
115
- opacity: 0.5,
116
- },
117
114
  errorText: {
118
115
  fontSize: "13px",
119
116
  color: "#ef4444",
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Skeleton } from "@agent-native/toolkit/design-system";
2
3
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@agent-native/toolkit/ui/alert-dialog";
3
4
  import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
4
5
  import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious, } from "@agent-native/toolkit/ui/pagination";
@@ -9,6 +10,7 @@ import { forwardRef, useEffect, useMemo, useRef, useState, } from "react";
9
10
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../components/ui/tooltip.js";
10
11
  import { useT } from "../i18n.js";
11
12
  import { SettingsGroup, SettingsRow } from "../settings/SettingsRow.js";
13
+ import { SettingsSkeleton } from "../settings/SettingsSkeleton.js";
12
14
  import { cn } from "../utils.js";
13
15
  import { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useSetOrgDomain, useSetOrgWorkspaceUrl, useSetOrgAuthProvider, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useJoinByDomain, useAppRoles, useSetAppMemberRole, ORG_MEMBER_PAGE_SIZE, } from "./hooks.js";
14
16
  const Button = forwardRef(({ className, ...props }, ref) => (_jsx(ToolkitButton, { ref: ref, variant: "ghost", className: cn("h-auto p-0 hover:bg-transparent hover:text-inherit active:scale-100 [&_svg]:!size-auto", className), ...props })));
@@ -139,7 +141,7 @@ function MembersTableCard({ members, totalMembers, pendingInvites, isLoadingMemb
139
141
  a.email.toLowerCase(),
140
142
  a.role,
141
143
  ]));
142
- return (_jsxs("section", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: [_jsxs("div", { className: "flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium", children: t("org.members") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("org.memberCount", { count: totalMembers }) })] }), canInvite && !showInviteForm && (_jsxs(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => setShowInviteForm(true), className: "inline-flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconUserPlus, { size: 14 }), t("org.inviteMembers")] }))] }), canInvite && showInviteForm && (_jsx("div", { className: "border-b border-border p-4", children: _jsx(BulkInviteForm, { currentUserRole: currentUserRole, onClose: () => setShowInviteForm(false) }) })), _jsx("div", { className: "divide-y divide-border/60 border-t border-border/60", children: isLoadingMembers && members.length === 0 ? ([0, 1, 2].map((i) => (_jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx("div", { className: "size-8 animate-pulse rounded-full bg-muted" }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "h-3.5 animate-pulse rounded bg-muted", style: { width: `${180 + i * 48}px` } }), _jsx("div", { className: "h-3 w-20 animate-pulse rounded bg-muted" })] })] }, i)))) : members.length === 0 && pendingInvites.length === 0 ? (_jsx("div", { className: "px-5 py-10 text-center text-sm text-muted-foreground", children: t("org.noMembers") })) : (_jsxs(_Fragment, { children: [members.map((m) => (_jsx(MemberRow, { email: m.email, role: m.role, isCurrentUser: m.email === currentUserEmail, currentUserRole: currentUserRole, appRoles: appRoles, appRole: appRoleByEmail.get(m.email.toLowerCase()) ?? null, canManageAppRoles: Boolean(appRoleData?.canManage) }, m.email))), pendingInvites.map((inv) => (_jsx(PendingInviteRow, { invite: inv }, inv.id)))] })) }), (memberOffset > 0 || hasNextPage) && (_jsx(MemberPagination, { memberOffset: memberOffset, totalMembers: totalMembers, hasNextPage: hasNextPage, nextMemberOffset: nextMemberOffset, isFetchingMembers: isFetchingMembers, onMemberPageChange: onMemberPageChange }))] }));
144
+ return (_jsxs("section", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: [_jsxs("div", { className: "flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium", children: t("org.members") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("org.memberCount", { count: totalMembers }) })] }), canInvite && !showInviteForm && (_jsxs(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => setShowInviteForm(true), className: "inline-flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconUserPlus, { size: 14 }), t("org.inviteMembers")] }))] }), canInvite && showInviteForm && (_jsx("div", { className: "border-b border-border p-4", children: _jsx(BulkInviteForm, { currentUserRole: currentUserRole, onClose: () => setShowInviteForm(false) }) })), _jsx("div", { className: "divide-y divide-border/60 border-t border-border/60", children: isLoadingMembers && members.length === 0 ? (_jsx("div", { role: "status", "aria-busy": "true", "aria-label": "Loading members", children: ["w-44", "w-56", "w-64"].map((nameWidth) => (_jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx(Skeleton, { className: "size-8 rounded-full bg-muted" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: cn("h-3.5 bg-muted", nameWidth) }), _jsx(Skeleton, { className: "h-3 w-20 bg-muted" })] })] }, nameWidth))) })) : members.length === 0 && pendingInvites.length === 0 ? (_jsx("div", { className: "px-5 py-10 text-center text-sm text-muted-foreground", children: t("org.noMembers") })) : (_jsxs(_Fragment, { children: [members.map((m) => (_jsx(MemberRow, { email: m.email, role: m.role, isCurrentUser: m.email === currentUserEmail, currentUserRole: currentUserRole, appRoles: appRoles, appRole: appRoleByEmail.get(m.email.toLowerCase()) ?? null, canManageAppRoles: Boolean(appRoleData?.canManage) }, m.email))), pendingInvites.map((inv) => (_jsx(PendingInviteRow, { invite: inv }, inv.id)))] })) }), (memberOffset > 0 || hasNextPage) && (_jsx(MemberPagination, { memberOffset: memberOffset, totalMembers: totalMembers, hasNextPage: hasNextPage, nextMemberOffset: nextMemberOffset, isFetchingMembers: isFetchingMembers, onMemberPageChange: onMemberPageChange }))] }));
143
145
  }
144
146
  function MemberPagination({ memberOffset, totalMembers, hasNextPage, nextMemberOffset, isFetchingMembers, onMemberPageChange, }) {
145
147
  const t = useT();
@@ -547,7 +549,7 @@ function A2ASecretSection({ isSet }) {
547
549
  export function TeamPage({ layout, title, showTitle = true, createOrgDescription, className, appRoles, }) {
548
550
  const t = useT();
549
551
  const { data: org, isLoading } = useOrg();
550
- const content = (_jsxs("div", { className: `w-full space-y-6 ${className ?? ""}`, children: [showTitle ? (_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title ?? t("org.team") })) : null, isLoading && (_jsx("section", { className: "rounded-lg border border-border bg-card p-6", children: _jsx("div", { className: "text-sm text-muted-foreground", children: t("org.loading") }) })), !isLoading && (_jsxs(_Fragment, { children: [_jsx(PendingInvitationsCard, {}), org?.domainMatches && org.domainMatches.length > 0 && (_jsx(JoinByDomainCard, { matches: org.domainMatches })), !org?.orgId ? (_jsx(CreateOrgCard, { description: createOrgDescription })) : (_jsx(MembersCard, { appRoles: appRoles }))] }))] }));
552
+ const content = (_jsxs("div", { className: `w-full space-y-6 ${className ?? ""}`, children: [showTitle ? (_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title ?? t("org.team") })) : null, isLoading && (_jsx("section", { className: "rounded-lg border border-border bg-card p-6", children: _jsx(SettingsSkeleton, { lines: 3 }) })), !isLoading && (_jsxs(_Fragment, { children: [_jsx(PendingInvitationsCard, {}), org?.domainMatches && org.domainMatches.length > 0 && (_jsx(JoinByDomainCard, { matches: org.domainMatches })), !org?.orgId ? (_jsx(CreateOrgCard, { description: createOrgDescription })) : (_jsx(MembersCard, { appRoles: appRoles }))] }))] }));
551
553
  const wrapped = (_jsx(TooltipProvider, { delayDuration: 200, children: content }));
552
554
  return layout ? _jsx(_Fragment, { children: layout(wrapped) }) : wrapped;
553
555
  }
@@ -11,6 +11,7 @@ import { useSession } from "../use-session.js";
11
11
  import { cn } from "../utils.js";
12
12
  import { SchedulingTimezoneField } from "./SchedulingTimezoneField.js";
13
13
  import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
14
+ import { SettingsSkeleton } from "./SettingsSkeleton.js";
14
15
  function profileInitials(name) {
15
16
  return (name
16
17
  .split(/[ @._-]+/)
@@ -74,7 +75,7 @@ function PasswordSettings() {
74
75
  const hasPassword = authMethods.data?.hasPassword ?? false;
75
76
  if (!session?.email)
76
77
  return null;
77
- const passwordForm = isLoading ? (_jsx("p", { className: "text-xs text-muted-foreground", children: t("settings.passwordStatusLoading") })) : authMethods.error ? (_jsx("p", { className: "text-xs text-destructive", children: t("settings.passwordSaveError") })) : (_jsxs("div", { className: "space-y-3", children: [hasPassword && (_jsx(TextField, { id: "agent-native-current-password", type: "password", label: t("settings.passwordCurrentLabel"), value: currentPassword, onChange: (value) => {
78
+ const passwordForm = isLoading ? (_jsx(SettingsSkeleton, { lines: 2 })) : authMethods.error ? (_jsx("p", { className: "text-xs text-destructive", children: t("settings.passwordSaveError") })) : (_jsxs("div", { className: "space-y-3", children: [hasPassword && (_jsx(TextField, { id: "agent-native-current-password", type: "password", label: t("settings.passwordCurrentLabel"), value: currentPassword, onChange: (value) => {
78
79
  clearStatus();
79
80
  setCurrentPassword(value);
80
81
  }, placeholder: t("settings.passwordPlaceholder"), autoComplete: "current-password", disabled: isPending })), _jsx(TextField, { id: "agent-native-new-password", type: "password", label: t("settings.passwordNewLabel"), value: newPassword, onChange: (value) => {
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Skeleton } from "@agent-native/toolkit/design-system";
2
3
  import { ButtonBase as ToolkitButtonBase } from "@agent-native/toolkit/ui/button";
3
4
  import { IconPlus, IconTrash, IconX, IconCheck, IconLoader2, IconAlertTriangle, IconExternalLink, IconRefresh, IconTopologyRing2, } from "@tabler/icons-react";
4
5
  import { useState, useEffect, useRef, useCallback } from "react";
@@ -403,7 +404,7 @@ export function AgentsSection() {
403
404
  }, className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [showAdd ? _jsx(IconX, { size: 13 }) : _jsx(IconPlus, { size: 13 }), showAdd ? "Cancel" : "Connect agent"] }), showAdd && (_jsx(AgentAddPopover, { initialName: prefill?.name, initialUrl: prefill?.url, initialDescription: prefill?.description, secretSet: org?.a2aSecretSet, syncSecret: syncSecret, onAdd: handleAdd, onClose: () => {
404
405
  setShowAdd(false);
405
406
  setPrefill(null);
406
- } }))] }) }), _jsx(A2ASecretStatusRow, { org: org, syncSecret: syncSecret }), loading ? (_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "h-6 w-full rounded bg-muted/50 animate-pulse" }), _jsx("div", { className: "h-6 w-3/4 rounded bg-muted/50 animate-pulse" })] })) : agents.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center rounded-xl border border-border/70 bg-card px-5 py-8 text-center", children: [_jsx("span", { className: "mb-2 flex size-9 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(IconTopologyRing2, { size: 17 }) }), _jsx("p", { className: "text-sm font-medium text-foreground", children: "No connected agents yet" }), _jsx("p", { className: "mt-1 max-w-sm text-xs leading-5 text-muted-foreground", children: "Connect an A2A agent to delegate work from chat." }), _jsxs("button", { onClick: () => setShowAdd(true), className: "mt-4 inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), "Connect agent"] })] })) : (_jsx("div", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: _jsx("div", { className: "divide-y divide-border/60 px-4", children: agents.map((agent) => {
407
+ } }))] }) }), _jsx(A2ASecretStatusRow, { org: org, syncSecret: syncSecret }), loading ? (_jsxs("div", { className: "space-y-1.5", role: "status", "aria-busy": "true", "aria-label": "Loading connected agents", children: [_jsx(Skeleton, { className: "h-6 w-full bg-muted/50" }), _jsx(Skeleton, { className: "h-6 w-3/4 bg-muted/50" })] })) : agents.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center rounded-xl border border-border/70 bg-card px-5 py-8 text-center", children: [_jsx("span", { className: "mb-2 flex size-9 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(IconTopologyRing2, { size: 17 }) }), _jsx("p", { className: "text-sm font-medium text-foreground", children: "No connected agents yet" }), _jsx("p", { className: "mt-1 max-w-sm text-xs leading-5 text-muted-foreground", children: "Connect an A2A agent to delegate work from chat." }), _jsxs("button", { onClick: () => setShowAdd(true), className: "mt-4 inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), "Connect agent"] })] })) : (_jsx("div", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: _jsx("div", { className: "divide-y divide-border/60 px-4", children: agents.map((agent) => {
407
408
  const probe = probeById?.get(getRemoteAgentIdFromPath(agent.path).toLowerCase());
408
409
  const dotState = !probe
409
410
  ? null
@@ -14,6 +14,7 @@ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popov
14
14
  import { Tooltip, TooltipContent, TooltipTrigger, } from "../components/ui/tooltip.js";
15
15
  import { useT } from "../i18n.js";
16
16
  import { cn } from "../utils.js";
17
+ import { SettingsSkeleton } from "./SettingsSkeleton.js";
17
18
  const Button = React.forwardRef(({ className, ...props }, ref) => (_jsx(ToolkitButton, { ref: ref, variant: "ghost", className: cn("h-auto p-0 hover:bg-transparent hover:text-inherit active:scale-100 [&_svg]:!size-auto", className), ...props })));
18
19
  Button.displayName = "SecretsPrimitiveButton";
19
20
  const ENDPOINT = agentNativePath("/_agent-native/secrets");
@@ -62,7 +63,7 @@ export function SecretsSection({ focusKey }) {
62
63
  return (_jsxs("p", { className: "text-[10px] text-red-500", children: ["Failed to load secrets: ", error] }));
63
64
  }
64
65
  if (secrets === null) {
65
- return (_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(IconLoader2, { size: 10, className: "animate-spin" }), "Loading\u2026"] }));
66
+ return _jsx(SettingsSkeleton, { lines: 2 });
66
67
  }
67
68
  if (secrets.length === 0) {
68
69
  return (_jsxs("div", { className: "space-y-3", children: [_jsx(KeysHeader, { onCustomKey: () => setCustomKeyOpen(true) }), _jsx(AdHocKeysSection, { showForm: customKeyOpen, onShowFormChange: setCustomKeyOpen, showEmptyState: true })] }));
@@ -356,7 +357,7 @@ function AdHocKeysSection({ showForm, onShowFormChange, showEmptyState, }) {
356
357
  }
357
358
  }, "aria-label": t("secrets.scopeLabel"), description: t(formScope === "user"
358
359
  ? "secrets.scopePersonalDescription"
359
- : "secrets.scopeWorkspaceDescription"), className: "text-[11px]" }), _jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: resetForm, className: "rounded border border-border px-2 py-1 text-[10px] font-medium text-muted-foreground hover:text-foreground", children: "Cancel" }), _jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: handleAdd, disabled: !formName.trim() || !formValue.trim() || formBusy, className: "inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium disabled:opacity-40", style: { backgroundColor: "#00B5FF", color: "white" }, children: formBusy ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Save") })] }), formError && _jsx("p", { className: "text-[10px] text-red-500", children: formError })] })), loading ? (_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(IconLoader2, { size: 10, className: "animate-spin" }), "Loading..."] })) : keys.length === 0 && !showForm && showEmptyState ? (_jsx("p", { className: "text-[10px] text-muted-foreground", children: "No keys added yet." })) : keys.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border border-border", children: keys.map((key) => (_jsx("div", { className: "border-b border-border px-2.5 py-2 last:border-b-0", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[11px] font-medium text-foreground font-mono truncate", children: key.name }), _jsx("span", { className: `rounded-full px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide ${key.scope === "workspace"
360
+ : "secrets.scopeWorkspaceDescription"), className: "text-[11px]" }), _jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: resetForm, className: "rounded border border-border px-2 py-1 text-[10px] font-medium text-muted-foreground hover:text-foreground", children: "Cancel" }), _jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: handleAdd, disabled: !formName.trim() || !formValue.trim() || formBusy, className: "inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium disabled:opacity-40", style: { backgroundColor: "#00B5FF", color: "white" }, children: formBusy ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Save") })] }), formError && _jsx("p", { className: "text-[10px] text-red-500", children: formError })] })), loading ? (_jsx(SettingsSkeleton, { lines: 2 })) : keys.length === 0 && !showForm && showEmptyState ? (_jsx("p", { className: "text-[10px] text-muted-foreground", children: "No keys added yet." })) : keys.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border border-border", children: keys.map((key) => (_jsx("div", { className: "border-b border-border px-2.5 py-2 last:border-b-0", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[11px] font-medium text-foreground font-mono truncate", children: key.name }), _jsx("span", { className: `rounded-full px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide ${key.scope === "workspace"
360
361
  ? "bg-blue-500/15 text-blue-500"
361
362
  : "bg-accent/60 text-muted-foreground"}`, children: key.scope === "workspace" ? "workspace" : "personal" })] }), key.description && (_jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: key.description })), _jsx("div", { className: "flex items-center gap-2 text-[10px] text-muted-foreground mt-0.5", children: _jsxs("span", { children: ["Ending in", " ", _jsx("code", { className: "rounded bg-background px-1 py-0.5 text-foreground", children: key.last4 })] }) })] }), _jsx("div", { className: "shrink-0", children: confirmDeleteName === key.name ? (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { type: "button", intent: "danger", emphasis: "solid", onClick: () => handleDelete(key.name), disabled: deletingName === key.name, className: "rounded px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide bg-red-500/15 text-red-500 hover:bg-red-500/25 disabled:opacity-40", children: deletingName === key.name ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Confirm") }), _jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: () => setConfirmDeleteName(null), className: "rounded px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide bg-accent/60 text-muted-foreground hover:text-foreground", children: "Cancel" })] })) : (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", intent: "danger", emphasis: "ghost", onClick: () => setConfirmDeleteName(key.name), className: "text-muted-foreground hover:text-red-500", children: _jsx(IconTrash, { size: 12 }) }) }), _jsx(TooltipContent, { children: "Delete" })] })) })] }) }, `${key.scope}-${key.name}`))) })) : null, toast && (_jsx("p", { className: `text-[10px] ${toast.kind === "ok" ? "text-green-500" : "text-red-500"}`, children: toast.text }))] }));
362
363
  }
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Checkbox, Picker, TextField, } from "@agent-native/toolkit/design-system";
2
+ import { Checkbox, Picker, Skeleton, TextField, } from "@agent-native/toolkit/design-system";
3
3
  import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
4
4
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@agent-native/toolkit/ui/command";
5
5
  import { IconChevronDown, IconChevronRight, IconCheck, IconExternalLink, IconBrain, IconHierarchy2, IconBrowser, IconGitBranch, IconCloud, IconDatabase, IconFolder, IconShield, IconPlugConnected, IconTopologyRing2, IconLoader2, IconUpload, IconCoin, IconMail, IconKey, IconMicrophone, IconEyeOff, IconBolt, IconGauge, IconApps, IconUsersGroup, IconTool, } from "@tabler/icons-react";
@@ -26,6 +26,7 @@ import { ExtensionsSettingsContent } from "./ExtensionsSettingsContent.js";
26
26
  import { SecretsSection } from "./SecretsSection.js";
27
27
  import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
28
28
  import { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
29
+ import { SettingsLoadingRow, SettingsSkeleton } from "./SettingsSkeleton.js";
29
30
  import { UsageSection } from "./UsageSection.js";
30
31
  import { useBuilderConnectFlow, useBuilderStatus, } from "./useBuilderStatus.js";
31
32
  import { settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
@@ -37,13 +38,6 @@ ManageButton.displayName = "SettingsManageButton";
37
38
  const IntegrationsPanel = lazy(() => import("../integrations/IntegrationsPanel.js").then((m) => ({
38
39
  default: m.IntegrationsPanel,
39
40
  })));
40
- // ─── Shared helpers ─────────────────────────────────────────────────────────
41
- function SettingsSkeleton({ lines = 3 }) {
42
- return (_jsx("div", { className: "space-y-3 animate-pulse", children: Array.from({ length: lines }, (_, i) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "h-3 rounded bg-muted-foreground/10", style: { width: i === 0 ? "30%" : i === 1 ? "100%" : "60%" } }), i < 2 && (_jsx("div", { className: "h-9 rounded-md border border-border bg-muted-foreground/5" }))] }, i))) }));
43
- }
44
- function SettingsLoadingRow({ label, description, controlCount = 1, }) {
45
- return (_jsxs("div", { className: "flex items-center justify-between gap-4 px-5 py-4 sm:px-6", children: [_jsxs("div", { className: "min-w-0 space-y-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: label }), description ? (_jsx("p", { className: "text-sm leading-5 text-muted-foreground", children: description })) : null] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", "aria-hidden": "true", children: Array.from({ length: controlCount }, (_, index) => (_jsx("div", { className: cn("h-9 animate-pulse rounded-md border border-border bg-muted-foreground/10", index === 0 ? "w-28" : "w-20") }, index))) })] }));
46
- }
47
41
  const CONTROL_STYLE = {
48
42
  fontSize: 12,
49
43
  lineHeight: 1,
@@ -553,7 +547,7 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
553
547
  }
554
548
  catch { }
555
549
  };
556
- return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", subtitle: "Use Builder.io free credits or your own LLM provider.", required: true, connected: initialLoading ? undefined : anyKeyConfigured, grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { label: "Connect an LLM", description: "Use Builder.io free credits or your own provider.", controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
550
+ return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", subtitle: "Use Builder.io free credits or your own LLM provider.", required: true, connected: initialLoading ? undefined : anyKeyConfigured, grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
557
551
  ? "flex items-center justify-between gap-4 px-5 py-4 sm:px-6"
558
552
  : "space-y-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Connect an LLM" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: "Use Builder.io free credits or your own provider." })] })), _jsxs("div", { className: cn(isPage && "flex flex-wrap items-center justify-end gap-2"), children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: isPage }), builderConnected && !isPage && (_jsxs(Button, { type: "button", intent: "neutral", emphasis: "ghost", "aria-expanded": manualSetupOpen, "aria-controls": "llm-manual-setup", onClick: () => setManualSetupOpen((open) => !open), className: cn("inline-flex items-center gap-1 px-0.5 text-muted-foreground hover:text-foreground", isPage ? "text-xs" : "text-[10px]"), children: [t("agentPanel.addOwnKeys", {
559
553
  defaultValue: "Add your own keys",
@@ -764,7 +758,7 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
764
758
  setSaving(false);
765
759
  }
766
760
  };
767
- return (_jsx(SettingsSection, { id: settingsSectionDomId("app-models"), icon: _jsx(IconApps, { size: 14 }), title: "App Default Model", subtitle: "Choose the model used by this app by default.", connected: loading ? undefined : hasAppDefault, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, { label: "Default model", description: "Choose the model used by this app by default." })) : settings ? (isPage ? (_jsxs(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Default model", description: hasAppDefault
761
+ return (_jsx(SettingsSection, { id: settingsSectionDomId("app-models"), icon: _jsx(IconApps, { size: 14 }), title: "App Default Model", subtitle: "Choose the model used by this app by default.", connected: loading ? undefined : hasAppDefault, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {})) : settings ? (isPage ? (_jsxs(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Default model", description: hasAppDefault
768
762
  ? `Used by ${friendlyAppName(settings.appId) || "this app"} · ${scopeLabel}.`
769
763
  : "Uses the global LLM default.", status: _jsx("span", { className: "text-xs text-muted-foreground", children: settings.source }), control: _jsx(AppDefaultModelPicker, { engines: settings.engines, value: hasAppDefault
770
764
  ? `${selectedEngine}::${selectedModel}`
@@ -987,7 +981,7 @@ function AgentLimitsSectionInner({ grouped = false, open, onToggle, }) {
987
981
  ? undefined
988
982
  : settings
989
983
  ? settings.maxIterations !== settings.defaultMaxIterations
990
- : false, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, { label: "Max iterations", description: "Set how long a response can work before pausing." })) : settings ? (isPage ? (_jsx(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Max iterations", description: `Default ${settings.defaultMaxIterations.toLocaleString()} · applies to ${scopeLabel}.`, status: _jsx("span", { className: "text-xs text-muted-foreground", children: settings.source }), control: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(ManageButton, {}) }), _jsx(PopoverContent, { align: "end", sideOffset: 6, className: "w-80 p-4", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Max iterations" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Choose how long a response can work before pausing." })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("input", { type: "number", min: settings.minMaxIterations, max: settings.maxMaxIterations, value: value, disabled: !settings.canUpdate || saving, onChange: (event) => {
984
+ : false, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {})) : settings ? (isPage ? (_jsx(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Max iterations", description: `Default ${settings.defaultMaxIterations.toLocaleString()} · applies to ${scopeLabel}.`, status: _jsx("span", { className: "text-xs text-muted-foreground", children: settings.source }), control: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(ManageButton, {}) }), _jsx(PopoverContent, { align: "end", sideOffset: 6, className: "w-80 p-4", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Max iterations" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Choose how long a response can work before pausing." })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("input", { type: "number", min: settings.minMaxIterations, max: settings.maxMaxIterations, value: value, disabled: !settings.canUpdate || saving, onChange: (event) => {
991
985
  setValue(event.target.value);
992
986
  setError(null);
993
987
  }, onKeyDown: (event) => {
@@ -1020,7 +1014,7 @@ function CapabilityStatusStrip({ isDevMode, builderConnected, builderLoading, bu
1020
1014
  : builderConnected && builderBranchesAvailable
1021
1015
  ? "Builder branches"
1022
1016
  : "Desktop/local";
1023
- return (_jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-2.5 py-2", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-medium text-muted-foreground", children: "Available now" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(CapabilityStatusRow, { label: "App", value: "Chat + actions", active: true }), _jsx(CapabilityStatusRow, { label: "Code", value: codeLabel, active: codeAvailable }), _jsx(CapabilityStatusRow, { label: "Builder", active: builderConnected, value: builderLoading ? ("Checking...") : builderConnected ? ("Connected") : (_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: onOpenLlm, className: "rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground", children: "Connect" })) })] })] }));
1017
+ return (_jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-2.5 py-2", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-medium text-muted-foreground", children: "Available now" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(CapabilityStatusRow, { label: "App", value: "Chat + actions", active: true }), _jsx(CapabilityStatusRow, { label: "Code", value: codeLabel, active: codeAvailable }), _jsx(CapabilityStatusRow, { label: "Builder", active: builderConnected, value: builderLoading ? (_jsx(Skeleton, { className: "h-3 w-16" })) : builderConnected ? ("Connected") : (_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: onOpenLlm, className: "rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground", children: "Connect" })) })] })] }));
1024
1018
  }
1025
1019
  function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, sections = ALL_SETTINGS_SECTIONS, showCapabilityStrip = true, className, surface = "sidebar", builderConnectionOwnedExternally = false, agentAdditionalContent, }) {
1026
1020
  const { status: builder, loading: builderLoading } = useBuilderStatus({
@@ -0,0 +1,12 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export interface SettingsSkeletonProps extends HTMLAttributes<HTMLDivElement> {
3
+ lines?: number;
4
+ label?: string;
5
+ }
6
+ /** Layout-matching placeholder for settings fields while their data loads. */
7
+ export declare function SettingsSkeleton({ lines, label, className, ...props }: SettingsSkeletonProps): import("react").JSX.Element;
8
+ export interface SettingsLoadingRowProps extends HTMLAttributes<HTMLDivElement> {
9
+ controlCount?: number;
10
+ }
11
+ /** Layout-matching placeholder for a single settings row while it loads. */
12
+ export declare function SettingsLoadingRow({ controlCount, className, ...props }: SettingsLoadingRowProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Skeleton } from "@agent-native/toolkit/design-system";
3
+ import { cn } from "../utils.js";
4
+ const LABEL_WIDTHS = ["w-1/3", "w-full", "w-3/5"];
5
+ /** Layout-matching placeholder for settings fields while their data loads. */
6
+ export function SettingsSkeleton({ lines = 3, label = "Loading settings", className, ...props }) {
7
+ return (_jsx("div", { ...props, className: cn("space-y-3", className), role: "status", "aria-busy": "true", "aria-label": label, children: Array.from({ length: lines }, (_, index) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Skeleton, { className: cn("h-3", LABEL_WIDTHS[index % 3]) }), index < 2 && (_jsx(Skeleton, { className: "h-9 w-full border border-border bg-muted-foreground/5" }))] }, index))) }));
8
+ }
9
+ /** Layout-matching placeholder for a single settings row while it loads. */
10
+ export function SettingsLoadingRow({ controlCount = 1, className, ...props }) {
11
+ return (_jsxs("div", { ...props, className: cn("flex items-center justify-between gap-4 px-5 py-4 sm:px-6", className), role: "status", "aria-busy": "true", "aria-label": "Loading setting", children: [_jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-3.5 w-32" }), _jsx(Skeleton, { className: "h-3 w-3/4 max-w-64" })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", "aria-hidden": "true", children: Array.from({ length: controlCount }, (_, index) => (_jsx(Skeleton, { className: cn("h-9 border border-border bg-muted-foreground/10", index === 0 ? "w-28" : "w-20") }, index))) })] }));
12
+ }
@@ -10,12 +10,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  * Provider status comes from `/_agent-native/voice-providers/status`, which
11
11
  * mirrors the server transcription route's key/env resolution.
12
12
  */
13
- import { Picker, Switch } from "@agent-native/toolkit/design-system";
14
- import { IconAlertCircle, IconCheck, IconChevronDown, IconChevronRight, IconExternalLink, IconLoader2, IconLockOpen, } from "@tabler/icons-react";
13
+ import { Picker, Skeleton, Switch } from "@agent-native/toolkit/design-system";
14
+ import { IconAlertCircle, IconCheck, IconChevronDown, IconChevronRight, IconExternalLink, IconLockOpen, } from "@tabler/icons-react";
15
15
  import { useCallback, useEffect, useState } from "react";
16
16
  import { buildSettingsRoute } from "../../navigation/index.js";
17
17
  import { agentNativePath } from "../api-path.js";
18
18
  import { SettingsRow } from "./SettingsRow.js";
19
+ import { SettingsSkeleton } from "./SettingsSkeleton.js";
19
20
  import { openBuilderConnectPopup, useBuilderStatus, } from "./useBuilderStatus.js";
20
21
  const PREFS_URL = agentNativePath("/_agent-native/application-state/voice-transcription-prefs");
21
22
  const CLEANUP_PREFS_URL = agentNativePath("/_agent-native/application-state/voice-cleanup-prefs");
@@ -269,9 +270,9 @@ export function VoiceTranscriptionSection({ compact = false, } = {}) {
269
270
  };
270
271
  if (transcriptionMode === null) {
271
272
  if (compact) {
272
- return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx("div", { className: "h-9 w-44 animate-pulse rounded-md border border-border bg-muted-foreground/10", "aria-label": "Loading voice transcription" }) }));
273
+ return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx(Skeleton, { className: "h-9 w-44 border border-border bg-muted-foreground/10", "aria-label": "Loading voice transcription" }) }));
273
274
  }
274
- return (_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(IconLoader2, { size: 10, className: "animate-spin" }), "Loading\u2026"] }));
275
+ return _jsx(SettingsSkeleton, { lines: 1 });
275
276
  }
276
277
  if (compact) {
277
278
  return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx(Picker, { mode: "select", options: [
@@ -5,4 +5,5 @@ export { AccountSettingsCard, AccountSettingsForm, type AccountSettingsCardProps
5
5
  export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, type BuilderConnectFlow, type BuilderConnectFlowOptions, type BuilderConnectStartOptions, type BuilderStatus, type OpenBuilderConnectPopupOptions, } from "./useBuilderStatus.js";
6
6
  export { SecretsSection, type SecretsSectionProps } from "./SecretsSection.js";
7
7
  export { SettingsGroup, SettingsRow, type SettingsGroupProps, type SettingsRowProps, } from "./SettingsRow.js";
8
+ export { SettingsLoadingRow, SettingsSkeleton, type SettingsLoadingRowProps, type SettingsSkeletonProps, } from "./SettingsSkeleton.js";
8
9
  export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, type SettingsPanelController, type SettingsPanelControllerOptions, } from "./useSettingsPanelController.js";
@@ -5,4 +5,5 @@ export { AccountSettingsCard, AccountSettingsForm, } from "./AccountSettingsCard
5
5
  export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, } from "./useBuilderStatus.js";
6
6
  export { SecretsSection } from "./SecretsSection.js";
7
7
  export { SettingsGroup, SettingsRow, } from "./SettingsRow.js";
8
+ export { SettingsLoadingRow, SettingsSkeleton, } from "./SettingsSkeleton.js";
8
9
  export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
@@ -335,6 +335,10 @@ class SyncTransport {
335
335
  // collab on its fast presence cadence against the local stream. Subscribers
336
336
  // are re-notified via the close/connect cycle below.
337
337
  this.capabilities = [];
338
+ // The failure count was earned against the gateway. Carrying it over would
339
+ // back the local endpoint off by 2^failures (already 8min at the threshold)
340
+ // before its first poll, even though it just served this page.
341
+ this.consecutiveFailures = 0;
338
342
  if (this.gatewayReconnectTimer) {
339
343
  clearTimeout(this.gatewayReconnectTimer);
340
344
  this.gatewayReconnectTimer = null;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -174,14 +174,20 @@ export declare function withDbTimeout<T>(op: string, run: () => Promise<T>, ms?:
174
174
  */
175
175
  export declare function isServerlessRuntime(): boolean;
176
176
  /**
177
- * postgres.js pool options tuned per runtime. idle_timeout is shortened on
178
- * serverless so a thawed-but-idle instance releases its connections quickly.
179
- * Long-lived Node servers keep the larger pool for throughput.
180
- *
181
- * The cap assumes {@link sharedDbPool}: one pool per URL for the whole
182
- * process, not one per consumer. See {@link neonPoolMax} for why the cap must
183
- * leave room for concurrency.
177
+ * True for production serverless execution contexts. Netlify also exposes
178
+ * `NETLIFY=true` in its build environment, so build-time code that owns
179
+ * migrations must use `withMigrationRuntime()` explicitly.
180
+ */
181
+ export declare function isProductionServerlessFunctionRuntime(env?: NodeJS.ProcessEnv): boolean;
182
+ /** True for statements that change database schema or database privileges. */
183
+ export declare function isSchemaMutationStatement(statement: DbExecStatement): boolean;
184
+ /**
185
+ * Request functions must never be able to prepare or mutate schema. Throwing
186
+ * here makes an unconverted ensureTable path fail loudly instead of silently
187
+ * reporting success against a missing table. The release migration wrapper
188
+ * is the only supported production opt-in.
184
189
  */
190
+ export declare function assertSchemaMutationAllowed(statement: DbExecStatement): void;
185
191
  export declare function pgPoolOptions(url: string): Record<string, unknown>;
186
192
  /**
187
193
  * Shared options for every Neon serverless pool. The startup parameter is