@agent-native/core 0.145.7 → 0.146.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.
Files changed (186) 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/.agents/skills/turn-into-app/SKILL.md +37 -11
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
  7. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
  8. package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
  9. package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
  11. package/corpus/templates/analytics/netlify.toml +1 -1
  12. package/corpus/templates/analytics/package.json +1 -0
  13. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
  14. package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
  15. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
  16. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
  17. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +11 -2
  18. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
  19. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
  20. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
  21. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
  22. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
  23. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
  24. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
  25. package/corpus/templates/analytics/server/plugins/db.ts +56 -3
  26. package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
  27. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
  28. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
  29. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
  30. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  31. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
  32. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
  33. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  34. package/corpus/templates/brain/server/lib/connectors.ts +38 -14
  35. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
  36. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
  37. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  38. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  39. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  40. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  41. package/corpus/templates/chat/AGENTS.md +3 -0
  42. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
  43. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
  44. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  45. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
  46. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
  47. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  48. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
  49. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  50. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
  51. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
  52. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
  53. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
  54. package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
  55. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
  56. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
  57. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  58. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
  59. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
  60. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  61. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
  62. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
  63. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  64. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
  65. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
  66. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  67. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
  68. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
  69. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  70. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
  71. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
  72. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  73. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
  74. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
  75. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  76. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
  77. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
  78. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  79. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
  80. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
  81. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
  82. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
  83. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  84. package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
  85. package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
  86. package/corpus/templates/slides/app/routes/slide.tsx +10 -5
  87. package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
  88. package/corpus/templates/slides/server/db/schema.ts +1 -0
  89. package/corpus/templates/slides/server/handlers/share.ts +51 -3
  90. package/corpus/templates/slides/server/plugins/db.ts +5 -0
  91. package/corpus/templates/slides/shared/api.ts +2 -0
  92. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
  93. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
  94. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  95. package/dist/cli/code-agent-executor.js +8 -3
  96. package/dist/client/ConnectBuilderCard.d.ts +7 -7
  97. package/dist/client/ConnectBuilderCard.js +45 -20
  98. package/dist/client/components/ApiKeySettings.js +2 -5
  99. package/dist/client/org/TeamPage.js +4 -2
  100. package/dist/client/settings/AccountSettingsCard.js +2 -1
  101. package/dist/client/settings/AgentsSection.js +2 -1
  102. package/dist/client/settings/SecretsSection.js +3 -2
  103. package/dist/client/settings/SettingsPanel.js +6 -12
  104. package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
  105. package/dist/client/settings/SettingsSkeleton.js +12 -0
  106. package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
  107. package/dist/client/settings/index.d.ts +1 -0
  108. package/dist/client/settings/index.js +1 -0
  109. package/dist/config.d.ts +9 -2
  110. package/dist/config.js +38 -2
  111. package/dist/db/client.d.ts +26 -8
  112. package/dist/db/client.js +200 -9
  113. package/dist/db/create-get-db.js +2 -2
  114. package/dist/db/ddl-guard.js +3 -1
  115. package/dist/db/ensure-additive-columns.d.ts +2 -0
  116. package/dist/db/ensure-additive-columns.js +5 -2
  117. package/dist/db/index.d.ts +2 -2
  118. package/dist/db/index.js +2 -2
  119. package/dist/db/migrations.d.ts +11 -54
  120. package/dist/db/migrations.js +70 -0
  121. package/dist/deploy/build.js +5 -1
  122. package/dist/jobs/index.d.ts +1 -0
  123. package/dist/jobs/index.js +1 -0
  124. package/dist/jobs/run-history.d.ts +4 -0
  125. package/dist/jobs/run-history.js +44 -0
  126. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  127. package/dist/scripts/docs/search.js +3 -2
  128. package/dist/secrets/routes.d.ts +6 -6
  129. package/dist/server/agent-chat/prompt-resources.js +4 -3
  130. package/dist/server/agent-chat-plugin.js +6 -4
  131. package/dist/server/agents-bundle.d.ts +20 -3
  132. package/dist/server/agents-bundle.js +57 -30
  133. package/dist/server/auth-plugin.js +5 -0
  134. package/dist/server/better-auth-instance.js +2 -63
  135. package/dist/server/better-auth-migrations.d.ts +8 -0
  136. package/dist/server/better-auth-migrations.js +179 -0
  137. package/dist/server/edge.d.ts +2 -0
  138. package/dist/server/edge.js +2 -0
  139. package/dist/server/index.d.ts +2 -0
  140. package/dist/server/index.js +2 -0
  141. package/dist/server/release-migrations.d.ts +8 -0
  142. package/dist/server/release-migrations.js +24 -0
  143. package/dist/server/sweep-runtime.d.ts +1 -1
  144. package/dist/server/sweep-runtime.js +15 -0
  145. package/dist/shared/runtime-config.js +1 -1
  146. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  147. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  148. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  149. package/dist/templates/chat/AGENTS.md +3 -0
  150. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  151. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  152. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  153. package/dist/templates/default/AGENTS.md +3 -0
  154. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  155. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  156. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  157. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  158. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  159. package/dist/templates/workspace-core/AGENTS.md +3 -0
  160. package/dist/templates/workspace-root/AGENTS.md +3 -0
  161. package/dist/vite/agent-native-config-loader.d.ts +10 -0
  162. package/dist/vite/agent-native-config-loader.js +56 -0
  163. package/dist/vite/agents-bundle-plugin.d.ts +5 -1
  164. package/dist/vite/agents-bundle-plugin.js +29 -6
  165. package/dist/vite/client.d.ts +4 -2
  166. package/dist/vite/client.js +4 -49
  167. package/dist/vite/index.d.ts +2 -2
  168. package/dist/vite/index.js +1 -1
  169. package/docs/content/agent-native-config.mdx +43 -17
  170. package/docs/content/environment-variables.mdx +1 -1
  171. package/package.json +3 -3
  172. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  173. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  174. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  175. package/src/templates/chat/AGENTS.md +3 -0
  176. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  177. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  178. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  179. package/src/templates/default/AGENTS.md +3 -0
  180. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  181. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  182. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  183. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  184. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  185. package/src/templates/workspace-core/AGENTS.md +3 -0
  186. package/src/templates/workspace-root/AGENTS.md +3 -0
