@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
@@ -42,8 +42,8 @@ const DASHBOARD_REPORT_SCREENSHOT_PARAM = "reportScreenshot";
42
42
  const DASHBOARD_REPORT_SETTINGS_PARAM = "reportSettings";
43
43
  const DASHBOARD_REPORT_CID = "dashboard-report-snapshot";
44
44
  const LOCAL_SCREENSHOT_TIMEOUT_MS = 90_000;
45
- const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 25_000;
46
- const SERVERLESS_SECOND_READY_TIMEOUT_MS = 10_000;
45
+ const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 60_000;
46
+ const SERVERLESS_SECOND_READY_TIMEOUT_MS = 30_000;
47
47
  const MAX_SCREENSHOT_VIEWPORT_HEIGHT = 30_000;
48
48
  const SCREENSHOT_VIEWPORT_PADDING = 64;
49
49
 
@@ -249,11 +249,28 @@ async function launchScreenshotBrowser() {
249
249
  }
250
250
 
251
251
  function screenshotTimeoutMs(): number {
252
+ const configured = positiveIntEnv("DASHBOARD_REPORT_SCREENSHOT_TIMEOUT_MS");
253
+ if (configured) return configured;
252
254
  return isServerlessBrowserRuntime()
253
255
  ? SERVERLESS_SCREENSHOT_TIMEOUT_MS
254
256
  : LOCAL_SCREENSHOT_TIMEOUT_MS;
255
257
  }
256
258
 
