@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
@@ -90,6 +90,7 @@ import {
90
90
  type PendingBrowserRecordingUpload,
91
91
  type RecorderHandle,
92
92
  type RecorderStopResult,
93
+ type RestartHandoff,
93
94
  } from "./lib/recorder";
94
95
  import {
95
96
  copyRecordingShareLink,
@@ -377,6 +378,12 @@ function normalizeCaptureSource(value: string): CaptureSource {
377
378
  return value === "window" ? "window" : "full-screen";
378
379
  }
379
380
 
381
+ function stopRestartHandoff(handoff: RestartHandoff): void {
382
+ [handoff.displayStream, handoff.audioStream].forEach((stream) =>
383
+ stream?.getTracks().forEach((track) => track.stop()),
384
+ );
385
+ }
386
+
380
387
  type FetchInput = Parameters<typeof fetch>[0];
381
388
  type FetchInit = Parameters<typeof fetch>[1];
382
389
 
@@ -1039,6 +1046,12 @@ export function App() {
1039
1046
  refreshHomeScreenMemoryStatus,
1040
1047
  ]);
1041
1048
  const recordShortcutHandlerRef = useRef<() => void>(() => {});
1049
+ const handleStartRecordingRef = useRef<
1050
+ (options?: {
1051
+ ignoreActiveRecorder?: boolean;
1052
+ resumeCapture?: RestartHandoff;
1053
+ }) => Promise<RecorderHandle | null>
1054
+ >(async () => null);
1042
1055
  // Mirrors `bubbleActive` (assigned below once it is computed) so device
1043
1056
  // probes can synchronously tell whether the camera bubble owns the grant.
1044
1057
  const bubbleActiveRef = useRef(false);
@@ -2197,6 +2210,11 @@ export function App() {
2197
2210
  // bubble effect re-acquires even if bubbleActive/cameraId are unchanged
2198
2211
  // (post-stop reopen with a blank "Default Camera" preview).
2199
2212
  const [bubbleSessionEpoch, setBubbleSessionEpoch] = useState(0);
2213
+ // Bumped when a session tears the recording chrome down without leaving the
2214
+ // recording flow, which only a restart does. `toolbarActive` stays true
2215
+ // across that handoff, so without an epoch the toolbar effect never re-runs
2216
+ // and the closed toolbar window is never rebuilt.
2217
+ const [recordingChromeEpoch, setRecordingChromeEpoch] = useState(0);
2200
2218
  const wantsCamera = mode !== "screen" && cameraOn;
2201
2219
  const nativeFullscreenRecordingActive =
2202
2220
  mode !== "camera" && shouldUseNativeFullscreenRecording(source);
@@ -2209,6 +2227,10 @@ export function App() {
2209
2227
  // fresh camera session can recover immediately. Keep that post-stop phase
2210
2228
  // separate so React cleanup does not close the finalizing progress window.
2211
2229
  const recordingStopFinalizingRef = useRef(false);
2230
+ // Held from the restart click until the replacement recorder is up (or has
2231
+ // failed). Stop and cancel are terminal transitions on the recorder a
2232
+ // restart is already tearing down, so they must not run against it.
2233
+ const restartInFlightRef = useRef(false);
2212
2234
  const recordingInFlight = isRecording || recordingFlowActive;
2213
2235
  useLayoutEffect(() => {
2214
2236
  recordingFlowGateRef.current = recordingInFlight;
@@ -2252,7 +2274,7 @@ export function App() {
2252
2274
  }).catch(() => {});
2253
2275
  }
2254
2276
  };
2255
- }, [toolbarActive]);
2277
+ }, [toolbarActive, recordingChromeEpoch]);
2256
2278
 