@@ -51,13 +51,24 @@ also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
53
 
54
+ For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
+ orchestrator, not the build environment. After forming the source brief, call
56
+ the Dispatch `start-workspace-app-creation` action so Builder creates the app
57
+ in the connected workspace. Do not use the host's code interpreter, shell,
58
+ artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
59
+ handoff action is unavailable, stop and ask the user to authenticate Dispatch;
60
+ do not fall back to a local sandbox build. Claude Code and Codex Code are the
61
+ local-agent exception and may follow the implementation steps in the main
62
+ skill.
63
+
54
64
  In a new Project chat, say:
55
65
 
56
66
  ```text
57
67
  Turn this project into an app. Use the visible Project instructions,
58
68
  knowledge files, and any selected successful runs as the source. Create the
59
- app in the connected Agent-Native workspace, keep the source brief bounded,
60
- and report the real path and verification result.
69
+ app in the connected Agent-Native workspace through Dispatch and Builder, keep
70
+ the source brief bounded, and report the real Builder branch/path and
71
+ verification result. Do not build it in this chat's sandbox.
61
72
  ```
62
73
 
63
74
  If the host does not show the Project instructions or files to the model, stop
@@ -13,6 +13,7 @@ import { buildMergedConfig, McpClientManager, mcpToolsToActionEntries, } from ".
13
13
  import { readAgentsBundleFromFs, generateDevelopmentSkillsPromptBlock, } from "../server/agents-bundle.js";
14
14
  import { getAmbientOrgId, getAmbientUserEmail, runWithRequestContext, } from "../server/request-context.js";
15
15
  import { isReasoningEffort, } from "../shared/reasoning-effort.js";
16
+ import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
16
17
  import { createCodeAgentOutputSmoother } from "./code-agent-output-smoother.js";
