@agent-native/core 0.136.4 → 0.137.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 (170) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +53 -0
  3. package/corpus/templates/clips/actions/import-loom-recording.ts +7 -0
  4. package/corpus/templates/clips/actions/lib/loom-import-job.ts +24 -4
  5. package/corpus/templates/clips/app/components/import-menu.tsx +109 -0
  6. package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
  7. package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
  8. package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
  9. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
  10. package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
  11. package/corpus/templates/clips/changelog/2026-08-03-restart-during-a-recording-now-immediately-starts-a-fresh-ta.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
  13. package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
  14. package/corpus/templates/clips/desktop/src/app.tsx +81 -30
  15. package/corpus/templates/clips/desktop/src/lib/recorder.ts +436 -158
  16. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +14 -1
  17. package/corpus/templates/clips/server/plugins/auth.ts +9 -0
  18. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +13 -0
  19. package/corpus/templates/content/actions/_database-utils.ts +6 -0
  20. package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
  21. package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
  22. package/corpus/templates/content/actions/configure-document-property.ts +113 -32
  23. package/corpus/templates/content/actions/delete-content-database.ts +5 -7
  24. package/corpus/templates/content/actions/delete-document-property.ts +109 -62
  25. package/corpus/templates/content/actions/delete-document.ts +178 -36
  26. package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
  27. package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
  28. package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
  29. package/corpus/templates/content/actions/remove-database-items.ts +13 -0
  30. package/corpus/templates/content/actions/set-document-property.ts +69 -1
  31. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
  32. package/corpus/templates/content/parity/matrix.md +1 -1
  33. package/corpus/templates/content/parity/matrix.ts +1 -0
  34. package/corpus/templates/content/server/db/schema.ts +27 -0
  35. package/corpus/templates/content/server/plugins/db.ts +24 -0
  36. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
  37. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
  38. package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
  39. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
  40. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
  41. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
  42. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
  43. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
  44. package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
  45. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  46. package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
  47. package/corpus/templates/design/app/i18n-data.ts +8 -0
  48. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
  49. package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
  50. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
  51. package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
  52. package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
  53. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
  54. package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
  55. package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
  56. package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
  57. package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
  58. package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
  59. package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
  60. package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
  61. package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
  62. package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
  63. package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
  64. package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
  65. package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
  66. package/corpus/templates/factory/.env.example +17 -0
  67. package/corpus/templates/factory/AGENTS.md +27 -17
  68. package/corpus/templates/factory/README.md +19 -9
  69. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
  70. package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
  71. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
  72. package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
  73. package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
  74. package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
  75. package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
  76. package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
  77. package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
  78. package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
  79. package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
  80. package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
  81. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
  82. package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
  83. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
  84. package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
  85. package/corpus/templates/factory/app/routes/factory.tsx +462 -9
  86. package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
  87. package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
  88. package/corpus/templates/factory/netlify.toml +2 -2
  89. package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
  90. package/corpus/templates/factory/server/connectors/slack.ts +133 -5
  91. package/corpus/templates/factory/server/db/schema.ts +6 -0
  92. package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
  93. package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
  94. package/corpus/templates/factory/server/plugins/auth.ts +1 -0
  95. package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
  96. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
  97. package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
  98. package/corpus/templates/factory/server/triage/contracts.ts +14 -1
  99. package/corpus/templates/factory/server/triage/github-client.ts +404 -0
  100. package/corpus/templates/factory/server/triage/metadata.ts +44 -0
  101. package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
  102. package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
  103. package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
  104. package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
  105. package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
  106. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
  107. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
  108. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
  109. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
  110. package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
  111. package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
  112. package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
  113. package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
  114. package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
  115. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
  116. package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
  117. package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
  118. package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
  119. package/corpus/templates/plan/shared/plan-content.ts +17 -2
  120. package/dist/cli/skills-content/canvas.d.ts +3 -3
  121. package/dist/cli/skills-content/canvas.js +16 -5
  122. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  123. package/dist/cli/skills-content/visual-plan-skill.js +27 -0
  124. package/dist/cli/skills-content/wireframe.d.ts +2 -2
  125. package/dist/cli/skills-content/wireframe.js +7 -0
  126. package/dist/client/AgentPanel.js +23 -8
  127. package/dist/client/api-path.d.ts +6 -0
  128. package/dist/client/api-path.js +55 -0
  129. package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
  130. package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
  131. package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
  132. package/dist/client/onboarding/first-run-enabled.js +9 -0
  133. package/dist/client/onboarding/index.d.ts +2 -1
  134. package/dist/client/onboarding/index.js +1 -0
  135. package/dist/client/onboarding/use-onboarding.d.ts +6 -1
  136. package/dist/client/onboarding/use-onboarding.js +32 -1
  137. package/dist/client/route-state.js +10 -0
  138. package/dist/client/settings/SecretsSection.js +32 -8
  139. package/dist/collab/struct-routes.d.ts +1 -1
  140. package/dist/connections/catalog.d.ts +4 -4
  141. package/dist/connections/catalog.js +3 -3
  142. package/dist/db/index.d.ts +1 -1
  143. package/dist/db/index.js +1 -1
  144. package/dist/localization/default-messages.d.ts +5 -0
  145. package/dist/localization/default-messages.js +5 -0
  146. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  147. package/dist/observability/routes.d.ts +3 -3
  148. package/dist/onboarding/app-profile.d.ts +4 -0
  149. package/dist/onboarding/app-profile.js +378 -0
  150. package/dist/onboarding/index.d.ts +2 -1
  151. package/dist/onboarding/index.js +1 -0
  152. package/dist/onboarding/plugin.d.ts +2 -0
  153. package/dist/onboarding/plugin.js +21 -1
  154. package/dist/onboarding/types.d.ts +19 -0
  155. package/dist/progress/routes.d.ts +1 -1
  156. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  157. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  158. package/dist/secrets/routes.d.ts +2 -2
  159. package/dist/secrets/routes.js +41 -17
  160. package/dist/server/auth.js +21 -1
  161. package/dist/server/better-auth-instance.d.ts +3 -2
  162. package/dist/server/better-auth-instance.js +9 -7
  163. package/dist/server/email-markdown.js +3 -8
  164. package/dist/server/transcribe-voice.d.ts +1 -1
  165. package/dist/shared/first-run-onboarding.d.ts +3 -0
  166. package/dist/shared/first-run-onboarding.js +3 -0
  167. package/dist/triggers/index.d.ts +3 -0
  168. package/dist/triggers/index.js +5 -0
  169. package/docs/content/cross-app-sso.mdx +26 -0
  170. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 7515
