@agent-native/core 0.80.11 → 0.81.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 (225) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +47 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  6. package/corpus/core/src/client/chat/index.ts +1 -0
  7. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  8. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  10. package/corpus/core/src/client/index.ts +1 -0
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  12. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  13. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  14. package/corpus/core/src/db/client.ts +29 -2
  15. package/corpus/core/src/provider-api/index.ts +29 -0
  16. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  17. package/corpus/core/src/styles/agent-native.css +54 -0
  18. package/corpus/core/src/usage/store.ts +2 -2
  19. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  20. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  21. package/corpus/templates/analytics/app/global.css +8 -0
  22. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  23. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  24. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  26. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  27. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  28. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  33. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  34. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  35. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  36. package/corpus/templates/assets/app/global.css +15 -0
  37. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  38. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  39. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  40. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  41. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  42. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  43. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  45. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  46. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  47. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  48. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  49. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  50. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  51. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  52. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  55. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  56. package/corpus/templates/design/AGENTS.md +27 -0
  57. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  58. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  59. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  60. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  61. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  62. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  63. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  64. package/corpus/templates/design/actions/export-zip.ts +23 -8
  65. package/corpus/templates/design/actions/generate-design.ts +53 -1
  66. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  67. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  68. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  69. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  70. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  71. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  72. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  73. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  74. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  75. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  76. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  77. package/corpus/templates/design/actions/navigate.ts +18 -3
  78. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  79. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  80. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  81. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  82. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  83. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  84. package/corpus/templates/design/actions/view-screen.ts +1 -1
  85. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  86. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  87. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  88. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  89. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  90. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  91. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  92. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  93. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  94. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  95. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  96. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  97. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  98. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  99. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  100. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  101. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  102. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  103. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  104. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  105. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  106. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  107. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  108. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  109. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  110. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  111. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  112. package/corpus/templates/design/app/global.css +7 -0
  113. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  114. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  115. package/corpus/templates/design/app/i18n-data.ts +358 -0
  116. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  117. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  118. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  119. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  120. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  143. package/corpus/templates/design/e2e/helpers.ts +168 -65
  144. package/corpus/templates/design/package.json +3 -1
  145. package/corpus/templates/design/server/db/schema.ts +23 -0
  146. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  147. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  148. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  149. package/corpus/templates/design/server/plugins/db.ts +39 -0
  150. package/corpus/templates/design/server/register-secrets.ts +38 -0
  151. package/corpus/templates/design/shared/board-file.ts +228 -0
  152. package/corpus/templates/design/shared/board-objects.ts +288 -0
  153. package/corpus/templates/design/shared/code-layer.ts +52 -0
  154. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  155. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  156. package/corpus/templates/design/tsconfig.json +2 -1
  157. package/corpus/templates/slides/AGENTS.md +9 -0
  158. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  159. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  160. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  161. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  162. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  163. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  164. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  166. package/corpus/templates/slides/package.json +1 -0
  167. package/corpus/templates/slides/vite.config.ts +1 -0
  168. package/dist/cli/design-connect.d.ts +25 -0
  169. package/dist/cli/design-connect.d.ts.map +1 -1
  170. package/dist/cli/design-connect.js +357 -18
  171. package/dist/cli/design-connect.js.map +1 -1
  172. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  173. package/dist/client/ConnectBuilderCard.js +1 -0
  174. package/dist/client/ConnectBuilderCard.js.map +1 -1
  175. package/dist/client/chat/index.d.ts +1 -1
  176. package/dist/client/chat/index.d.ts.map +1 -1
  177. package/dist/client/chat/index.js +1 -1
  178. package/dist/client/chat/index.js.map +1 -1
  179. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  180. package/dist/client/chat/run-recovery.js +5 -5
  181. package/dist/client/chat/run-recovery.js.map +1 -1
  182. package/dist/client/chat-view-transition.d.ts +6 -0
  183. package/dist/client/chat-view-transition.d.ts.map +1 -1
  184. package/dist/client/chat-view-transition.js +23 -2
  185. package/dist/client/chat-view-transition.js.map +1 -1
  186. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  187. package/dist/client/composer/PromptComposer.js +3 -2
  188. package/dist/client/composer/PromptComposer.js.map +1 -1
  189. package/dist/client/index.d.ts +1 -1
  190. package/dist/client/index.d.ts.map +1 -1
  191. package/dist/client/index.js +1 -1
  192. package/dist/client/index.js.map +1 -1
  193. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  194. package/dist/client/org/OrgSwitcher.js +11 -4
  195. package/dist/client/org/OrgSwitcher.js.map +1 -1
  196. package/dist/client/sharing/ShareButton.js +3 -3
  197. package/dist/client/sharing/ShareButton.js.map +1 -1
  198. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  199. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  200. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  201. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  202. package/dist/collab/awareness.d.ts +2 -2
  203. package/dist/collab/awareness.d.ts.map +1 -1
  204. package/dist/collab/routes.d.ts +2 -2
  205. package/dist/db/client.d.ts +1 -0
  206. package/dist/db/client.d.ts.map +1 -1
  207. package/dist/db/client.js +28 -2
  208. package/dist/db/client.js.map +1 -1
  209. package/dist/notifications/routes.d.ts +3 -3
  210. package/dist/observability/routes.d.ts +2 -2
  211. package/dist/progress/routes.d.ts +1 -1
  212. package/dist/provider-api/index.d.ts +5 -4
  213. package/dist/provider-api/index.d.ts.map +1 -1
  214. package/dist/provider-api/index.js +28 -0
  215. package/dist/provider-api/index.js.map +1 -1
  216. package/dist/resources/handlers.d.ts +3 -3
  217. package/dist/server/core-routes-plugin.d.ts +25 -0
  218. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  219. package/dist/server/core-routes-plugin.js +43 -18
  220. package/dist/server/core-routes-plugin.js.map +1 -1
  221. package/dist/server/transcribe-voice.d.ts +1 -1
  222. package/dist/styles/agent-native.css +54 -0
  223. package/dist/usage/store.js +2 -2
  224. package/dist/usage/store.js.map +1 -1
  225. package/package.json +1 -1