17
18
  import { addCodeAgentCommandToAllowlist, appendCodeAgentTranscriptEvent, dequeueCodeAgentFollowUp, getCodeAgentRunRecord, isCodeAgentCommandAllowed, listCodeAgentTranscriptEvents, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
18
19
  const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
@@ -1295,11 +1296,15 @@ const AGENTS_MD_INLINE_CAP = 16_000;
1295
1296
  */
1296
1297
  /** @internal exported for unit tests */
1297
1298
  export async function buildCodeAgentSystemPrompt(cwd, permissionMode) {
1298
- const bundle = readAgentsBundleFromFs(cwd);
1299
+ const appConfig = await loadResolvedAgentNativeConfig(cwd, createAgentNativeConfigContext("serve", "development"));
1300
+ const bundle = readAgentsBundleFromFs(cwd, null, {
1301
+ instructions: appConfig.instructions,
1302
+ });
1299
1303
  // If the bundle has no AGENTS.md, try CLAUDE.md as a fallback — many repos
1300
1304
  // use that name for agent instructions (e.g. Claude Code projects).
1301
- let agentsMdContent = bundle.agentsMd;
1302
- if (!agentsMdContent.trim()) {
1305
+ let agentsMdContent = bundle.developmentAgentsMd ?? bundle.agentsMd;
1306
+ if (!agentsMdContent.trim() &&
1307
+ appConfig.instructions?.development === undefined) {
1303
1308
  try {
1304
1309
  const fs = await import("node:fs");
1305
1310
  const path = await import("node:path");
@@ -3,19 +3,19 @@ export interface ConnectBuilderCardProps {
3
3
  configured: boolean;
4
4
  /**
5
5
  * True when the server has a Builder branch project configured for this
6
- * request. When false, the card shows a waitlist CTA instead of a Send
7
- * button — the /builder/run endpoint would 403 anyway.
6
+ * request. When false, the card shows a hosted waitlist CTA or a local code
7
+ * external coding-agent handoff instead of a Send button.
8
8
  */
9
9
  builderEnabled?: boolean;
10
10
  connectUrl: string;
11
11
  orgName?: string | null;
12
- /** The user's feature/change request, forwarded to Builder's cloud agent
13
- * when they click Send. Empty for generic "connect Builder" prompts. */
12
+ /** The user's feature/change request, forwarded to the selected coding
13
+ * agent when they click Send. Empty for generic "connect Builder" prompts. */
14
14
  prompt?: string;
15
15
  }
16
16
  /**
17
- * Rich inline card rendered for the `connect-builder` tool call. Shows a
18
- * prominent Connect button that opens the Builder CLI auth flow and polls
19
- * /_agent-native/builder/status until credentials land.
17
+ * Rich inline card rendered for the `connect-builder` tool call. Shows the
18
+ * Builder handoff when available, or routes local development requests to the
19
+ * external coding agent.
20
20
  */
21
21
  export declare function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt, }: ConnectBuilderCardProps): React.JSX.Element;
@@ -1,27 +1,27 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconExternalLink, IconLoader2 } from "@tabler/icons-react";
2
+ import { IconCheck, IconCode, IconCopy, IconExternalLink, IconLoader2, } from "@tabler/icons-react";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
5
5
  import { agentNativePath } from "./api-path.js";
6
6
  import { BuilderBMark } from "./builder-mark.js";
7
+ import { writeClipboardText } from "./clipboard.js";
7
8
  import { getCallbackOrigin } from "./frame.js";
8
9
  import { useBuilderConnectFlow } from "./settings/useBuilderStatus.js";
9
10
  import { cn } from "./utils.js";
10
11
  const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
11
12
  const CODE_CHANGE_FALLBACK_DETAIL = "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
12
13
  const CODE_CHANGE_FALLBACK_TEXT = `This requires a code change. ${CODE_CHANGE_FALLBACK_DETAIL}`;
13
- function isLocalBrowserOutsideDesktop() {
14
- if (typeof window === "undefined" || typeof navigator === "undefined") {
14
+ function isLocalDevelopment() {
15
+ if (typeof window === "undefined") {
15
16
  return false;
16
17
  }
17
18
  const hostname = window.location.hostname;
18
- const local = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
19
- return local && !/AgentNativeDesktop/i.test(navigator.userAgent || "");
19
+ return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
20
20
  }
21
21
  /**
22
- * Rich inline card rendered for the `connect-builder` tool call. Shows a
23
- * prominent Connect button that opens the Builder CLI auth flow and polls
24
- * /_agent-native/builder/status until credentials land.
22
+ * Rich inline card rendered for the `connect-builder` tool call. Shows the
23
+ * Builder handoff when available, or routes local development requests to the
24
+ * external coding agent.
25
25
  */
26
26
  export function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled = true, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt = "", }) {
27
27
  // The connect-poll state machine is shared — the tool-call result is
@@ -46,8 +46,11 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
46
46
  const [sending, setSending] = useState(false);
47
47
  const [runResult, setRunResult] = useState(null);
48
48
  const [sendErr, setSendErr] = useState(null);
49
- const [localBrowser, setLocalBrowser] = useState(false);
49
+ const [copyErr, setCopyErr] = useState(null);
50
+ const [promptCopied, setPromptCopied] = useState(false);
51
+ const [localDevelopment] = useState(() => isLocalDevelopment());
50
52
  const mountedRef = useRef(true);
53
+ const copyResetRef = useRef(null);
51
54
  // Tracks whether the user clicked "Connect Builder" *this session*. When
52
55
  // the connect-then-poll round-trip lands `configured=true`, we use this
53
56
  // flag to decide whether to retry the user's pending prompt automatically
@@ -58,9 +61,10 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
58
61
  const wasConnectingRef = useRef(false);
59
62
  useEffect(() => {
60
63
  mountedRef.current = true;
61
- setLocalBrowser(isLocalBrowserOutsideDesktop());
62
64
  return () => {
63
65
  mountedRef.current = false;
66
+ if (copyResetRef.current)
67
+ clearTimeout(copyResetRef.current);
64
68
  };
65
69
  }, []);
66
70
  const handleSend = useCallback(async () => {
@@ -127,8 +131,27 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
127
131
  setJoiningWaitlist(false);
128
132
  }
129
133
  }, [orgName, prompt]);
130
- // Combine connect-flow errors, send errors, and waitlist errors.
131
- const err = sendErr ?? waitlistErr ?? flow.error;
134
+ const handleCopyPrompt = useCallback(async () => {
135
+ if (!prompt.trim())
136
+ return;
137
+ setCopyErr(null);
138
+ const copied = await writeClipboardText(prompt);
139
+ if (!mountedRef.current)
140
+ return;
141
+ if (!copied) {
142
+ setCopyErr("Couldn't copy the prompt");
143
+ return;
144
+ }
145
+ setPromptCopied(true);
146
+ if (copyResetRef.current)
147
+ clearTimeout(copyResetRef.current);
148
+ copyResetRef.current = setTimeout(() => {
149
+ if (mountedRef.current)
150
+ setPromptCopied(false);
151
+ }, 1600);
152
+ }, [prompt]);
153
+ // Combine connect-flow errors, send errors, waitlist errors, and copy errors.
154
+ const err = sendErr ?? waitlistErr ?? copyErr ?? flow.error;
132
155
  const hasPrompt = prompt.trim().length > 0;
133
156
  const canSend = configured && builderEnabled && hasPrompt;
134
157
  // Auto-send the user's pending prompt the moment connecting finishes
@@ -149,7 +172,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
149
172
  }, [flow.connecting, canSend, sending, runResult, sendErr, handleSend]);