34
+ - template files: 7555
@@ -189,6 +189,59 @@ When mode is `screen+camera`, "the bubble" is two different things:
189
189
 
190
190
  Because that draw loop runs continuously for the whole recording, keep its CPU/GPU cost bounded: a Worker-based timer drives the draw loop at the capture frame rate (`SCREEN_CAPTURE_FRAME_RATE`, 24fps — falling back to `requestAnimationFrame` only if Worker creation fails, e.g. under a strict CSP), the canvas is hard-capped to 1080p-class dimensions (1920px on its longest edge) even if the source display track is Retina/4K, and the bubble's drop shadow is pre-rendered into a small cached sprite (keyed by bubble size) instead of re-blurring with `shadowBlur` every frame.
191
191
 
192
+ ## Restart (desktop)
193
+
194
+ Restart throws the current take away and immediately starts another one. It
195
+ must never re-acquire the screen: the toolbar click reaches the popover through
196
+ async Tauri IPC, which carries no user activation, so a second `getDisplayMedia`
197
+ would throw. Instead the dying session hands its live display and mic streams to
198
+ the replacement — `RecorderHandle.discardForRestart()` returns a `RestartHandoff`
199
+ that `startRecording` accepts as `preAcquiredDisplayStream` /
200
+ `preAcquiredAudioStream`.
201
+
202
+ Ownership is the opposite of the camera's. The popover owns the camera stream for
203
+ the whole session and re-hands it unchanged, so restart must not bump
204
+ `bubbleSessionEpoch` or clear `bubbleStreamTransferredToRecorder`. The handed-off
205
+ display and mic streams belong to the **recorder**, so the new session stops them
206
+ on its own stop/cancel, and whoever asked for the restart must stop them if the
207
+ new session never comes up.
208
+
209
+ `cancel()` and `discardForRestart()` share one `discardTake(forRestart)` per
210
+ backend, and the difference is which teardown they run. Cancel ends the
211
+ *session*: `hide_overlays` (which destroys the camera bubble window too) and
212
+ `clearRecordingState()`. A restart ends only the *take*, so it uses
213
+ `hide_recording_chrome`, which spares the bubble, and leaves the recording state
214
+ active.
215
+
216
+ Stop, cancel and restart are mutually exclusive terminal transitions, so each
217
+ gets its own promise slot — never reuse `cancelPromise` for a discard, or a
218
+ cancel arriving mid-restart returns the take-level teardown and the session
219
+ never ends. A cancel that lands after a discard still owes the session half,
220
+ plus stopping the capture the retake never took ownership of. On the app side
221
+ `restartInFlightRef` latches synchronously so stop and cancel events cannot act
222
+ on the recorder a restart is already tearing down.
223
+
224
+ Native full-screen backends re-acquire capture in Rust and hand off nothing.
225
+ `resolveRestartHandoff` vets the inherited streams before anything is acquired,
226
+ and it treats the two kinds of capture differently: an ended display share is
227
+ fatal and fails with `RESTART_CAPTURE_ENDED_MESSAGE`, because re-acquiring it
228
+ would surface an activation error that names the wrong cause, while an ended
229
+ microphone is just dropped and re-acquired normally. A restart also has to wait
230
+ for the old take's `transcriptionCapture.cancel()` — the engine is process-global
231
+ (`audio_transcription_stop` / `native_speech_stop`), so a late cancel would stop
232
+ the replacement's engine. And `stop()` after a discard must throw rather than
233
+ answer with the discarded take's id, or an aborted recording gets published as a
234
+ finished one. The
235
+ recording-flow latches (`recordingFlowGateRef`, `recordingFlowActive`,
236
+ `clipsForceAlive`, `set_recording_state`) stay held across the restart; releasing
237
+ them the way cancel does lets the popover blur auto-hide fire mid-restart.
238
+
239
+ Holding those latches has a catch. The `show_toolbar` effect is keyed on
240
+ `isRecording || recordingFlowActive`, so it does not re-run when the flow never
241
+ leaves — but the discard already closed the toolbar window. Restart therefore
242
+ bumps `recordingChromeEpoch` to rebuild it. The countdown needs no such nudge;
243
+ the recorder recreates it on every start.
244
+
192
245
  ## Error recovery