259
+ function secondReadyTimeoutMs(): number {
260
+ const configured = positiveIntEnv("DASHBOARD_REPORT_SECOND_READY_TIMEOUT_MS");
261
+ if (configured) return configured;
262
+ return isServerlessBrowserRuntime()
263
+ ? SERVERLESS_SECOND_READY_TIMEOUT_MS
264
+ : screenshotTimeoutMs();
265
+ }
266
+
267
+ function positiveIntEnv(name: string): number | null {
268
+ const raw = process.env[name]?.trim();
269
+ if (!raw) return null;
270
+ const parsed = Number.parseInt(raw, 10);
271
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
272
+ }
273
+
257
274
  async function waitForDashboardReportReady(
258
275
  page: any,
259
276
  timeout: number,
@@ -393,12 +410,7 @@ async function captureDashboardPng(
393
410
  await capture.waitFor({ state: "visible", timeout });
394
411
  await waitForDashboardReportReady(page, timeout);
395
412
  await scrollDashboardForLazyRendering(page);
396
- await waitForDashboardReportReady(
397
- page,
398
- isServerlessBrowserRuntime()
399
- ? SERVERLESS_SECOND_READY_TIMEOUT_MS
400
- : timeout,
401
- );
413
+ await waitForDashboardReportReady(page, secondReadyTimeoutMs());
402
414
 
403
415
  await fitViewportToDashboardCapture(page, capture, attempt.viewport);
404
416
  await capture.scrollIntoViewIfNeeded();
@@ -478,18 +490,15 @@ function renderReportEmailHtml(
478
490
  return `<!doctype html>
479
491
  <html>
480
492
  <body style="margin:0;padding:24px;background:#ffffff;color:#171717;font-family:Inter,Arial,sans-serif;">
481
- <h3 style="margin:0 0 8px;font-size:18px;line-height:1.35;font-weight:600;">
482
- Here's the report of <a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">${title}</a> for ${date}.
483
- </h3>
493
+ <p style="margin:0 0 12px;font-size:15px;line-height:1.4;font-weight:600;">
494
+ Here's your report of <a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">${title}</a> for ${date}
495
+ </p>
484
496
  ${screenshotBlock}
485
497
  <p style="margin:18px 0 0;color:#525866;font-size:13px;line-height:1.45;">
486
498
  <a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">Open dashboard</a>
487
499
  <span style="color:#9ca3af;"> · </span>
488
500
  <a href="${reportSettingsUrl}" style="color:#2563eb;text-decoration:none;">Edit subscription settings</a>
489
501
  </p>
490
- <p style="margin:6px 0 0;color:#6b7280;font-size:12px;line-height:1.45;">
491
- Change recipients, delivery time, filters, or turn this report on/off.
492
- </p>
493
502
  </body>
494
503
  </html>`;
495
504
  }
@@ -521,6 +530,7 @@ function reportFilename(title: string): string {
521
530
 
522
531
  export async function sendDashboardReportSubscription(
523
532
  sub: DashboardReportSubscription,
533
+ options: { requireScreenshot?: boolean } = {},
524
534
  ): Promise<{
525
535
  dashboardUrl: string;
526
536
  recipientCount: number;
@@ -530,6 +540,13 @@ export async function sendDashboardReportSubscription(
530
540
  }> {
531
541
  const snapshot = await collectReportSnapshot(sub);
532
542
  const capture = await captureDashboardPngWithFallback(sub, snapshot);
543
+ if (!capture.png && options.requireScreenshot) {
544
+ throw new Error(
545
+ capture.error
546
+ ? `Dashboard screenshot unavailable: ${capture.error}`
547
+ : "Dashboard screenshot unavailable",
548
+ );
549
+ }
533
550
  const screenshotAttached = Boolean(capture.png);
534
551
  const html = renderReportEmailHtml(snapshot, { screenshotAttached });
535
552
  const text = renderReportText(snapshot, { screenshotAttached });
@@ -7,6 +7,7 @@ import { z } from "zod";
7
7
  import { IMAGE_QUALITY_TIERS, STYLE_STRENGTHS } from "../shared/api.js";
8
8
 
9
9
  const mediaTypeSchema = z.enum(["image", "video"]);
10
+ const layoutSchema = z.enum(["default", "vertical"]);
10
11
  const booleanParam = z.preprocess((value) => {
11
12
  if (typeof value === "boolean") return value;
12
13
  if (typeof value !== "string") return value;
@@ -77,6 +78,11 @@ const schema = z.object({
77
78
  .string()
78
79
  .optional()
79
80
  .describe("Calling app id, for audit grouping, e.g. design."),
81
+ layout: layoutSchema
82
+ .default("default")
83
+ .describe(
84
+ "Picker layout density. Use vertical when embedding in a narrow sidebar.",
85
+ ),
80
86
  });
81
87
 
82
88
  type OpenAssetPickerArgs = z.infer<typeof schema>;
@@ -132,6 +138,7 @@ function pickerPath(args: Partial<OpenAssetPickerArgs>): string {
132
138
  }
133
139
  if (args.includeLogo) params.set("includeLogo", "1");
134
140
  if (args.callerAppId?.trim()) params.set("callerAppId", args.callerAppId);
141
+ if (args.layout === "vertical") params.set("layout", "vertical");
135
142
  for (const runId of args.candidateRunIds ?? []) {
136
143
  if (runId.trim()) params.append("candidateRunIds", runId.trim());
137
144
  }
@@ -206,6 +213,7 @@ const action = defineAction({
206
213
  prompt: args.prompt ?? null,
207
214
  aspectRatio: args.aspectRatio ?? null,
208
215
  presetId: args.presetId ?? null,
216
+ layout: args.layout,
209
217
  _writeId: `open-asset-picker-${Date.now()}-${Math.random()
210
218
  .toString(36)
211
219
  .slice(2, 8)}`,
@@ -219,6 +227,7 @@ const action = defineAction({
219
227
  path,
220
228
  url: path,
221
229
  embed: true,
230
+ layout: args.layout,
222
231
  title:
223
232
  args.mediaType === "video"
224
233
  ? "Select a video asset"
@@ -191,6 +191,21 @@
191
191
  }
192
192
  }
193
193
 
194
+ .assets-picker-vertical .assets-library-dense-grid,
195
+ .assets-picker-vertical .assets-library-grid {
196
+ grid-template-columns: minmax(0, 1fr);
197
+ }
198
+
199
+ .assets-picker-vertical [role="tablist"] {
200
+ width: 100%;
201
+ }
202
+
203
+ .assets-picker-vertical [role="tab"] {
204
+ min-width: 0;
205
+ flex: 1 1 0;
206
+ padding-inline: 0.5rem;
207
+ }
208
+
194
209
  @container agent-native-main (min-width: 980px) {
195
210
  .assets-brand-kit-preview-grid {
196
211
  grid-template-columns: minmax(0, 1fr) 284px;
@@ -73,6 +73,7 @@ function navigationFromPath(pathname: string, search = "") {
73
73
  query: optionalParam(params, "q"),
74
74
  prompt: optionalParam(params, "prompt"),
75
75
  aspectRatio: optionalParam(params, "aspectRatio"),
76
+ layout: params.get("layout") === "vertical" ? "vertical" : undefined,
76
77
  };
77
78
  }
78
79
  const queryLibraryId = optionalParam(params, "libraryId");
@@ -153,6 +154,9 @@ function pathFromCommand(command: any): string | null {
153
154
  if (typeof command.aspectRatio === "string" && command.aspectRatio.trim()) {
154
155
  params.set("aspectRatio", command.aspectRatio.trim());
155
156
  }
157
+ if (command.layout === "vertical") {
158
+ params.set("layout", "vertical");
159
+ }
156
160
  const query = params.toString();
157
161
  return query ? `/library?${query}` : "/library";
158
162
  }
@@ -129,6 +129,7 @@ const MCP_APP_CHAT_BRIDGE_QUERY_PARAM = "__an_mcp_chat_bridge";
129
129
  const PICKER_INLINE_SELECT_CLASS =
130
130
  "h-7 w-auto min-w-0 max-w-full rounded-md border-0 bg-transparent px-1.5 py-1 text-xs font-medium text-muted-foreground shadow-none ring-offset-transparent transition hover:bg-accent/50 hover:text-foreground focus:ring-0 focus:ring-offset-0 sm:px-2 [&>svg]:ms-1 [&>svg]:size-3.5 [&>svg]:opacity-60";
131
131
  type PickerMediaType = "image" | "video";
132
+ type PickerLayout = "default" | "vertical";
132
133
 
133
134
  type Asset = {
134
135
  id: string;
@@ -193,6 +194,7 @@ type HostConfig = {
193
194
  styleStrength?: StyleStrength;
194
195
  includeLogo?: boolean;
195
196
  callerAppId?: string;
197
+ layout?: PickerLayout;
196
198
  autoGenerate?: boolean;
197
199
  candidateRunIds?: string[];
198
200
  };
@@ -244,6 +246,10 @@ function normalizeMediaType(value: unknown): PickerMediaType {
244
246
  return value === "video" ? "video" : "image";
245
247
  }
246
248
 
249
+ function normalizePickerLayout(value: unknown): PickerLayout | undefined {
250
+ return value === "vertical" ? "vertical" : undefined;
251
+ }
252
+
247
253
  function normalizeBoolean(value: unknown): boolean | undefined {
248
254
  if (typeof value === "boolean") return value;
249
255
  if (typeof value !== "string") return undefined;
@@ -320,6 +326,7 @@ function normalizeHostConfig(value: unknown): HostConfig {
320
326
  includeLogo: normalizeBoolean(record.includeLogo),
321
327
  callerAppId:
322
328
  typeof record.callerAppId === "string" ? record.callerAppId : undefined,
329
+ layout: normalizePickerLayout(record.layout),
323
330
  candidateRunIds: normalizeCandidateRunIds(record.candidateRunIds),
324
331
  };
325
332
  if (Object.prototype.hasOwnProperty.call(record, "autoGenerate")) {
@@ -1999,6 +2006,7 @@ export function AssetPickerSurface() {
1999
2006
  styleStrength: normalizeStyleStrength(params.get("styleStrength")),
2000
2007
  includeLogo: normalizeBoolean(params.get("includeLogo")),
2001
2008
  callerAppId: params.get("callerAppId") ?? undefined,
2009
+ layout: normalizePickerLayout(params.get("layout")),
2002
2010
  candidateRunIds: normalizeCandidateRunIds(
2003
2011
  params.getAll("candidateRunIds").length > 0
2004
2012
  ? params.getAll("candidateRunIds")
@@ -2024,6 +2032,9 @@ export function AssetPickerSurface() {
2024
2032
  const [mediaType, setMediaType] = useState<PickerMediaType>(
2025
2033
  () => hostConfig.mediaType ?? "image",
2026
2034
  );
2035
+ const [pickerLayout, setPickerLayout] = useState<PickerLayout>(
2036
+ () => hostConfig.layout ?? "default",
2037
+ );
2027
2038
  const [query, setQuery] = useState(() => hostConfig.query ?? "");
2028
2039
  const [prompt, setPrompt] = useState(() => hostConfig.prompt ?? "");
2029
2040
  const [aspectRatio, setAspectRatio] = useState<string>(
@@ -2046,6 +2057,7 @@ export function AssetPickerSurface() {
2046
2057
  useEffect(() => {
2047
2058
  setHostConfig((current) => ({ ...current, ...urlHostConfig }));
2048
2059
  setMediaType(urlHostConfig.mediaType ?? "image");
2060
+ setPickerLayout(urlHostConfig.layout ?? "default");
2049
2061
  setQuery(urlHostConfig.query ?? "");
2050
2062
  setPrompt(urlHostConfig.prompt ?? "");
2051
2063
  setSelectedLibraryId(urlHostConfig.libraryId ?? "");
@@ -2504,6 +2516,7 @@ export function AssetPickerSurface() {
2504
2516
  const next = normalizeHostConfig(payload);
2505
2517
  setHostConfig((current) => ({ ...current, ...next }));
2506
2518
  if (next.mediaType !== undefined) setMediaType(next.mediaType);
2519
+ if (next.layout !== undefined) setPickerLayout(next.layout);
2507
2520
  if (next.query !== undefined) setQuery(next.query);
2508
2521
  if (next.prompt !== undefined) setPrompt(next.prompt);
2509
2522
  if (next.libraryId !== undefined) setSelectedLibraryId(next.libraryId);
@@ -2533,10 +2546,11 @@ export function AssetPickerSurface() {
2533
2546
  query,
2534
2547
  prompt,
2535
2548
  aspectRatio,
2549
+ layout: pickerLayout,
2536
2550
  },
2537
2551
  { requestSource: "assets-picker-ui" },
2538
2552
  ).catch(() => {});
2539
- }, [aspectRatio, mediaType, prompt, query, selectedLibraryId]);
2553
+ }, [aspectRatio, mediaType, pickerLayout, prompt, query, selectedLibraryId]);
2540
2554
 
2541
2555
  const canGenerate =
2542
2556
  mediaType === "image" &&
@@ -2652,14 +2666,22 @@ export function AssetPickerSurface() {
2652
2666
  waitingForRequestedPreset,
2653
2667
  ]);
2654
2668
 
2669
+ const verticalLayout = pickerLayout === "vertical";
2670
+
2655
2671
  return (
2656
2672
  <div
2657
2673
  className={cn(
2658
2674
  "flex flex-col overflow-hidden bg-background text-foreground",
2659
2675
  embedded ? "h-screen w-screen" : "h-full w-full",
2676
+ verticalLayout && "assets-picker-vertical",
2660
2677
  )}
2661
2678
  >
2662
- <header className="flex h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-3">
2679
+ <header
2680
+ className={cn(
2681
+ "flex shrink-0 items-center justify-between gap-3 border-b border-border",
2682
+ verticalLayout ? "h-10 px-2" : "h-12 px-3",
2683
+ )}
2684
+ >
2663
2685
  <div className="min-w-0 truncate text-sm font-semibold">
2664
2686
  {embedded ? t("navigation.brand") : t("library.library")}
2665
2687
  </div>
@@ -2668,7 +2690,7 @@ export function AssetPickerSurface() {
2668
2690
  <Button
2669
2691
  variant={showCreatePane ? "secondary" : "default"}
2670
2692
  size="sm"
2671
- className="h-8 gap-1.5"
2693
+ className={cn("gap-1.5", verticalLayout ? "h-7 px-2" : "h-8")}
2672
2694
  onClick={() => setShowCreatePane((open) => !open)}
2673
2695
  >
2674
2696
  {showCreatePane ? (
@@ -2676,7 +2698,9 @@ export function AssetPickerSurface() {
2676
2698
  ) : (
2677
2699
  <IconPhotoPlus className="h-3.5 w-3.5" />
2678
2700
  )}
2679
- {showCreatePane ? t("library.close") : t("library.createPane")}
2701
+ <span className={verticalLayout ? "sr-only" : undefined}>
2702
+ {showCreatePane ? t("library.close") : t("library.createPane")}
2703
+ </span>
2680
2704
  </Button>
2681
2705
  )}
2682
2706
  {embedded && (
@@ -2705,9 +2729,14 @@ export function AssetPickerSurface() {
2705
2729
  </header>
2706
2730
 
2707
2731
  {showCreatePane && mediaType === "image" && (
2708
- <section className="shrink-0 border-b border-border px-3 py-3">
2732
+ <section
2733
+ className={cn(
2734
+ "shrink-0 border-b border-border",
2735
+ verticalLayout ? "px-2 py-2" : "px-3 py-3",
2736
+ )}
2737
+ >
2709
2738
  {mediaType === "image" ? (
2710
- <div className="mt-2 rounded-lg border border-border/80 bg-background focus-within:ring-1 focus-within:ring-ring">
2739
+ <div className="rounded-lg border border-border/80 bg-background focus-within:ring-1 focus-within:ring-ring">
2711
2740
  <Input
2712
2741
  type="text"
2713
2742
  value={prompt}
@@ -2724,10 +2753,23 @@ export function AssetPickerSurface() {
2724
2753
  if (canGenerate) runGenerate();
2725
2754
  }}
2726
2755
  placeholder={t("library.generateImagePlaceholder")}
2727
- className="h-11 border-0 bg-transparent px-3 py-2.5 leading-6 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
2756
+ className={cn(
2757
+ "border-0 bg-transparent px-3 leading-6 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0",
2758
+ verticalLayout ? "h-9 py-2 text-xs" : "h-11 py-2.5",
2759
+ )}
2728
2760
  />
2729
- <div className="flex items-center gap-1 px-2 pb-2">
2730
- <div className="flex min-w-0 flex-1 items-center justify-end gap-0.5 sm:gap-1">
2761
+ <div
2762
+ className={cn(
2763
+ "flex gap-1 px-2 pb-2",
2764
+ verticalLayout ? "flex-col items-stretch" : "items-center",
2765
+ )}
2766
+ >
2767
+ <div
2768
+ className={cn(
2769
+ "flex min-w-0 flex-1 items-center gap-0.5 sm:gap-1",
2770
+ verticalLayout ? "justify-between" : "justify-end",
2771
+ )}
2772
+ >
2731
2773
  <Select value={aspectRatio} onValueChange={setAspectRatio}>
2732
2774
  <SelectTrigger
2733
2775
  aria-label={t("brandKitDetail.aspectRatio")}
@@ -2792,7 +2834,10 @@ export function AssetPickerSurface() {
2792
2834
  ) : null}
2793
2835
  </div>
2794
2836
  <Button
2795
- className="h-7 min-w-[5.75rem] shrink-0 px-3 text-xs"
2837
+ className={cn(
2838
+ "h-7 shrink-0 px-3 text-xs",
2839
+ !verticalLayout && "min-w-[5.75rem]",
2840
+ )}
2796
2841
  disabled={!canGenerate}
2797
2842
  onClick={runGenerate}
2798
2843
  >
@@ -2932,12 +2977,33 @@ export function AssetPickerSurface() {
2932
2977
  </section>
2933
2978
  )}
2934
2979
 
2935
- <main className="min-h-0 flex-1 overflow-y-auto p-3">
2980
+ <main
2981
+ className={cn(
2982
+ "min-h-0 flex-1 overflow-y-auto",
2983
+ verticalLayout ? "p-2" : "p-3",
2984
+ )}
2985
+ >
2936
2986
  {displayLibraries.length > 0 && (
2937
- <div className="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
2938
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
2987
+ <div
2988
+ className={cn(
2989
+ "mb-3 flex flex-col gap-2",
2990
+ !verticalLayout &&
2991
+ "sm:flex-row sm:items-center sm:justify-between",
2992
+ )}
2993
+ >
2994
+ <div
2995
+ className={cn(
2996
+ "flex flex-col gap-2",
2997
+ !verticalLayout && "sm:flex-row sm:items-center",
2998
+ )}
2999
+ >
2939
3000
  {draftsGlobalView ? (
2940
- <div className="flex h-9 items-center rounded-md border border-border/70 bg-background px-3 text-sm text-muted-foreground sm:w-48">
3001
+ <div
3002
+ className={cn(
3003
+ "flex h-9 items-center rounded-md border border-border/70 bg-background px-3 text-sm text-muted-foreground",
3004
+ !verticalLayout && "sm:w-48",
3005
+ )}
3006
+ >
2941
3007
  {t("library.allLibraries")}
2942
3008
  </div>
2943
3009
  ) : (
@@ -2945,7 +3011,12 @@ export function AssetPickerSurface() {
2945
3011
  value={selectedLibraryId}
2946
3012
  onValueChange={setSelectedLibraryId}
2947
3013
  >
2948
- <SelectTrigger className="h-9 w-full border-border/70 bg-background sm:w-48">
3014
+ <SelectTrigger
3015
+ className={cn(
3016
+ "h-9 w-full border-border/70 bg-background",
3017
+ !verticalLayout && "sm:w-48",
3018
+ )}
3019
+ >
2949
3020
  <SelectValue placeholder={t("library.library")} />
2950
3021
  </SelectTrigger>
2951
3022
  <SelectContent>
@@ -2990,7 +3061,10 @@ export function AssetPickerSurface() {
2990
3061
  onInput={(event) => setQuery(event.currentTarget.value)}
2991
3062
  onChange={(event) => setQuery(event.target.value)}
2992
3063
  placeholder={t("library.searchMedia", { mediaLabel })}
2993
- className="h-9 border-border/70 bg-background sm:max-w-xs"
3064
+ className={cn(
3065
+ "h-9 border-border/70 bg-background",
3066
+ !verticalLayout && "sm:max-w-xs",
3067
+ )}
2994
3068
  />
2995
3069
  )}
2996
3070
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ The Assets picker now supports a vertical embedded layout for sidebar workflows.
@@ -10,7 +10,6 @@
10
10
 
11
11
  import { defineAction } from "@agent-native/core";
12
12
  import {
13
- appStateList,
14
13
  readAppState,
15
14
  writeAppState,
16
15
  deleteAppState,
@@ -28,6 +27,7 @@ import {
28
27
  applyFaststart,
29
28
  hasPlayableMp4Metadata,
30
29
  } from "../server/lib/faststart.js";
30
+ import { listRecordingChunkKeys } from "../server/lib/recording-upload-state.js";
31
31
  import {
32
32
  getCurrentOwnerEmail,
33
33
  ownerEmailMatches,
@@ -66,17 +66,6 @@ function concatBytes(parts: Uint8Array[]): Uint8Array {
66
66
  return out;
67
67
  }
68
68
 
69
- async function listRecordingChunkKeys(
70
- ownerEmail: string,
71
- recordingId: string,
72
- ): Promise<string[]> {
73
- const rows = await appStateList(
74
- ownerEmail,
75
- `recording-chunks-${recordingId}-`,
76
- );
77
- return rows.map((row) => row.key);
78
- }
79
-
80
69
  function chunkIndexFromKey(key: string): number {
81
70
  return Number(key.split("-").pop() || 0);
82
71
  }
@@ -86,6 +86,9 @@ import { parsePlaybackSpeed } from "@/lib/playback-speed";
86
86
  import { isStorageSetupFailureReason } from "@/lib/storage-failures";
87
87
  import { cn } from "@/lib/utils";
88
88
 
89
+ const UPLOAD_STUCK_TIMEOUT_MS = 5 * 60 * 1000;
90
+ const PROCESSING_STUCK_TIMEOUT_MS = 2 * 60 * 1000;
91
+
89
92
  export function meta() {
90
93
  return [{ title: enMessages.recordingRoute.pageTitle }];
91
94
  }
@@ -562,10 +565,8 @@ export default function RecordingPage() {
562
565
  .finally(() => playerDataQ.refetch());
563
566
  }, [recording?.id, recording?.status, transcriptStatus, role, playerDataQ]);
564
567
 
565
- // After 30 seconds of non-ready status (without an explicit failure), flip
566
- // a local flag so we can stop pretending this is normal and show an error.
567
- // Even a 10-minute recording's finalize completes in a few seconds with
568
- // the SQL fallback, so anything past 30s means something is wrong.
568
+ // Long browser-extension clips can still be uploading chunks or assembling
569
+ // for more than 30s. Keep polling before surfacing a stuck-state fallback.
569
570
  useEffect(() => {
570
571
  if (!recording) {
571
572
  setProcessingTimeout(false);
@@ -579,7 +580,11 @@ export default function RecordingPage() {
579
580
  setProcessingTimeout(false);
580
581
  return;
581
582
  }
582
- const handle = setTimeout(() => setProcessingTimeout(true), 30_000);
583
+ const timeoutMs =
584
+ recording.status === "processing"
585
+ ? PROCESSING_STUCK_TIMEOUT_MS
586
+ : UPLOAD_STUCK_TIMEOUT_MS;
587
+ const handle = setTimeout(() => setProcessingTimeout(true), timeoutMs);
583
588
  return () => clearTimeout(handle);
584
589
  }, [recording?.status, recording?.videoUrl, recordingId]);
585
590
 
@@ -229,6 +229,8 @@ const CLIPS_SOURCE_URL =
229
229
  const CLIPS_TEMPLATE_URL = "https://www.agent-native.com/templates/clips";
230
230
  const CLIPS_AGENT_DOCS_URL =
231
231
  "https://www.agent-native.com/docs/template-clips#agent-readable-clips";
232
+ const UPLOAD_STUCK_TIMEOUT_MS = 5 * 60 * 1000;
233
+ const PROCESSING_STUCK_TIMEOUT_MS = 2 * 60 * 1000;
232
234
 
233
235
  type ViewerPlatform = "mac" | "windows";
234
236
 
@@ -453,17 +455,13 @@ export default function ShareRoute() {
453
455
  return;
454
456
  }
455
457
 
456
- const progress = Number(recording.uploadProgress ?? 0);
457
458
  const timeoutMs =
458
- recording.status === "processing" || progress >= 95 ? 12_000 : 30_000;
459
+ recording.status === "processing"
460
+ ? PROCESSING_STUCK_TIMEOUT_MS
461
+ : UPLOAD_STUCK_TIMEOUT_MS;
459
462
  const handle = setTimeout(() => setProcessingTimeout(true), timeoutMs);
460
463
  return () => clearTimeout(handle);
461
- }, [
462
- recording?.id,
463
- recording?.status,
464
- recording?.videoUrl,
465
- recording?.uploadProgress,
466
- ]);
464
+ }, [recording?.id, recording?.status, recording?.videoUrl]);
467
465
 
468
466
  usePlayerShortcuts({ playerRef });
469
467
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Chrome extension recordings no longer show an upload failure when the clip finished saving successfully.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-30
4
+ ---
5
+
6
+ Longer Chrome extension recordings now keep saving reliably instead of getting stuck or showing a second countdown.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "Agent-Native Clips",
4
4
  "short_name": "Clips",
5
- "version": "0.1.6",
5
+ "version": "0.1.7",
6
6
  "description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub.",
7
7
  "minimum_chrome_version": "116",
8
8
  "action": {
@@ -261,6 +261,7 @@ const CROSS_TAB_FOLLOW: boolean = false;
261
261
  // The tab the recording was launched from — the only tab activeTab lets us touch.
262
262
  let overlayTabId: number | null = null;
263
263
  let countdownEndsAtMs = 0;
264
+ let armingNativeRecordingSessionId: string | null = null;
264
265
 
265
266
  function desiredParts(): OverlayPart[] {
266
267
  // The on-page controls match the desktop app: a left-edge vertical pill plus
@@ -950,15 +951,7 @@ function createSession(
950
951
  }
951
952
 
952
953
  async function handlePopupStart(message: PopupStartMessage) {
953
- const tab = await queryActiveTab();
954
- if (!tab || typeof tab.id !== "number") {
955
- return { ok: false, error: "No active tab is available to record." };
956
- }
957
-
958
- const settings = await readSettings(message.settings);
959
- await storageSet(settings);
960
-
961
- if (activeNativeRecording) {
954
+ if (activeNativeRecording || armingNativeRecordingSessionId) {
962
955
  return {
963
956
  ok: false,
964
957
  error: "Clips is already recording. Stop the active clip first.",
@@ -966,7 +959,22 @@ async function handlePopupStart(message: PopupStartMessage) {
966
959
  }
967
960
 
968
961
  const sessionId = crypto.randomUUID();
969
- return armRecording({ sessionId, tab, settings });
962
+ armingNativeRecordingSessionId = sessionId;
963
+ try {
964
+ const tab = await queryActiveTab();
965
+ if (!tab || typeof tab.id !== "number") {
966
+ return { ok: false, error: "No active tab is available to record." };
967
+ }
968
+
969
+ const settings = await readSettings(message.settings);
970
+ await storageSet(settings);
971
+
972
+ return await armRecording({ sessionId, tab, settings });
973
+ } finally {
974
+ if (armingNativeRecordingSessionId === sessionId) {
975
+ armingNativeRecordingSessionId = null;
976
+ }
977
+ }
970
978
  }
971
979
 
972
980
  // Arm a Loom-style in-page recording: show the native picker, create the row,
@@ -1474,6 +1482,7 @@ async function handlePopupStatus() {
1474
1482
  return {
1475
1483
  ok: true,
1476
1484
  activeRecording: activeNativeRecording,
1485
+ arming: Boolean(armingNativeRecordingSessionId),
1477
1486
  };
1478
1487
  }
1479
1488