150
173
  // Branch creation is gated by a server-side project id, which may come
151
174
  // from deployment config or org-scoped secrets.
152
- const showWaitlist = !builderEnabled && hasPrompt;
175
+ const showExternalAgentHandoff = localDevelopment && !builderEnabled && hasPrompt;
176
+ const showWaitlist = !localDevelopment && !builderEnabled && hasPrompt;
153
177
  // Title + subtitle depend on which mode we're in. We compute them up front
154
178
  // so the render tree below stays flat.
155
179
  const connectedCapabilityText = builderEnabled
@@ -161,13 +185,14 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
161
185
  title = "Builder is working on it";
162
186
  subtitle = (_jsxs(_Fragment, { children: ["Working on branch", " ", _jsx("span", { className: "font-mono text-foreground", children: runResult.branchName }), ". Click through to watch progress in the Visual Editor."] }));
163
187
  }
188
+ else if (showExternalAgentHandoff) {
189
+ title = "This requires a code change";
190
+ subtitle =
191
+ "Open your coding agent in this project, then paste this request.";
192
+ }
164
193
  else if (showWaitlist) {
165
194
  title = "This requires a code change";
166
- subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " ", localBrowser
167
- ? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
168
- : "You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " ", localBrowser
169
- ? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
170
- : "You can still clone the project locally and use the desktop app for code changes."] }));
195
+ subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] }));
171
196
  }