@@ -132,6 +132,31 @@ export function markAgentChatHomeHandoff(storageKey?: string | null): void {
132
132
  } catch {}
133
133
  }
134
134
 
135
+ /**
136
+ * Check whether a full-page-chat handoff marker is still recent without
137
+ * consuming it. Use this when a chat route should only restore or animate a
138
+ * conversation if the user actually chatted a moment ago.
139
+ */
140
+ export function isAgentChatHomeHandoffActive(
141
+ storageKey?: string | null,
142
+ options: AgentChatHomeHandoffOptions = {},
143
+ ): boolean {
144
+ if (typeof window === "undefined") return false;
145
+
146
+ let startedAt = 0;
147
+ try {
148
+ const raw = window.sessionStorage.getItem(
149
+ agentChatHomeHandoffKey(storageKey),
150
+ );
151
+ startedAt = raw ? Number.parseInt(raw, 10) : 0;
152
+ } catch {
153
+ startedAt = 0;
154
+ }
155
+
156
+ const ttlMs = options.ttlMs ?? AGENT_CHAT_HOME_HANDOFF_TTL_MS;
157
+ return Number.isFinite(startedAt) && Date.now() - startedAt <= ttlMs;
158
+ }
159
+
135
160
  /**
136
161
  * Consume a recent full-page-chat handoff marker. Returns true only once per
137
162
  * marker, so layouts can keep `openOnChatRunning` scoped to the route that
@@ -153,9 +178,15 @@ export function consumeAgentChatHomeHandoff(
153
178
  startedAt = 0;
154
179
  }
155
180
 
181
+ return isAgentChatHomeHandoffActiveFromTimestamp(startedAt, options);
182
+ }
183
+
184
+ function isAgentChatHomeHandoffActiveFromTimestamp(
185
+ startedAt: number,
186
+ options: AgentChatHomeHandoffOptions,
187
+ ): boolean {
156
188
  const ttlMs = options.ttlMs ?? AGENT_CHAT_HOME_HANDOFF_TTL_MS;
157
- const active = Number.isFinite(startedAt) && Date.now() - startedAt <= ttlMs;
158
- return active;
189
+ return Number.isFinite(startedAt) && Date.now() - startedAt <= ttlMs;
159
190
  }
160
191
 
161
192
  /**
@@ -30,7 +30,7 @@ import {
30
30
 
31
31
  import type { ReasoningEffort } from "../../shared/reasoning-effort.js";
32
32
  import { AssistantUiStaleIndexErrorBoundary } from "../assistant-ui-recovery.js";
33
- import { BuilderSetupCard } from "../chat/run-recovery.js";
33
+ import { BuilderConnectCta, BuilderSetupCard } from "../chat/run-recovery.js";
34
34
  import { TooltipProvider } from "../components/ui/tooltip.js";
35
35
  import {
36
36
  fetchAgentEngineConfiguredState,
@@ -578,10 +578,11 @@ function PromptComposerInner({
578
578
  },
579
579
  [composerEffort, composerEngine, composerModel, onSubmit],
580
580
  );
581
+ const useInlineMissingKeySetup = layoutVariant === "compact";
581
582
 
582
583
  return (
583
584
  <>
584
- {missingApiKey ? (
585
+ {missingApiKey && !useInlineMissingKeySetup ? (
585
586
  <BuilderSetupCard
586
587
  onConnected={handleBuilderConnected}
587
588
  bouncePulse={missingKeyBouncePulse}
@@ -589,6 +590,24 @@ function PromptComposerInner({
589
590
  layout="sidebar"
590
591
  />
591
592
  ) : null}
593
+ {missingApiKey && useInlineMissingKeySetup ? (
594
+ <div className="mb-2 rounded-md border border-border/80 bg-background/80 p-2.5 text-start shadow-sm">
595
+ <div className="flex flex-col gap-2">
596
+ <div className="min-w-0">
597
+ <p className="text-[12px] font-medium text-foreground">
598
+ Connect AI
599
+ </p>
600
+ <p className="mt-0.5 text-[11px] leading-4 text-muted-foreground">
601
+ Connect Builder.io before sending.
602
+ </p>
603
+ </div>
604
+ <BuilderConnectCta
605
+ variant="compact"
606
+ onConnected={handleBuilderConnected}
607
+ />
608
+ </div>
609
+ </div>
610
+ ) : null}
592
611
  <AgentComposerFrame
593
612
  className={cn(
594
613
  "text-start",
@@ -529,6 +529,7 @@ export {
529
529
  AGENT_CHAT_VIEW_TRANSITION_NAME,
530
530
  consumeAgentChatHomeHandoff,
531
531
  getAgentChatViewTransitionStyle,
532
+ isAgentChatHomeHandoffActive,
532
533
  markAgentChatHomeHandoff,
533
534
  navigateWithAgentChatViewTransition,
534
535
  startAgentChatViewTransition,
@@ -97,6 +97,9 @@ const SECTION_LABEL_CLASS =
97
97
  const APP_SUBMENU_CONTENT_CLASS =
98
98
  "z-50 w-72 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2";
99
99
 
100
+ const SWITCHER_BUTTON_CLASS =
101
+ "flex w-full items-center gap-2 rounded-md border border-border/70 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60 cursor-pointer";
102
+
100
103
  const DEFAULT_ORGANIZATION_SETTINGS_PATH = "/settings#team";
101
104
 
102
105
  const APP_ICON_MAP: Record<string, typeof IconApps> = {
@@ -274,6 +277,25 @@ function AppsSubmenu({
274
277
  );
275
278
  }
276
279
 
280
+ function ReservedOrgSwitcherSpace({ className }: { className?: string }) {
281
+ return <div aria-hidden="true" className={`h-8 ${className ?? ""}`} />;
282
+ }
283
+
284
+ function OrgSwitcherLoadingPlaceholder({ className }: { className?: string }) {
285
+ return (
286
+ <button
287
+ type="button"
288
+ disabled
289
+ aria-label="Loading organization"
290
+ className={`${SWITCHER_BUTTON_CLASS} animate-pulse ${className ?? ""}`}
291
+ >
292
+ <IconBuilding className="h-3.5 w-3.5 shrink-0 opacity-60" />
293
+ <span className="h-3 min-w-0 flex-1 rounded-sm bg-muted-foreground/20" />
294
+ <IconSelector className="h-3 w-3 shrink-0 opacity-30" />
295
+ </button>
296
+ );
297
+ }
298
+
277
299
  /**
278
300
  * Compact org switcher button. Shows the active org (or "Personal" when the
279
301
  * user has none); opens a popover with the user's other orgs, pending
@@ -326,7 +348,7 @@ export function OrgSwitcher({
326
348
 
327
349
  if (!org) {
328
350
  return reserveSpace && isLoading ? (
329
- <div aria-hidden="true" className={`h-8 ${className ?? ""}`} />
351
+ <OrgSwitcherLoadingPlaceholder className={className} />
330
352
  ) : null;
331
353
  }
332
354
 
@@ -338,7 +360,7 @@ export function OrgSwitcher({
338
360
  orgCount > 0 || pendingInvitations.length > 0 || domainMatches.length > 0;
339
361
  if (!hasAny && !org.email) {
340
362
  return reserveSpace ? (
341
- <div aria-hidden="true" className={`h-8 ${className ?? ""}`} />
363
+ <ReservedOrgSwitcherSpace className={className} />
342
364
  ) : null;
343
365
  }
344
366
  if (
@@ -348,7 +370,7 @@ export function OrgSwitcher({
348
370
  domainMatches.length === 0
349
371
  ) {
350
372
  return reserveSpace ? (
351
- <div aria-hidden="true" className={`h-8 ${className ?? ""}`} />
373
+ <ReservedOrgSwitcherSpace className={className} />
352
374
  ) : null;
353
375
  }
354
376
 
@@ -368,7 +390,7 @@ export function OrgSwitcher({
368
390
  <PopoverPrimitive.Trigger asChild>
369
391
  <button
370
392
  type="button"
371
- className={`flex w-full items-center gap-2 rounded-md border border-border/70 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer ${className ?? ""}`}
393
+ className={`${SWITCHER_BUTTON_CLASS} ${className ?? ""}`}
372
394
  >
373
395
  <ButtonIcon className="h-3.5 w-3.5 shrink-0" />
374
396
  <span className="truncate flex-1 text-start">{buttonLabel}</span>
@@ -153,13 +153,13 @@ interface SharesResponse {
153
153
  policy?: SharesPolicy;
154
154
  }
155
155
 
156
- // Mirror shadcn's <Button size="sm" variant="outline"> class string so the
157
- // trigger sits flush next to other sm outline buttons in the template.
156
+ // Match shadcn's <Button size="sm" variant="outline"> sizing so the trigger
157
+ // sits flush next to other controls while staying transparent at rest.
158
158
  const BUTTON_BASE =
159
159
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0";
160
160
  const BUTTON_OUTLINE_SM = cn(
161
161
  BUTTON_BASE,
162
- "h-9 px-3 border border-[hsl(var(--sidebar-border,var(--input)))] bg-[hsl(var(--sidebar-background,var(--background)))] text-foreground hover:bg-[hsl(var(--sidebar-accent,var(--accent)))] hover:text-[hsl(var(--sidebar-accent-foreground,var(--accent-foreground)))]",
162
+ "h-9 px-3 border border-[hsl(var(--sidebar-border,var(--input)))] bg-transparent text-foreground hover:bg-[hsl(var(--sidebar-accent,var(--accent)))] hover:text-[hsl(var(--sidebar-accent-foreground,var(--accent-foreground)))]",
163
163
  );
164
164
  const BUTTON_PRIMARY_SM = cn(
165
165
  BUTTON_BASE,
@@ -4,6 +4,7 @@ import { useLocation, useNavigate } from "react-router";
4
4
  import { appBasePath } from "./api-path.js";
5
5
  import {
6
6
  consumeAgentChatHomeHandoff,
7
+ isAgentChatHomeHandoffActive,
7
8
  markAgentChatHomeHandoff,
8
9
  navigateWithAgentChatViewTransition,
9
10
  } from "./chat-view-transition.js";
@@ -13,6 +14,8 @@ export interface UseAgentChatHomeHandoffOptions {
13
14
  storageKey?: string | null;
14
15
  /** The current destination path that receives the sidebar handoff. */