193
246
 
194
247
  | Failure | Handling |
@@ -387,11 +387,18 @@ export default defineAction({
387
387
  await writeAppState("navigate", { view: "recording", recordingId: id });
388
388
 
389
389
  try {
390
+ console.log("[import-loom-recording] dispatching loom-import job", {
391
+ recordingId: id,
392
+ provider: providerId,
393
+ });
390
394
  await dispatchPostFinalizeJob({
391
395
  recordingId: id,
392
396
  kind: "loom-import",
393
397
  requireAccepted: true,
394
398
  });
399
+ console.log("[import-loom-recording] loom-import job accepted", {
400
+ recordingId: id,
401
+ });
395
402
  } catch (err) {
396
403
  const failureReason = `Could not start the Loom import: ${
397
404
  err instanceof Error ? err.message : String(err)
@@ -55,6 +55,11 @@ export async function failLoomImport(
55
55
  failureReason: string,
56
56
  claimId?: string,
57
57
  ): Promise<LoomImportJobResult> {
58
+ console.error("[loom-import] failed", {
59
+ recordingId,
60
+ claimId,
61
+ failureReason,
62
+ });
58
63
  const now = new Date().toISOString();
59
64
  const [updated] = await getDb()
60
65
  .update(schema.recordings)
@@ -140,9 +145,16 @@ export async function runLoomImportJob({
140
145
  );
141
146
  }
142
147
 
148
+ console.log("[loom-import] job started", { recordingId, claimId });
149
+
143
150
  let media: Awaited<ReturnType<typeof downloadLoomVideo>>;
144
151
  try {
145
152
  media = await downloadLoomVideo({ loomId, shareUrl });
153
+ console.log("[loom-import] download complete", {
154
+ recordingId,
155
+ bytes: media.sizeBytes,
156
+ mimeType: media.mimeType,
157
+ });
146
158
  } catch (err) {
147
159
  return failLoomImport(
148
160
  recordingId,
@@ -167,6 +179,10 @@ export async function runLoomImportJob({
167
179
  claimId,
168
180
  );
169
181
  }
182
+ console.log("[loom-import] reupload complete", {
183
+ recordingId,
184
+ videoUrl: upload.url,
185
+ });
170
186
 
171
187
  const now = new Date().toISOString();
172
188
  const [mediaReady] = await db
@@ -187,11 +203,15 @@ export async function runLoomImportJob({
187
203
  )
188
204
  .returning({ id: schema.recordings.id });
189
205
  if (!mediaReady) {
190
- return {
191
- status: "failed",
192
- failureReason: "The Loom import lease was lost before media was saved.",
193
- };
206
+ const failureReason =
207
+ "The Loom import lease was lost before media was saved.";
208
+ console.warn("[loom-import] lease lost before ready", {
209
+ recordingId,
210
+ claimId,
211
+ });
212
+ return { status: "failed", failureReason };
194
213
  }
214
+ console.log("[loom-import] recording ready", { recordingId });
195
215
 
196
216
  void queueBuilderMediaCompression({
197
217
  recordingId,
@@ -0,0 +1,109 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { IconChevronDown, IconLink, IconUpload } from "@tabler/icons-react";
3
+ import { Link } from "react-router";
4
+
5
+ import { Button, type ButtonProps } from "@/components/ui/button";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuTrigger,
11
+ } from "@/components/ui/dropdown-menu";
12
+ import {
13
+ Tooltip,
14
+ TooltipContent,
15
+ TooltipTrigger,
16
+ } from "@/components/ui/tooltip";
17
+ import { cn } from "@/lib/utils";
18
+
19
+ type MenuSide = "top" | "right" | "bottom" | "left";
20
+ type MenuAlign = "start" | "center" | "end";
21
+
22
+ export interface ImportMenuProps {
23
+ uploadHref?: string;
24
+ onUpload?: () => void;
25
+ importLoomHref?: string;
26
+ className?: string;
27
+ disabled?: boolean;
28
+ iconOnly?: boolean;
29
+ menuAlign?: MenuAlign;
30
+ menuSide?: MenuSide;
31
+ size?: ButtonProps["size"];
32
+ variant?: ButtonProps["variant"];
33
+ }
34
+
35
+ export function ImportMenu({
36
+ uploadHref,
37
+ onUpload,
38
+ importLoomHref,
39
+ className,
40
+ disabled,
41
+ iconOnly = false,
42
+ menuAlign = "center",
43
+ menuSide,
44
+ size = iconOnly ? "icon" : "default",
45
+ variant = "outline",
46
+ }: ImportMenuProps) {
47
+ const t = useT();
48
+
49
+ if (!uploadHref && !onUpload && !importLoomHref) return null;
50
+
51
+ const trigger = (
52
+ <Button
53
+ type="button"
54
+ variant={variant}
55
+ size={size}
56
+ disabled={disabled}
57
+ aria-label={t("preRecord.import")}
58
+ className={cn(!iconOnly && "gap-2", className)}
59
+ >
60
+ <IconUpload />
61
+ {!iconOnly ? (
62
+ <>
63
+ {t("preRecord.import")}
64
+ <IconChevronDown />
65
+ </>
66
+ ) : null}
67
+ </Button>
68
+ );
69
+
70
+ return (
71
+ <DropdownMenu>
72
+ {iconOnly ? (
73
+ <Tooltip>
74
+ <TooltipTrigger asChild>
75
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
76
+ </TooltipTrigger>
77
+ <TooltipContent side={menuSide ?? "right"}>
78
+ {t("preRecord.import")}
79
+ </TooltipContent>
80
+ </Tooltip>
81
+ ) : (
82
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
83
+ )}
84
+ <DropdownMenuContent align={menuAlign} side={menuSide} className="w-56">
85
+ {uploadHref ? (
86
+ <DropdownMenuItem asChild>
87
+ <Link to={uploadHref}>
88
+ <IconUpload />
89
+ {t("preRecord.uploadVideo")}
90
+ </Link>
91
+ </DropdownMenuItem>
92
+ ) : onUpload ? (
93
+ <DropdownMenuItem onSelect={onUpload}>
94
+ <IconUpload />
95
+ {t("preRecord.uploadVideo")}
96
+ </DropdownMenuItem>
97
+ ) : null}
98
+ {importLoomHref ? (
99
+ <DropdownMenuItem asChild>
100
+ <Link to={importLoomHref}>
101
+ <IconLink />
102
+ {t("preRecord.importLoom")}
103
+ </Link>
104
+ </DropdownMenuItem>
105
+ ) : null}
106
+ </DropdownMenuContent>
107
+ </DropdownMenu>
108
+ );
109
+ }
@@ -8,13 +8,12 @@ import {
8
8
  IconChevronLeft,
9
9
  IconChevronRight,
10
10
  IconPlayerRecord,
11
- IconUpload,
12
- IconLink,
13
11
  } from "@tabler/icons-react";
14
12
  import { useCallback, useEffect, useMemo, useState } from "react";
15
13
  import { NavLink } from "react-router";
16
14
  import { toast } from "sonner";
17
15
 
16
+ import { ImportMenu } from "@/components/import-menu";
18
17
  import { CreateFolderDialog } from "@/components/library/create-folder-dialog";
19
18
  import { ShareRecordingDialog } from "@/components/player/share-dialog";
20
19
  import { Button } from "@/components/ui/button";
@@ -101,22 +100,13 @@ function NewRecordingTile({
101
100
  {t("navigation.newRecording")}
102
101
  </NavLink>
103
102
  </Button>
104
- <div className="flex items-center gap-3">
105
- <NavLink
106
- to={uploadHref}
107
- className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground"
108
- >
109
- <IconUpload className="h-3.5 w-3.5" />
110
- {t("preRecord.uploadVideo")}
111
- </NavLink>
112
- <NavLink
113
- to={importHref}
114
- className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground"
115
- >
116
- <IconLink className="h-3.5 w-3.5" />
117
- {t("preRecord.importLoom")}
118
- </NavLink>
119
- </div>
103
+ <ImportMenu
104
+ uploadHref={uploadHref}
105
+ importLoomHref={importHref}
106
+ size="sm"
107
+ variant="ghost"
108
+ className="h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground"
109
+ />
120
110
  </div>
121
111
  );
122
112
  }
@@ -32,8 +32,6 @@ import {
32
32
  IconShare,
33
33
  IconSettings,
34
34
  IconSearch,
35
- IconUpload,
36
- IconLink,
37
35
  } from "@tabler/icons-react";
38
36
  import { ReactNode, useEffect, useMemo, useState } from "react";
39
37
  import { NavLink, useLocation, useParams } from "react-router";
@@ -43,6 +41,7 @@ import {
43
41
  CaptureInstallButton,
44
42
  CaptureInstallInlineLink,
45
43
  } from "@/components/capture-install-options";
44
+ import { ImportMenu } from "@/components/import-menu";
46
45
  import {
47
46
  AlertDialog,
48
47
  AlertDialogAction,
@@ -400,34 +399,14 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
400
399
  </div>
401
400
 
402
401
  <div className="flex flex-col items-center gap-1 px-2">
403
- <Tooltip>
404
- <TooltipTrigger asChild>
405
- <NavLink
406
- to="/record?autoUpload=1"
407
- aria-label={t("preRecord.uploadVideo")}
408
- className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground"
409
- >
410
- <IconUpload className="h-4 w-4" />
411
- </NavLink>
412
- </TooltipTrigger>
413
- <TooltipContent side="right">
414
- {t("preRecord.uploadVideo")}
415
- </TooltipContent>
416
- </Tooltip>
417
- <Tooltip>
418
- <TooltipTrigger asChild>
419
- <NavLink
420
- to="/import"
421
- aria-label={t("preRecord.importLoom")}
422
- className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground"
423
- >
424
- <IconLink className="h-4 w-4" />
425
- </NavLink>
426
- </TooltipTrigger>
427
- <TooltipContent side="right">
428
- {t("preRecord.importLoom")}
429
- </TooltipContent>
430
- </Tooltip>
402
+ <ImportMenu
403
+ uploadHref="/record?autoUpload=1"
404
+ importLoomHref="/import"
405
+ iconOnly
406
+ variant="ghost"
407
+ menuSide="right"
408
+ menuAlign="start"
409
+ />
431
410
  </div>
432
411
 
433
412
  <nav className="mt-3 flex flex-col items-center gap-1 px-2">
@@ -463,28 +442,13 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
463
442
  {t("navigation.newRecording")}
464
443
  </NavLink>
465
444
  </Button>
466
- <Button
467
- className="mt-1.5 w-full gap-1.5 text-muted-foreground"
445
+ <ImportMenu
446
+ uploadHref="/record?autoUpload=1"
447
+ importLoomHref="/import"
468
448
  size="sm"
469
449
  variant="ghost"
470
- asChild
471
- >
472
- <NavLink to="/record?autoUpload=1">
473
- <IconUpload className="h-4 w-4" />
474
- {t("preRecord.uploadVideo")}
475
- </NavLink>
476
- </Button>
477
- <Button
478
- className="mt-1.5 w-full gap-1.5 text-muted-foreground"
479
- size="sm"
480
- variant="ghost"
481
- asChild
482
- >
483
- <NavLink to="/import">
484
- <IconLink className="h-4 w-4" />
485
- {t("preRecord.importLoom")}
486
- </NavLink>
487
- </Button>
450
+ className="mt-1.5 w-full"
451
+ />
488
452
  </div>
489
453
 
490
454
  <nav className="mt-3 space-y-0.5 px-2">
@@ -7,6 +7,11 @@ import {
7
7
  DropdownMenuItem,
8
8
  DropdownMenuTrigger,
9
9
  } from "@/components/ui/dropdown-menu";
10
+ import {
11
+ Tooltip,
12
+ TooltipContent,
13
+ TooltipTrigger,
14
+ } from "@/components/ui/tooltip";
10
15
 
11
16
  export type SortKey = "recent" | "views" | "oldest";
12
17
 
@@ -24,12 +29,21 @@ interface SortMenuProps {
24
29
  export function SortMenu({ value, onChange }: SortMenuProps) {
25
30
  return (
26
31
  <DropdownMenu>
27
- <DropdownMenuTrigger asChild>
28
- <Button variant="outline" size="sm" className="gap-1.5 h-9">
29
- <IconArrowsSort className="h-3.5 w-3.5" />
30
- <span className="text-xs">{LABELS[value]}</span>
31
- </Button>
32
- </DropdownMenuTrigger>
32
+ <Tooltip>
33
+ <TooltipTrigger asChild>
34
+ <DropdownMenuTrigger asChild>
35
+ <Button
36
+ variant="outline"
37
+ size="icon"
38
+ className="h-9 w-9"
39
+ aria-label={LABELS[value]}
40
+ >
41
+ <IconArrowsSort className="h-3.5 w-3.5" />
42
+ </Button>
43
+ </DropdownMenuTrigger>
44
+ </TooltipTrigger>
45
+ <TooltipContent>{LABELS[value]}</TooltipContent>
46
+ </Tooltip>
33
47
  <DropdownMenuContent align="end" className="w-44">
34
48
  {(Object.keys(LABELS) as SortKey[]).map((key) => (
35
49
  <DropdownMenuItem
@@ -7,16 +7,14 @@ import {
7
7
  IconChevronDown,
8
8
  IconDeviceDesktop,
9
9
  IconDeviceScreen,
10
- IconLink,
11
10
  IconMicrophone,
12
11
  IconPlayerRecord,
13
- IconUpload,
14
12
  IconVideo,
15
13
  } from "@tabler/icons-react";
16
14
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
17
- import { Link } from "react-router";
18
15
 
19
16
  import { CaptureInstallInlineLink } from "@/components/capture-install-options";
17
+ import { ImportMenu } from "@/components/import-menu";
20
18
  import { Button } from "@/components/ui/button";
21
19
  import {
22
20
  Collapsible,
@@ -66,15 +64,14 @@ export interface PreRecordPanelProps {
66
64
  initialDisplaySurface?: DisplaySurface | null;
67
65
  /** Called when the user picks a local video file to upload. */
68
66
  onUpload?: (file: File) => void;
69
- /** When set, shows an "Import Loom" link pointing at the dedicated
70
- * Loom-import page instead of an inline import form. */
67
+ /** When set, includes an "Import Loom" option in the shared import menu. */
71
68
  importLoomHref?: string;
72
69
  onCancel?: () => void;
73
70
  busy?: boolean;
74
71
  cameraSize?: CameraBubbleSize;
75
72
  onCameraSizeChange?: (size: CameraBubbleSize) => void;
76
73
  /** Opens the file picker once on mount, e.g. when arriving from a
77
- * dedicated "Upload video" entry point elsewhere in the app. */
74
+ * dedicated upload entry point elsewhere in the app. */
78
75
  autoOpenUpload?: boolean;
79
76
  }
80
77
 
@@ -924,34 +921,13 @@ export function PreRecordPanel({
924
921
 
925
922
  {(onUpload || importLoomHref) && (
926
923
  <>
927
- <div
928
- className={cn(
929
- "grid gap-2",
930
- onUpload && importLoomHref && "sm:grid-cols-2",
931
- )}
932
- >
933
- {onUpload ? (
934
- <button
935
- type="button"
936
- disabled={busy}
937
- onClick={() => fileInputRef.current?.click()}
938
- className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60"
939
- >
940
- <IconUpload className="h-4 w-4" />
941
- {t("preRecord.uploadVideo")}
942
- </button>
943
- ) : null}
944
-
945
- {importLoomHref ? (
946
- <Link
947
- to={importLoomHref}
948
- className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
949
- >
950
- <IconLink className="h-4 w-4" />
951
- {t("preRecord.importLoom")}
952
- </Link>
953
- ) : null}
954
- </div>
924
+ <ImportMenu
925
+ onUpload={
926
+ onUpload ? () => fileInputRef.current?.click() : undefined
927
+ }
928
+ importLoomHref={importLoomHref}
929
+ disabled={busy}
930
+ />
955
931
 
956
932
  {onUpload ? (
957
933
  <input
@@ -1,5 +1,4 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { IconPlayerRecord } from "@tabler/icons-react";
3
2
  import { NavLink } from "react-router";
4
3
 
5
4
  import { LibraryGrid } from "@/components/library/library-grid";
@@ -38,7 +37,6 @@ export default function LibraryIndexRoute() {
38
37
  asChild
39
38
  >
40
39
  <NavLink to="/record" aria-label={t("navigation.newRecording")}>
41
- <IconPlayerRecord className="h-4 w-4" />
42
40
  <span className="hidden sm:inline">
43
41
  {t("navigation.newRecording")}
44
42
  </span>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-03
4
+ ---
5
+
6
+ Restart during a recording now immediately starts a fresh take instead of cancelling.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-04
4
+ ---
5
+
6
+ Cleaner Library controls with a simpler New recording button and icon-only sort control
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-04
4
+ ---
5
+
6
+ Upload and Loom imports now live under one Import menu