2257
2279
  useEffect(() => {
2258
2280
  if (!bubbleActive) return;
@@ -2759,7 +2781,9 @@ export function App() {
2759
2781
 
2760
2782
  async function handleStartRecording(options?: {
2761
2783
  ignoreActiveRecorder?: boolean;
2762
- }) {
2784
+ /** Live capture inherited from the take a restart is replacing. */
2785
+ resumeCapture?: RestartHandoff;
2786
+ }): Promise<RecorderHandle | null> {
2763
2787
  if (recorder && !options?.ignoreActiveRecorder) {
2764
2788
  console.warn(
2765
2789
  "[clips-popover] handleStartRecording ignored — recorder already active",
@@ -2767,7 +2791,7 @@ export function App() {
2767
2791
  setRecError(
2768
2792
  "Still finishing the last recording. Wait a moment, then try again.",
2769
2793
  );
2770
- return;
2794
+ return null;
2771
2795
  }
2772
2796
  const bubbleTracks = bubbleStreamRef.current?.getTracks() ?? [];
2773
2797
  const bubbleStreamDead =
@@ -2782,11 +2806,11 @@ export function App() {
2782
2806
  if (localRecordingMode === "off") {
2783
2807
  if (videoStorageStatus === "checking") {
2784
2808
  setRecError("Checking video storage. Try again in a moment.");
2785
- return;
2809
+ return null;
2786
2810
  }
2787
2811
  if (videoStorageStatus === "missing") {
2788
2812
  openVideoStorageSetup();
2789
- return;
2813
+ return null;
2790
2814
  }
2791
2815
  }
2792
2816
  setRecError(null);
@@ -2810,12 +2834,12 @@ export function App() {
2810
2834
  setReadinessOpen(true);
2811
2835
  setRecError(MACOS_SCREEN_PERMISSION_MESSAGE);
2812
2836
  openPrivacySettings("screen");
2813
- return;
2837
+ return null;
2814
2838
  }
2815
2839
  } catch (err) {
2816
2840
  setReadinessOpen(true);
2817
2841
  setRecError(err instanceof Error ? err.message : String(err));
2818
- return;
2842
+ return null;
2819
2843
  }
2820
2844
  }
2821
2845
 
@@ -2901,6 +2925,8 @@ export function App() {
2901
2925
  systemAudioOn,
2902
2926
  localRecordingMode,
2903
2927
  preAcquiredCameraStream,
2928
+ preAcquiredDisplayStream: options?.resumeCapture?.displayStream ?? null,
2929
+ preAcquiredAudioStream: options?.resumeCapture?.audioStream ?? null,
2904
2930
  });
2905
2931
  // macOS: park the popover to its 2×2 pinhole IMMEDIATELY so it
2906
2932
  // doesn't appear in the screen picker window list. The native
@@ -2967,7 +2993,7 @@ export function App() {
2967
2993
 
2968
2994
  if (handle) {
2969
2995
  setRecorder(handle);
2970
- return;
2996
+ return handle;
2971
2997
  }
2972
2998
 
2973
2999
  // Failure path — the recorder never came up. Side-effects (recording
@@ -2988,13 +3014,13 @@ export function App() {
2988
3014
  errName === "AbortError" ||
2989
3015
  /was cancelled|dismissed|region selection cancelled/i.test(message)
2990
3016
  ) {
2991
- return;
3017
+ return null;
2992
3018
  }
2993
3019
  if (
2994
3020
  errName === "NotAllowedError" &&
2995
3021
  !isHardCapturePermissionError(message)
2996
3022
  ) {
2997
- return;
3023
+ return null;
2998
3024
  }
2999
3025
  if (isHardCapturePermissionError(message)) {
3000
3026
  // If an update has finished downloading and is waiting to install, the
@@ -3009,16 +3035,22 @@ export function App() {
3009
3035
  ? MACOS_CAPTURE_PERMISSION_MESSAGE
3010
3036
  : DESKTOP_CAPTURE_PERMISSION_MESSAGE,
3011
3037
  );
3012
- return;
3038
+ return null;
3013
3039
  }
3014
3040
  if (isStorageSetupFailureMessage(message)) {
3015
3041
  setRecError(STORAGE_SETUP_HELP_TEXT);
3016
3042
  openVideoStorageSetup();
3017
- return;
3043
+ return null;
3018
3044
  }
3019
3045
  setRecError(message);
3046
+ return null;
3020
3047
  }
3021
3048
 
3049
+ // The restart listener lives in an effect keyed on `recorder`; calling the
3050
+ // start flow through this ref keeps that dependency list from having to
3051
+ // include a function that is recreated every render.
3052
+ handleStartRecordingRef.current = handleStartRecording;
3053
+
3022
3054
  recordShortcutHandlerRef.current = () => {
3023
3055
  if (recorder) {
3024
3056
  emit("clips:recorder-stop").catch(() => {});
@@ -3124,6 +3156,7 @@ export function App() {
3124
3156
  };
3125
3157
  track(
3126
3158
  listen("clips:recorder-stop", async () => {
3159
+ if (restartInFlightRef.current) return;
3127
3160
  // Detach the React Start/bubble gate immediately. The recorder keeps
3128
3161
  // Rust `is_recording_active` and the finalizing overlay guarded until
3129
3162
  // its durable backup/finalize boundary; keeping this React handle set
@@ -3185,6 +3218,7 @@ export function App() {
3185
3218
  );
3186
3219
  track(
3187
3220
  listen("clips:recorder-cancel", async () => {
3221
+ if (restartInFlightRef.current) return;
3188
3222
  try {
3189
3223
  await recorder.cancel();
3190
3224
  } finally {
@@ -3206,26 +3240,43 @@ export function App() {
3206
3240
  );
3207
3241
  track(
3208
3242
  listen("clips:recorder-restart", async () => {
3243
+ if (recordingStopFinalizingRef.current) return;
3244
+ // Latched synchronously: a restart is a terminal transition on this
3245
+ // recorder, and stop/cancel must not act on it while the replacement
3246
+ // is being brought up.
3247
+ if (restartInFlightRef.current) return;
3248
+ restartInFlightRef.current = true;
3249
+ let handoff: RestartHandoff | null = null;
3209
3250
  try {
3210
- await recorder.cancel();
3251
+ handoff = await recorder.discardForRestart();
3252
+ if (cancelled) return;
3253
+ // The recording flow stays latched across the restart. Releasing
3254
+ // `clipsForceAlive` / `recordingFlowGateRef` / `recordingFlowActive`
3255
+ // / `set_recording_state` the way cancel does would let the popover's
3256
+ // blur auto-hide fire and flicker the pill between the two takes. The
3257
+ // camera also stays owned by the popover, so the bubble session epoch
3258
+ // is deliberately not bumped and the stream is re-handed unchanged.
3259
+ //
3260
+ // The discard did close the countdown and toolbar windows. The
3261
+ // recorder rebuilds the countdown on every start, but the toolbar is
3262
+ // owned by an effect keyed on the flow latches we just kept held — so
3263
+ // it has to be told the chrome is gone.
3264
+ setRecordingChromeEpoch((epoch) => epoch + 1);
3265
+ setRecorder(null);
3266
+ const restarted = await handleStartRecordingRef.current({
3267
+ ignoreActiveRecorder: true,
3268
+ resumeCapture: handoff,
3269
+ });
3270
+ // The new session owns the handed-off capture only once it exists.
3271
+ if (restarted) handoff = null;
3272
+ } catch (err) {
3273
+ console.error("[clips-popover] restart failed:", err);
3274
+ setRecError(err instanceof Error ? err.message : String(err));
3211
3275
  } finally {
3212
- if (!cancelled) {
3213
- (
3214
- window as unknown as { clipsForceAlive?: boolean }
3215
- ).clipsForceAlive = false;
3216
- bubbleStreamTransferredToRecorder.current = false;
3217
- bubbleStreamRef.current = null;
3218
- recordingFlowGateRef.current = false;
3219
- setRecorder(null);
3220
- setRecordingFlowActive(false);
3221
- setBubbleSessionEpoch((epoch) => epoch + 1);
3222
- invoke("set_recording_state", { active: false }).catch(() => {});
3223
- // Starting a new browser capture must come from a fresh click in
3224
- // this webview. The toolbar click arrives here through async Tauri
3225
- // IPC, so reopen the popover and let the next Start click provide
3226
- // the required user activation.
3227
- invoke("show_popover").catch(() => {});
3228
- }
3276
+ // Anything still held here belongs to a retake that never came up.
3277
+ // Leaving it would keep the screen captured with nothing recording.
3278
+ if (handoff) stopRestartHandoff(handoff);
3279
+ restartInFlightRef.current = false;
3229
3280
  }
3230
3281
  }),
3231
3282
  );