15
16
  activePath: string;
17
+ /** How long the handoff marker remains valid. Defaults to 6 hours. */
18
+ ttlMs?: number;
16
19
  /** Disable consumption without changing hook call order. */
17
20
  enabled?: boolean;
18
21
  }
@@ -30,6 +33,10 @@ export interface UseAgentChatHomeHandoffLinksOptions {
30
33
  isChatPath?: (pathname: string) => boolean;
31
34
  /** Disable link interception without changing hook call order. */
32
35
  enabled?: boolean;
36
+ /** How long the handoff marker remains valid. Defaults to 6 hours. */
37
+ ttlMs?: number;
38
+ /** Only intercept links if a recent handoff marker already exists. */
39
+ requireActiveHandoff?: boolean;
33
40
  }
34
41
 
35
42
  function stripBasePath(path: string): string {
@@ -96,6 +103,10 @@ function shouldHandleAnchorClick(
96
103
  return true;
97
104
  }
98
105
 
106
+ function handoffTtlOptions(ttlMs: number | undefined) {
107
+ return ttlMs === undefined ? undefined : { ttlMs };
108
+ }
109
+
99
110
  /**
100
111
  * Returns true for the route that has just received a full-page-chat handoff.
101
112
  * Pass the result to `AgentSidebar openOnChatRunning`.
@@ -103,6 +114,7 @@ function shouldHandleAnchorClick(
103
114
  export function useAgentChatHomeHandoff({
104
115
  storageKey,
105
116
  activePath,
117
+ ttlMs,
106
118
  enabled = true,
107
119
  }: UseAgentChatHomeHandoffOptions): boolean {
108
120
  const [handoffPath, setHandoffPath] = useState<string | null>(null);
@@ -112,10 +124,10 @@ export function useAgentChatHomeHandoff({
112
124
  setHandoffPath(null);
113
125
  return;
114
126
  }
115
- if (consumeAgentChatHomeHandoff(storageKey)) {
127
+ if (consumeAgentChatHomeHandoff(storageKey, handoffTtlOptions(ttlMs))) {
116
128
  setHandoffPath(activePath);
117
129
  }
118
- }, [activePath, enabled, storageKey]);
130
+ }, [activePath, enabled, storageKey, ttlMs]);
119
131
 
120
132
  return enabled && handoffPath === activePath;
121
133
  }
@@ -129,6 +141,8 @@ export function useAgentChatHomeHandoffLinks({
129
141
  chatPath = "/",
130
142
  isChatPath,
131
143
  enabled = true,
144
+ ttlMs,
145
+ requireActiveHandoff = false,
132
146
  }: UseAgentChatHomeHandoffLinksOptions): void {
133
147
  const location = useLocation();
134
148
  const navigate = useNavigate();
@@ -161,6 +175,13 @@ export function useAgentChatHomeHandoffLinks({
161
175
  return;
162
176
  }
163
177
 
178
+ if (
179
+ requireActiveHandoff &&
180
+ !isAgentChatHomeHandoffActive(storageKey, handoffTtlOptions(ttlMs))
181
+ ) {
182
+ return;
183
+ }
184
+
164
185
  event.preventDefault();
165
186
  markAgentChatHomeHandoff(storageKey);
166
187
  navigateWithAgentChatViewTransition(navigate, path);
@@ -168,5 +189,12 @@ export function useAgentChatHomeHandoffLinks({
168
189
 
169
190
  document.addEventListener("click", handleClick, true);
170
191
  return () => document.removeEventListener("click", handleClick, true);
171
- }, [enabled, location.pathname, navigate, storageKey]);
192
+ }, [
193
+ enabled,
194
+ location.pathname,
195
+ navigate,
196
+ requireActiveHandoff,
197
+ storageKey,
198
+ ttlMs,
199
+ ]);
172
200
  }
@@ -123,6 +123,33 @@ export function pgliteDataDirFromUrl(url: string): string {
123
123
  return dataDir;
124
124
  }
125
125
 
126
+ export function pgliteRuntimeDataDir(dataDir: string): string {
127
+ if (dataDir === "memory://") return dataDir;
128
+ if (!isServerlessRuntime() || path.isAbsolute(dataDir)) return dataDir;
129
+
130
+ const safeParts = dataDir
131
+ .split(/[\\/]+/)
132
+ .filter((part) => part && part !== "." && part !== "..");
133
+ const safeRelative =
134
+ safeParts.length > 0
135
+ ? path.join(...safeParts)
136
+ : path.join("data", "pglite");
137
+ return path.join("/tmp", safeRelative);
138
+ }
139
+
140
+ async function preparePgliteDataDir(dataDir: string): Promise<string> {
141
+ const runtimeDataDir = pgliteRuntimeDataDir(dataDir);
142
+ if (runtimeDataDir === "memory://") return runtimeDataDir;
143
+
144
+ try {
145
+ const fs = await import("fs");
146
+ fs.mkdirSync(runtimeDataDir, { recursive: true });
147
+ } catch {
148
+ // Edge runtimes may not expose fs. PGlite will surface any real open error.
149
+ }
150
+ return runtimeDataDir;
151
+ }
152
+
126
153
  async function importOptionalModule(specifier: string): Promise<any> {
127
154
  return import(/* @vite-ignore */ specifier);