172
197
  else if (canSend) {
173
198
  title = "Send this to Builder";
@@ -181,8 +206,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
181
206
  title = "Connect Builder.io";
182
207
  subtitle = _jsx(_Fragment, { children: "Builder.io's free tier includes AI credits." });
183
208
  }
184
- return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
209
+ return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : showExternalAgentHandoff ? (_jsx(IconCode, { className: "h-5 w-5" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
185
210
  campaign: "product",
186
211
  content: "connect_builder_card_branch",
187
- }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
212
+ }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showExternalAgentHandoff ? (_jsx("button", { type: "button", onClick: () => void handleCopyPrompt(), className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: promptCopied ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Prompt copied"] })) : (_jsxs(_Fragment, { children: [_jsx(IconCopy, { className: "h-3.5 w-3.5" }), "Copy prompt"] })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
188
213
  }
@@ -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";
package/dist/config.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Public, non-secret configuration for an Agent-Native app.
3
3
  *
4
4
  * This module is intentionally free of Node and framework imports so it can be
5
- * used from a typed `agent-native.ts` file and from browser code after Vite
5
+ * used from a typed `agent-native.config.ts` file and from browser code after Vite
6
6
  * serializes the resolved config into the client bundle.
7
7
  */
8
8
  export declare const AGENT_NATIVE_CONFIG_VERSION: 1;
@@ -42,11 +42,18 @@ export interface AgentNativeDiagnosticsConfig {
42
42
  /** Fail a production Vite build when runtime configuration has issues. */
43
43
  failOnBuild?: boolean;
44
44
  }
45
+ export interface AgentNativeInstructionsConfig {
46
+ /** Relative Markdown file loaded by the in-app runtime agent. */
47
+ runtime?: string;
48
+ /** Relative Markdown file loaded by development/coding agents. */
49
+ development?: string;
50
+ }
45
51
  export interface AgentNativeConfig {
46
52
  version?: typeof AGENT_NATIVE_CONFIG_VERSION;
47
53
  onboarding?: AgentNativeOnboardingConfig;
48
54
  runtime?: AgentNativeRuntimeConfig;
49
55
  diagnostics?: AgentNativeDiagnosticsConfig;
56
+ instructions?: AgentNativeInstructionsConfig;
50
57
  }
51
58
  export interface AgentNativeConfigContext {
52
59
  command: "serve" | "build";
@@ -57,7 +64,7 @@ export interface AgentNativeConfigContext {
57
64
  export type AgentNativeConfigFactory = (context: AgentNativeConfigContext) => AgentNativeConfig;
58
65
  export type AgentNativeConfigInput = AgentNativeConfig | AgentNativeConfigFactory;
59
66
  /**
60
- * Type-safe authoring helper for `agent-native.ts`.
67
+ * Type-safe authoring helper for `agent-native.config.ts`.
61
68
  *
62
69
  * Like Next's typed config file, this is deliberately identity-like: the
63
70
  * framework evaluates the exported object or factory in the Vite config