128
155
  }
@@ -170,7 +197,7 @@ export async function loadPgliteDrizzle(): Promise<{
170
197
  const _pgliteClients = new Map<string, Promise<any>>();
171
198
 
172
199
  export async function getPgliteClient(url: string): Promise<any> {
173
- const dataDir = pgliteDataDirFromUrl(url);
200
+ const dataDir = await preparePgliteDataDir(pgliteDataDirFromUrl(url));
174
201
  let ready = _pgliteClients.get(dataDir);
175
202
  if (!ready) {
176
203
  ready = loadPglitePackage().then(({ PGlite }) => PGlite.create(dataDir));
@@ -190,7 +217,7 @@ export async function closePgliteClients(): Promise<void> {
190
217
  }
191
218
 
192
219
  export async function closePgliteClient(url: string): Promise<void> {
193
- const dataDir = pgliteDataDirFromUrl(url);
220
+ const dataDir = pgliteRuntimeDataDir(pgliteDataDirFromUrl(url));
194
221
  const ready = _pgliteClients.get(dataDir);
195
222
  _pgliteClients.delete(dataDir);
196
223
  if (!ready) return;
@@ -58,6 +58,7 @@ export const PROVIDER_API_IDS = [
58
58
  "ga4",
59
59
  "gcloud",
60
60
  "github",
61
+ "figma",
61
62
  "gmail",
62
63
  "gong",
63
64
  "google_calendar",
@@ -374,6 +375,7 @@ export interface ProviderApiExample {
374
375
  label: string;
375
376
  method: ProviderApiMethod;
376
377
  path: string;
378
+ query?: unknown;
377
379
  body?: unknown;
378
380
  }
379
381
 
@@ -777,6 +779,33 @@ const PROVIDER_CONFIGS: Record<ProviderApiId, ProviderApiConfig> = {
777
779
  { label: "Search issues", method: "GET", path: "/search/issues" },
778
780
  ],
779
781
  },
782
+ figma: {
783
+ id: "figma",
784
+ label: "Figma REST API",
785
+ defaultBaseUrl: "https://api.figma.com/v1",
786
+ auth: {
787
+ type: "api-key-header",
788
+ key: "FIGMA_ACCESS_TOKEN",
789
+ header: "X-Figma-Token",
790
+ },
791
+ credentialKeys: ["FIGMA_ACCESS_TOKEN"],
792
+ docsUrls: ["https://www.figma.com/developers/api"],
793
+ allowedHostSuffixes: ["figma.com"],
794
+ templateUses: ["design"],
795
+ examples: [
796
+ {
797
+ label: "List file components",
798
+ method: "GET",
799
+ path: "/files/{fileKey}/components",
800
+ },
801
+ {
802
+ label: "Render file nodes",
803
+ method: "GET",
804
+ path: "/images/{fileKey}",
805
+ query: { ids: "1:2", format: "svg" },
806
+ },
807
+ ],
808
+ },
780
809
  gmail: {
781
810
  id: "gmail",
782
811
  label: "Gmail API",
@@ -236,6 +236,11 @@ export async function runDbHealthProbe(
236
236
  const DEFAULT_BUILDER_WAITLIST_FORM_ID = "DYTHuM0jlV";
237
237
  const DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN = "https://forms.agent-native.com";
238
238
  const BUILDER_WAITLIST_FORM_SOURCE = "connect_builder_card";
239
+ const BUILDER_WAITLIST_DEFAULT_USE_CASE = "builder_agent_background_coding";
240
+ const BUILDER_WAITLIST_USE_CASES = new Set([
241
+ BUILDER_WAITLIST_DEFAULT_USE_CASE,
242
+ "design_publish_app",
243
+ ]);
239
244
  const BUILDER_WAITLIST_FORM_TIMEOUT_MS = 8000;
240
245
  const BUILDER_WAITLIST_TEXT_LIMIT = 4000;
241
246
 
@@ -244,12 +249,13 @@ interface BuilderWaitlistFormTarget {
244
249
  formsOrigin: string;
245
250
  }
246
251
 
247
- interface BuilderWaitlistBody {
252
+ export interface BuilderWaitlistBody {
248
253
  prompt?: unknown;
249
254
  orgName?: unknown;
250
255
  appUrl?: unknown;
251
256
  pageUrl?: unknown;
252
257
  source?: unknown;
258
+ useCase?: unknown;
253
259
  }
254
260
 
255
261
  export function resolveFrameworkSseRoutes(sseRoute?: string): string[] {
@@ -274,6 +280,13 @@ function cleanBuilderWaitlistText(
274
280
  return trimmed.slice(0, maxLength);
275
281
  }
276
282
 
283
+ function normalizeBuilderWaitlistUseCase(value: unknown): string {
284
+ const useCase = cleanBuilderWaitlistText(value, 100);
285
+ return useCase && BUILDER_WAITLIST_USE_CASES.has(useCase)
286
+ ? useCase
287
+ : BUILDER_WAITLIST_DEFAULT_USE_CASE;
288
+ }
289
+
277
290
  function normalizeHttpOrigin(value: string): string | null {
278
291
  try {
279
292
  const url = new URL(value);
@@ -317,20 +330,45 @@ export function resolveBuilderWaitlistFormTargetForRequest(
317
330
  return { formId, formsOrigin };
318
331
  }
319
332
 
320
- async function submitBuilderWaitlistForm(
333
+ export function buildBuilderWaitlistFormPayload(
321
334
  event: H3Event,
322
335
  sessionEmail: string,
323
336
  body: BuilderWaitlistBody,
324
- ): Promise<{ submitted: boolean; formId?: string }> {
325
- const target = resolveBuilderWaitlistFormTargetForRequest(event);
326
- if (!target) return { submitted: false };
327
-
337
+ ) {
328
338
  const appUrl =
329
339
  cleanBuilderWaitlistText(body.pageUrl ?? body.appUrl, 2000) ??
330
340
  cleanBuilderWaitlistText(getHeader(event, "referer"), 2000) ??
331
341
  getOrigin(event);
332
342
  const source =
333
343
  cleanBuilderWaitlistText(body.source, 100) ?? BUILDER_WAITLIST_FORM_SOURCE;
344
+ const useCase = normalizeBuilderWaitlistUseCase(body.useCase);
345
+
346
+ return {
347
+ data: {
348
+ email: sessionEmail,
349
+ orgName: cleanBuilderWaitlistText(body.orgName, 500),
350
+ appUrl,
351
+ prompt: cleanBuilderWaitlistText(body.prompt),
352
+ source,
353
+ useCase,
354
+ },
355
+ _hp: "",
356
+ _meta: {
357
+ submitterEmail: sessionEmail,
358
+ pageUrl: appUrl,
359
+ source,
360
+ useCase,
361
+ },
362
+ };
363
+ }
364
+
365
+ async function submitBuilderWaitlistForm(
366
+ event: H3Event,
367
+ sessionEmail: string,
368
+ body: BuilderWaitlistBody,
369
+ ): Promise<{ submitted: boolean; formId?: string }> {
370
+ const target = resolveBuilderWaitlistFormTargetForRequest(event);
371
+ if (!target) return { submitted: false };
334
372
 
335
373
  const controller = new AbortController();
336
374
  const timeout = setTimeout(
@@ -344,20 +382,9 @@ async function submitBuilderWaitlistForm(
344
382
  {
345
383
  method: "POST",
346
384
  headers: { "Content-Type": "application/json" },
347
- body: JSON.stringify({
348
- data: {
349
- email: sessionEmail,
350
- orgName: cleanBuilderWaitlistText(body.orgName, 500),
351
- appUrl,
352
- prompt: cleanBuilderWaitlistText(body.prompt),
353
- source,
354
- },
355
- _hp: "",
356
- _meta: {
357
- submitterEmail: sessionEmail,
358
- pageUrl: appUrl,
359
- },
360
- }),
385
+ body: JSON.stringify(
386
+ buildBuilderWaitlistFormPayload(event, sessionEmail, body),
387
+ ),
361
388
  signal: controller.signal,
362
389
  },
363
390
  );
@@ -1671,6 +1698,13 @@ export function createCoreRoutesPlugin(
1671
1698
  }
1672
1699
  const body = ((await readBody(event).catch(() => ({}))) ??
1673
1700
  {}) as BuilderWaitlistBody;
1701
+ const waitlistPayload = buildBuilderWaitlistFormPayload(
1702
+ event,
1703
+ session.email,
1704
+ body,
1705
+ );
1706
+ const waitlistSource = waitlistPayload.data.source;
1707
+ const waitlistUseCase = waitlistPayload.data.useCase;
1674
1708
  let formSubmission: { submitted: boolean; formId?: string };
1675
1709
  try {
1676
1710
  formSubmission = await submitBuilderWaitlistForm(
@@ -1686,7 +1720,9 @@ export function createCoreRoutesPlugin(
1686
1720
  {
1687
1721
  reason:
1688
1722
  err instanceof Error ? err.message : "unknown_waitlist_error",
1723
+ source: waitlistSource,
1689
1724
  stage: "waitlist",
1725
+ useCase: waitlistUseCase,
1690
1726
  },
1691
1727
  );
1692
1728
  setResponseStatus(event, 502);
@@ -1702,7 +1738,9 @@ export function createCoreRoutesPlugin(
1702
1738
  {
1703
1739
  formId: formSubmission.formId ?? null,
1704
1740
  formSubmitted: formSubmission.submitted,
1741
+ source: waitlistSource,
1705
1742
  stage: "waitlist",
1743
+ useCase: waitlistUseCase,
1706
1744
  },
1707
1745
  );
1708
1746
  return { ok: true, formSubmitted: formSubmission.submitted };
@@ -537,6 +537,60 @@
537
537
  }
538
538
  }
539
539
 
540
+ /* Connect AI card: use the card width, not the viewport, so narrow embedded
541
+ sidebars stack the copy and actions before the secondary button clips. */
542
+ .agent-builder-setup-card {
543
+ min-inline-size: min(18rem, 100%);
544
+ }
545
+
546
+ .agent-builder-setup-card--sidebar {
547
+ container-type: inline-size;
548
+ container-name: agent-builder-setup;
549
+ }
550
+
551
+ @container agent-builder-setup (max-width: 560px) {
552
+ .agent-builder-setup-card__content {
553
+ align-items: stretch;
554
+ flex-direction: column;
555
+ }
556
+
557
+ .agent-builder-setup-card__actions {
558
+ align-items: center;
559
+ flex-direction: row;
560
+ flex-wrap: wrap;
561
+ gap: 0.5rem;
562
+ width: 100%;
563
+ }
564
+
565
+ .agent-builder-setup-card__builder-cta {
566
+ align-items: stretch;
567
+ flex: 1 1 11rem;
568
+ }
569
+
570
+ .agent-builder-setup-card__builder-button,
571
+ .agent-builder-setup-card__key-button {
572
+ justify-content: center;
573
+ min-width: 0;
574
+ width: 100%;
575
+ }
576
+
577
+ .agent-builder-setup-card__key-button {
578
+ border: 1px solid hsl(var(--border));
579
+ background: hsl(var(--background));
580
+ color: hsl(var(--foreground));
581
+ flex: 1 1 8rem;
582
+ height: 2rem;
583
+ padding-inline: 0.75rem;
584
+ }
585
+ }
586
+
587
+ @container agent-builder-setup (max-width: 360px) {
588
+ .agent-builder-setup-card__actions {
589
+ align-items: stretch;
590
+ flex-direction: column;
591
+ }
592
+ }
593
+
540
594
  .agent-composer-area--hero {
541
595
  padding-inline: 0;
542
596
  padding-block: 0.5rem;
@@ -112,12 +112,12 @@ const PRICING: Array<{ match: RegExp; pricing: ModelPricing }> = [
112
112
  // ── Google Gemini ───────────────────────────────────────────────────────────
113
113
  // Gemini 3 Pro / 3.1 Pro: ~$1.25/$10 per MTok (approximate; verify on billing)
114
114
  {
115
- match: /gemini-3[.\-]1-pro/i,
115
+ match: /gemini-3[.-]1-pro/i,
116
116
  pricing: { input: 125, output: 1000, cacheRead: 31, cacheWrite: 0 },
117
117
  },
118
118
  // Gemini 3.5 Flash / 3 Flash: ~$0.15/$0.60 per MTok
119
119
  {
120
- match: /gemini-3[.\-][0-9]+-flash/i,
120
+ match: /gemini-3[.-][0-9]+-flash/i,
121
121
  pricing: { input: 15, output: 60, cacheRead: 4, cacheWrite: 0 },
122
122
  },
123
123
  // Gemini 2.5 Pro/Flash (catch-all for older 2.5)