@agent-native/core 0.145.1 → 0.145.3

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 (42) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/brain/server/lib/capture-sanitization.ts +6 -2
  3. package/corpus/templates/clips/changelog/2026-08-07-full-screen-recordings-on-a-multi-monitor-mac-now-let-you-pi.md +6 -0
  4. package/corpus/templates/clips/chrome-extension/src/styles.css +13 -4
  5. package/corpus/templates/clips/desktop/src/app.tsx +38 -1
  6. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +123 -104
  7. package/corpus/templates/clips/desktop/src/lib/recorder.ts +165 -2
  8. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  9. package/corpus/templates/clips/desktop/src/overlays/monitor-picker.tsx +70 -0
  10. package/corpus/templates/clips/desktop/src/styles.css +70 -11
  11. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +163 -1
  13. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -1
  14. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +141 -22
  15. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +27 -1
  16. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +12 -1
  17. package/dist/agent/engine/index.d.ts +1 -1
  18. package/dist/agent/engine/index.js +1 -1
  19. package/dist/agent/engine/registry.d.ts +15 -0
  20. package/dist/agent/engine/registry.js +39 -10
  21. package/dist/agent/production-agent.d.ts +40 -1
  22. package/dist/agent/production-agent.js +81 -40
  23. package/dist/collab/awareness.d.ts +2 -2
  24. package/dist/collab/struct-routes.d.ts +1 -1
  25. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  26. package/dist/integrations/webhook-handler.d.ts +3 -2
  27. package/dist/integrations/webhook-handler.js +8 -38
  28. package/dist/observability/routes.d.ts +3 -3
  29. package/dist/server/agent-chat-plugin.js +67 -33
  30. package/dist/server/complete-text.js +14 -7
  31. package/dist/server/index.d.ts +1 -1
  32. package/dist/server/index.js +1 -1
  33. package/dist/server/poll.d.ts +11 -0
  34. package/dist/server/poll.js +7 -4
  35. package/dist/server/realtime-token.d.ts +1 -1
  36. package/dist/server/realtime-token.js +8 -0
  37. package/dist/server/request-context.d.ts +7 -1
  38. package/dist/server/short-lived-token.d.ts +8 -0
  39. package/dist/server/short-lived-token.js +12 -0
  40. package/dist/server/transcribe-voice.d.ts +1 -1
  41. package/docs/content/environment-variables.mdx +6 -0
  42. package/package.json +3 -3
@@ -0,0 +1,70 @@
1
+ import { emit } from "@tauri-apps/api/event";
2
+ import { useEffect } from "react";
3
+
4
+ interface MonitorPickerParams {
5
+ index: number;
6
+ total: number;
7
+ displayId: number | null;
8
+ }
9
+
10
+ function parseParams(search: string): MonitorPickerParams {
11
+ const params = new URLSearchParams(search);
12
+ const index = Number.parseInt(params.get("index") ?? "", 10);
13
+ const total = Number.parseInt(params.get("total") ?? "", 10);
14
+ const displayIdRaw = Number.parseInt(params.get("displayId") ?? "", 10);
15
+ return {
16
+ index: Number.isFinite(index) && index > 0 ? index : 1,
17
+ total: Number.isFinite(total) && total > 0 ? total : 1,
18
+ displayId:
19
+ Number.isFinite(displayIdRaw) && displayIdRaw > 0 ? displayIdRaw : null,
20
+ };
21
+ }
22
+
23
+ /**
24
+ * One of these opens centered on every connected monitor before a
25
+ * full-screen native recording starts with more than one monitor attached
26
+ * (see `show_monitor_picker` on the Rust side). The display id this specific
27
+ * monitor resolves to is baked into this window's own URL rather than looked
28
+ * up again here, so a click can report it without a round trip. The recorder
29
+ * driver — not this component — closes every sibling window once it hears
30
+ * either event, so a click never closes just itself.
31
+ */
32
+ export function MonitorPicker() {
33
+ const { index, total, displayId } =
34
+ typeof window === "undefined"
35
+ ? { index: 1, total: 1, displayId: null }
36
+ : parseParams(window.location.search);
37
+
38
+ useEffect(() => {
39
+ const onKeyDown = (event: KeyboardEvent) => {
40
+ if (event.key !== "Escape") return;
41
+ event.preventDefault();
42
+ emit("clips:monitor-picker-cancelled").catch(() => {});
43
+ };
44
+ window.addEventListener("keydown", onKeyDown);
45
+ return () => window.removeEventListener("keydown", onKeyDown);
46
+ }, []);
47
+
48
+ function select() {
49
+ emit("clips:monitor-picker-selected", { displayId }).catch(() => {});
50
+ }
51
+
52
+ return (
53
+ <div className="monitor-picker-root">
54
+ <button
55
+ type="button"
56
+ className="monitor-picker-card"
57
+ onClick={select}
58
+ autoFocus
59
+ >
60
+ <span className="monitor-picker-title">Screen {index}</span>
61
+ <span className="monitor-picker-subtitle">
62
+ Click to record this display
63
+ </span>
64
+ <span className="monitor-picker-hint">
65
+ {total} displays connected · Esc to cancel
66
+ </span>
67
+ </button>
68
+ </div>
69
+ );
70
+ }
@@ -606,7 +606,7 @@ body[data-clips-route="recording-pill"] #root {
606
606
  }
607
607
 
608
608
  /* Popover menu rendered below a device row. */
609
- .media-device-row {
609
+ .media-device-picker {
610
610
  position: relative;
611
611
  }
612
612
 
@@ -665,19 +665,14 @@ body[data-clips-route="recording-pill"] #root {
665
665
  border-radius: 4px;
666
666
  }
667
667
 
668
- .row-menu-toggle {
669
- display: flex;
670
- align-items: center;
671
- justify-content: space-between;
672
- gap: 8px;
673
- margin-top: 4px;
674
- padding: 10px 10px 8px 34px;
675
- border-top: 1px solid var(--border, rgba(255, 255, 255, 0.08));
668
+ .system-audio-option {
669
+ margin-top: 8px;
676
670
  }
677
671
 
678
- .row-menu-toggle-label {
679
- font-size: 13px;
672
+ .system-audio-option-label {
680
673
  color: var(--fg);
674
+ font-size: 13px;
675
+ font-weight: 500;
681
676
  }
682
677
 
683
678
  .row-menu-check {
@@ -6229,3 +6224,67 @@ body[data-clips-route="recording-pill"] #root {
6229
6224
  transform: rotate(360deg);
6230
6225
  }
6231
6226
  }
6227
+
6228
+ /*
6229
+ * One of these windows opens centered on every connected monitor when a
6230
+ * full-screen native recording is about to start with more than one monitor
6231
+ * attached (see `show_monitor_picker`). The window itself is already sized
6232
+ * and positioned by Rust, so the card just fills it.
6233
+ */
6234
+ .monitor-picker-root {
6235
+ position: fixed;
6236
+ inset: 0;
6237
+ display: flex;
6238
+ align-items: center;
6239
+ justify-content: center;
6240
+ padding: 18px;
6241
+ background: transparent;
6242
+ }
6243
+
6244
+ .monitor-picker-card {
6245
+ display: flex;
6246
+ flex-direction: column;
6247
+ align-items: center;
6248
+ justify-content: center;
6249
+ gap: 6px;
6250
+ width: 100%;
6251
+ height: 100%;
6252
+ padding: 16px;
6253
+ border-radius: 20px;
6254
+ background: rgba(20, 22, 28, 0.94);
6255
+ backdrop-filter: blur(20px);
6256
+ -webkit-backdrop-filter: blur(20px);
6257
+ border: 1px solid rgba(255, 255, 255, 0.12);
6258
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
6259
+ color: white;
6260
+ text-align: center;
6261
+ cursor: pointer;
6262
+ transition:
6263
+ background 120ms,
6264
+ border-color 120ms;
6265
+ }
6266
+
6267
+ .monitor-picker-card:hover,
6268
+ .monitor-picker-card:focus-visible {
6269
+ background: rgba(32, 35, 43, 0.96);
6270
+ border-color: rgba(255, 255, 255, 0.24);
6271
+ outline: none;
6272
+ }
6273
+
6274
+ .monitor-picker-title {
6275
+ font-size: 17px;
6276
+ font-weight: 700;
6277
+ }
6278
+
6279
+ .monitor-picker-subtitle {
6280
+ font-size: 13px;
6281
+ font-weight: 500;
6282
+ color: rgba(255, 255, 255, 0.72);
6283
+ }
6284
+
6285
+ .monitor-picker-hint {
6286
+ margin-top: 4px;
6287
+ font-size: 11px;
6288
+ font-weight: 500;
6289
+ color: rgba(255, 255, 255, 0.45);
6290
+ }
@@ -16,7 +16,8 @@
16
16
  "recording-pill",
17
17
  "region-guides",
18
18
  "region-guide-editor",
19
- "region-record-border"
19
+ "region-record-border",
20
+ "monitor-picker-*"
20
21
  ],
21
22
  "permissions": [
22
23
  "core:default",
@@ -47,6 +47,7 @@ const FLOW_BAR_LABEL: &str = "flow-bar";
47
47
  const REGION_GUIDES_LABEL: &str = "region-guides";
48
48
  const REGION_GUIDE_EDITOR_LABEL: &str = "region-guide-editor";
49
49
  const REGION_RECORD_BORDER_LABEL: &str = "region-record-border";
50
+ const MONITOR_PICKER_LABEL_PREFIX: &str = "monitor-picker-";
50
51
  const ONBOARDING_LABEL: &str = "onboarding";
51
52
  const OVERLAY_LABELS: &[&str] = &[
52
53
  COUNTDOWN_LABEL,
@@ -859,6 +860,153 @@ pub async fn show_region_capture_selector(app: AppHandle) -> Result<(), String>
859
860
  Ok(())
860
861
  }
861
862
 
863
+ fn close_monitor_picker_windows(app: &AppHandle) {
864
+ for (label, window) in app.webview_windows() {
865
+ if label.starts_with(MONITOR_PICKER_LABEL_PREFIX) {
866
+ let _ = window.close();
867
+ }
868
+ }
869
+ }
870
+
871
+ /// Resolve the CGDirectDisplayID a monitor's own center point maps to, using
872
+ /// that monitor's own scale factor (mixed-DPI multi-monitor setups can differ
873
+ /// per screen, unlike the single popover-scale shortcut `tray_display_id`
874
+ /// uses elsewhere).
875
+ #[cfg(target_os = "macos")]
876
+ fn display_id_for_monitor_rect(x: i32, y: i32, width: u32, height: u32, scale: f64) -> Option<u32> {
877
+ let cx_phys = x as f64 + width as f64 / 2.0;
878
+ let cy_phys = y as f64 + height as f64 / 2.0;
879
+ crate::native_screen::cg_display_id_at_physical_point(cx_phys, cy_phys, scale)
880
+ }
881
+
882
+ /// One small "record this screen" popup centered on every connected monitor,
883
+ /// shown before a full-screen native recording starts when more than one
884
+ /// monitor is connected. The React view in each window emits
885
+ /// `clips:monitor-picker-selected` (with the display id baked into its own
886
+ /// URL) or `clips:monitor-picker-cancelled`; the recorder driver listens for
887
+ /// either and calls `close_monitor_picker` to tear all of them down. Returns
888
+ /// `false` without opening anything when there's only one monitor, so the
889
+ /// caller can skip straight to its existing single-monitor start path.
890
+ #[tauri::command]
891
+ pub async fn show_monitor_picker(app: AppHandle) -> Result<bool, String> {
892
+ #[cfg(not(target_os = "macos"))]
893
+ {
894
+ let _ = app;
895
+ Ok(false)
896
+ }
897
+ #[cfg(target_os = "macos")]
898
+ {
899
+ close_monitor_picker_windows(&app);
900
+ // Every fresh pick starts clean — a leftover pick from a previous
901
+ // recording must never apply here, including the single-monitor
902
+ // early-return below.
903
+ crate::state::SelectedRecordingDisplay::set(&app, None);
904
+ let monitors = app
905
+ .get_webview_window("popover")
906
+ .and_then(|w| w.available_monitors().ok())
907
+ .unwrap_or_default();
908
+ if monitors.len() <= 1 {
909
+ return Ok(false);
910
+ }
911
+ let total = monitors.len();
912
+ // Only the LAST window gets the full activate/makeKey/focus dance —
913
+ // `present_interactive_window` steals key-window status from
914
+ // whichever window had it, so calling it once per monitor just
915
+ // re-steals focus from the previous iteration's window for no
916
+ // lasting effect. A plain `show()` is enough for the rest.
917
+ let mut last_window: Option<WebviewWindow> = None;
918
+ for (index, monitor) in monitors.iter().enumerate() {
919
+ let pos = monitor.position();
920
+ let size = monitor.size();
921
+ let scale = monitor.scale_factor().max(1.0);
922
+ // Fail closed: a `0` placeholder here would round-trip through
923
+ // the URL and back as a `null` pick, which `set_recording_display_override`
924
+ // accepts as "no override" — clicking this exact card would then
925
+ // silently record whatever the tray-anchor heuristic picks
926
+ // instead of the screen the user is looking at right now.
927
+ let Some(display_id) =
928
+ display_id_for_monitor_rect(pos.x, pos.y, size.width, size.height, scale)
929
+ else {
930
+ eprintln!(
931
+ "[clips-tray] monitor picker: could not resolve a display id for monitor {index}"
932
+ );
933
+ close_monitor_picker_windows(&app);
934
+ return Err("Could not identify one of the connected displays.".to_string());
935
+ };
936
+ let gutter = overlay_shadow_gutter_physical(&app);
937
+ let content_w: u32 = (240.0 * scale).round() as u32;
938
+ let content_h: u32 = (150.0 * scale).round() as u32;
939
+ let w = content_w + gutter * 2;
940
+ let h = content_h + gutter * 2;
941
+ let x = pos.x + (size.width as i32 - w as i32) / 2;
942
+ let y = pos.y + (size.height as i32 - h as i32) / 2;
943
+ let label = format!("{MONITOR_PICKER_LABEL_PREFIX}{index}");
944
+ let url = WebviewUrl::App(
945
+ format!(
946
+ "index.html?index={}&total={}&displayId={}#monitor-picker",
947
+ index + 1,
948
+ total,
949
+ display_id
950
+ )
951
+ .into(),
952
+ );
953
+ let win = match WebviewWindowBuilder::new(&app, &label, url)
954
+ .title("Choose a screen to record")
955
+ .decorations(false)
956
+ .transparent(true)
957
+ .always_on_top(true)
958
+ .skip_taskbar(true)
959
+ .resizable(false)
960
+ .shadow(false)
961
+ .visible(false)
962
+ .focused(true)
963
+ .accept_first_mouse(true)
964
+ .build()
965
+ {
966
+ Ok(win) => win,
967
+ Err(e) => {
968
+ eprintln!("[clips-tray] monitor picker build failed: {}", e);
969
+ // Earlier iterations already built and showed windows for
970
+ // other monitors — without this they'd stay open and
971
+ // always-on-top indefinitely since nothing else tears
972
+ // them down after this function errors out.
973
+ close_monitor_picker_windows(&app);
974
+ return Err(e.to_string());
975
+ }
976
+ };
977
+ let _ = win.set_size(tauri::Size::Physical(PhysicalSize::new(w, h)));
978
+ let _ = win.set_position(PhysicalPosition::new(x, y));
979
+ let _ = win.set_ignore_cursor_events(false);
980
+ set_capture_excluded_always(&win);
981
+ configure_overlay_behavior(&win);
982
+ let _ = win.show();
983
+ last_window = Some(win);
984
+ }
985
+ if let Some(win) = last_window {
986
+ present_interactive_window(&win);
987
+ }
988
+ Ok(true)
989
+ }
990
+ }
991
+
992
+ #[tauri::command]
993
+ pub async fn close_monitor_picker(app: AppHandle) -> Result<(), String> {
994
+ close_monitor_picker_windows(&app);
995
+ Ok(())
996
+ }
997
+
998
+ /// Stash the user's monitor-picker pick for `tray_display_id` to consume the
999
+ /// next time it resolves a target display — i.e. for the native full-screen
1000
+ /// recording about to start.
1001
+ #[tauri::command]
1002
+ pub async fn set_recording_display_override(
1003
+ app: AppHandle,
1004
+ display_id: Option<u32>,
1005
+ ) -> Result<(), String> {
1006
+ crate::state::SelectedRecordingDisplay::set(&app, display_id);
1007
+ Ok(())
1008
+ }
1009
+
862
1010
  /// Vertical recording pill anchored to the left edge. Stop + timer + pause,
863
1011
  /// with hover-revealed restart/cancel controls matching Loom's left-rail
864
1012
  /// placement. Draggable, always on top.
@@ -1066,6 +1214,7 @@ pub async fn hide_overlays(
1066
1214
  preserve_finalizing: Option<bool>,
1067
1215
  ) -> Result<(), String> {
1068
1216
  stop_countdown_control_tracking();
1217
+ close_monitor_picker_windows(&app);
1069
1218
  for label in overlay_labels_to_hide(preserve_finalizing.unwrap_or(false)) {
1070
1219
  if let Some(w) = app.get_webview_window(label) {
1071
1220
  let _ = w.close();
@@ -1086,7 +1235,10 @@ pub async fn hide_overlays(
1086
1235
  /// by the popover's session effect (show on popover-open, hide on
1087
1236
  /// popover-close).
1088
1237
  #[tauri::command]
1089
- pub async fn hide_recording_chrome(app: AppHandle) -> Result<(), String> {
1238
+ pub async fn hide_recording_chrome(
1239
+ app: AppHandle,
1240
+ preserve_display_override: Option<bool>,
1241
+ ) -> Result<(), String> {
1090
1242
  stop_countdown_control_tracking();
1091
1243
  // The countdown + toolbar always tear down on recording stop. The region
1092
1244
  // guides only tear down when they aren't pinned on-screen via the always-on
@@ -1104,6 +1256,16 @@ pub async fn hide_recording_chrome(app: AppHandle) -> Result<(), String> {
1104
1256
  let _ = w.close();
1105
1257
  }
1106
1258
  }
1259
+ // A recording that used the multi-monitor picker is over — clear its
1260
+ // display override so the NEXT recording (which might skip the picker
1261
+ // entirely on a single monitor) doesn't inherit a stale target screen.
1262
+ // A native restart is the one exception: it discards this take and
1263
+ // immediately starts a replacement on the SAME screen without re-running
1264
+ // the picker (see `pickFullscreenRecordingDisplay`'s skip condition in
1265
+ // recorder.ts), so the caller passes `preserve_display_override: true`.
1266
+ if !preserve_display_override.unwrap_or(false) {
1267
+ crate::state::SelectedRecordingDisplay::set(&app, None);
1268
+ }
1107
1269
  // If meeting or voice flows showed a recording pill, auto-hide it after
1108
1270
  // recording stops. Bail early if a new recording came up in the meantime.
1109
1271
  let app_for_pill = app.clone();
@@ -46,7 +46,8 @@ use tauri::{Emitter, Manager};
46
46
  use clips::{position_popover, toggle_popover};
47
47
  use state::{
48
48
  ActiveMeetingId, DictationActive, DictationEnabled, LastTranscript, MeetingActive,
49
- PopoverShownAt, RecordingActive, TrayAnchor, TrayMeetings, VoiceTargetBundle, VoiceWakePopover,
49
+ PopoverShownAt, RecordingActive, SelectedRecordingDisplay, TrayAnchor, TrayMeetings,
50
+ VoiceTargetBundle, VoiceWakePopover,
50
51
  };
51
52
  use util::{
52
53
  configure_overlay_behavior, is_recording_active, present_interactive_window,
@@ -98,6 +99,9 @@ pub fn run() {
98
99
  clips::hide_region_record_border,
99
100
  clips::show_region_guide_editor,
100
101
  clips::show_region_capture_selector,
102
+ clips::show_monitor_picker,
103
+ clips::close_monitor_picker,
104
+ clips::set_recording_display_override,
101
105
  clips::close_bubble,
102
106
  clips::show_popover,
103
107
  clips::park_popover_offscreen,
@@ -270,6 +274,7 @@ pub fn run() {
270
274
  .manage(VoiceWakePopover::default())
271
275
  .manage(VoiceTargetBundle::default())
272
276
  .manage(LastTranscript::default())
277
+ .manage(SelectedRecordingDisplay::default())
273
278
  .manage(native_screen::NativeFullscreenRecordingState::default())
274
279
  .manage(screen_memory::ScreenMemoryState::default())
275
280
  .manage(capture_graph::CaptureGraphState::default())
@@ -270,6 +270,14 @@ mod native_upload_mode_tests {
270
270
  #[derive(Default)]
271
271
  pub struct NativeFullscreenRecordingState {
272
272
  inner: Mutex<Option<NativeFullscreenSession>>,
273
+ /// Bumped by `native_fullscreen_recording_cancel`. The warm task runs on
274
+ /// a blocking-pool thread and can still be mid-setup when a cancel
275
+ /// arrives — at that point `inner` is still empty, so cancel's own
276
+ /// `take()` finds nothing to discard. The warm task re-checks this
277
+ /// generation right after it installs its session; if it moved on, a
278
+ /// cancel arrived while it was working and it must undo the install
279
+ /// instead of leaving an orphaned, still-capturing stream behind.
280
+ warm_generation: AtomicU64,
273
281
  }
274
282
 
275
283
  #[derive(Clone, Copy, Debug, Deserialize)]
@@ -1273,6 +1281,7 @@ fn start_native_session_locked(
1273
1281
  mic_device_label: Option<String>,
1274
1282
  capture_region: Option<NativeCaptureRegion>,
1275
1283
  defer_recording_output: bool,
1284
+ expected_generation: Option<u64>,
1276
1285
  ) -> Result<NativeFullscreenStartInfo, String> {
1277
1286
  let safe_id = sanitize_recording_id(recording_id);
1278
1287
  reset_native_upload_completion_state();
@@ -1282,7 +1291,7 @@ fn start_native_session_locked(
1282
1291
  || mic_device_label
1283
1292
  .as_deref()
1284
1293
  .is_some_and(|v| !v.trim().is_empty());
1285
- let session = match start_screencapturekit_recording(
1294
+ let mut session = match start_screencapturekit_recording(
1286
1295
  app,
1287
1296
  &safe_id,
1288
1297
  include_audio,
@@ -1329,17 +1338,21 @@ fn start_native_session_locked(
1329
1338
 
1330
1339
  let previous = {
1331
1340
  let mut guard = state.inner.lock().map_err(|e| e.to_string())?;
1332
- guard.take()
1341
+ if let Some(expected) = expected_generation {
1342
+ if state.warm_generation.load(Ordering::SeqCst) != expected {
1343
+ drop(guard);
1344
+ discard_session(&mut session);
1345
+ return Err("Recording cancelled while warming up.".to_string());
1346
+ }
1347
+ }
1348
+ let previous = guard.take();
1349
+ *guard = Some(session);
1350
+ previous
1333
1351
  };
1334
1352
  if let Some(mut previous) = previous {
1335
1353
  discard_session(&mut previous);
1336
1354
  }
1337
1355
 
1338
- {
1339
- let mut guard = state.inner.lock().map_err(|e| e.to_string())?;
1340
- *guard = Some(session);
1341
- }
1342
-
1343
1356
  Ok(NativeFullscreenStartInfo {
1344
1357
  recording_id: recording_id.to_string(),
1345
1358
  width,
@@ -1358,7 +1371,6 @@ fn start_native_session_locked(
1358
1371
  #[tauri::command]
1359
1372
  pub async fn native_fullscreen_recording_warm(
1360
1373
  app: AppHandle,
1361
- state: State<'_, NativeFullscreenRecordingState>,
1362
1374
  recording_id: String,
1363
1375
  include_audio: bool,
1364
1376
  capture_system_audio: bool,
@@ -1370,7 +1382,6 @@ pub async fn native_fullscreen_recording_warm(
1370
1382
  {
1371
1383
  let _ = (
1372
1384
  app,
1373
- state,
1374
1385
  recording_id,
1375
1386
  include_audio,
1376
1387
  capture_system_audio,
@@ -1383,26 +1394,55 @@ pub async fn native_fullscreen_recording_warm(
1383
1394
 
1384
1395
  #[cfg(target_os = "macos")]
1385
1396
  {
1386
- // Only the mic introduces a warm-up gap; with mic off there's nothing
1387
- // to pre-warm, so `begin` just starts normally. SCK failures are
1388
- // swallowed too — `begin` detects the absent warm session and falls
1389
- // back to an immediate start, so warming is always best-effort.
1390
- if include_audio {
1391
- if let Err(err) = start_native_session_locked(
1392
- &app,
1397
+ // Warm regardless of mic: the real cost here is SCShareableContent
1398
+ // lookup + SCStream/output construction + `start_capture`, not just
1399
+ // the microphone. Gating this on `include_audio` left every mic-off
1400
+ // recording to do that full setup synchronously inside `begin` —
1401
+ // i.e. after the countdown — which is exactly the multi-second gap
1402
+ // between "countdown hits zero" and "capture actually starts" that
1403
+ // mic-on recordings never had. SCK failures here are swallowed —
1404
+ // `begin` detects the absent warm session and falls back to an
1405
+ // immediate start, so warming is always best-effort.
1406
+ //
1407
+ // `SCShareableContent::get()` blocks its calling thread on a real
1408
+ // condvar wait (not polling) that this file's own comments elsewhere
1409
+ // clock at multi-second — that's the whole reason this exists instead
1410
+ // of running at `begin`. Run it via `spawn_blocking` so that wait
1411
+ // parks a blocking-pool thread instead of a tokio worker, which the
1412
+ // countdown's `Promise.all` overlap is also relying on to stay
1413
+ // responsive.
1414
+ let app_for_warm = app.clone();
1415
+ let recording_id_for_warm = recording_id.clone();
1416
+ let generation_before_warm = app
1417
+ .state::<NativeFullscreenRecordingState>()
1418
+ .warm_generation
1419
+ .load(Ordering::SeqCst);
1420
+ let warm_result = tauri::async_runtime::spawn_blocking(move || {
1421
+ let state = app_for_warm.state::<NativeFullscreenRecordingState>();
1422
+ start_native_session_locked(
1423
+ &app_for_warm,
1393
1424
  &state,
1394
- &recording_id,
1425
+ &recording_id_for_warm,
1395
1426
  include_audio,
1396
1427
  capture_system_audio,
1397
1428
  mic_device_id,
1398
1429
  mic_device_label,
1399
1430
  capture_region,
1400
1431
  true,
1401
- ) {
1432
+ Some(generation_before_warm),
1433
+ )
1434
+ })
1435
+ .await;
1436
+ match warm_result {
1437
+ Ok(Ok(_)) => {}
1438
+ Ok(Err(err)) => {
1402
1439
  eprintln!(
1403
- "[clips-tray] microphone pre-warm unavailable; will start normally at begin: {err}"
1440
+ "[clips-tray] recording pre-warm unavailable; will start normally at begin: {err}"
1404
1441
  );
1405
1442
  }
1443
+ Err(join_err) => {
1444
+ eprintln!("[clips-tray] recording pre-warm task panicked: {join_err}");
1445
+ }
1406
1446
  }
1407
1447
  Ok(())
1408
1448
  }
@@ -1480,6 +1520,15 @@ pub async fn native_fullscreen_recording_begin(
1480
1520
  .unwrap_or(false)
1481
1521
  };
1482
1522
  if !is_warmed {
1523
+ // A warm task can still be mid-`SCShareableContent` lookup on a
1524
+ // blocking-pool thread right now — that's exactly why nothing is
1525
+ // installed yet. Bump the generation before building our own
1526
+ // session below: without this, when that warm task eventually
1527
+ // finishes, it sees no cancel occurred, assumes its generation is
1528
+ // still current, and silently replaces the live session we're
1529
+ // about to install with its own (never actually attached)
1530
+ // deferred-output one.
1531
+ state.warm_generation.fetch_add(1, Ordering::SeqCst);
1483
1532
  let info = start_native_session_locked(
1484
1533
  &app,
1485
1534
  &state,
@@ -1490,6 +1539,7 @@ pub async fn native_fullscreen_recording_begin(
1490
1539
  mic_device_label,
1491
1540
  capture_region,
1492
1541
  false,
1542
+ None,
1493
1543
  )?;
1494
1544
  {
1495
1545
  let mut guard = state.inner.lock().map_err(|e| e.to_string())?;
@@ -2094,8 +2144,15 @@ pub(crate) fn kill_active_screencapture_child(state: &NativeFullscreenRecordingS
2094
2144
 
2095
2145
  #[tauri::command]
2096
2146
  pub async fn native_fullscreen_recording_cancel(
2147
+ app: AppHandle,
2097
2148
  state: State<'_, NativeFullscreenRecordingState>,
2149
+ preserve_display_override: Option<bool>,
2098
2150
  ) -> Result<(), String> {
2151
+ // Bump BEFORE taking the session: a warm task on a blocking-pool thread
2152
+ // may still be mid-setup right now, with nothing installed yet for this
2153
+ // `take()` to find. Bumping first guarantees that when it later checks
2154
+ // the generation after installing its session, it sees this cancel.
2155
+ state.warm_generation.fetch_add(1, Ordering::SeqCst);
2099
2156
  let session = {
2100
2157
  let mut guard = state.inner.lock().map_err(|e| e.to_string())?;
2101
2158
  guard.take()
@@ -2103,6 +2160,14 @@ pub async fn native_fullscreen_recording_cancel(
2103
2160
  if let Some(mut session) = session {
2104
2161
  discard_session(&mut session);
2105
2162
  }
2163
+ // An aborted start (countdown/warm cancelled before `begin`) never reaches
2164
+ // `hide_recording_chrome`, so the picker's monitor override must also be
2165
+ // cleared here or it would wrongly apply to the next recording attempt.
2166
+ // A native restart is the exception — see `hide_recording_chrome`'s
2167
+ // matching `preserve_display_override` doc comment.
2168
+ if !preserve_display_override.unwrap_or(false) {
2169
+ crate::state::SelectedRecordingDisplay::set(&app, None);
2170
+ }
2106
2171
  Ok(())
2107
2172
  }
2108
2173
 
@@ -3895,7 +3960,21 @@ pub(crate) fn clear_shared_clip_recording(app: &AppHandle, recording_id: &str, p
3895
3960
  let _ = app.emit("clips:pending-uploads-changed", ());
3896
3961
  }
3897
3962
 
3963
+ /// Convert a physical-pixel point to the logical-point coordinate space
3964
+ /// `CGDisplay::displays_with_point` requires (dividing by the monitor's own
3965
+ /// scale factor) and resolve which display owns it. Shared by every place in
3966
+ /// this file that maps a screen point to a `CGDirectDisplayID`.
3898
3967
  #[cfg(target_os = "macos")]
3968
+ pub(crate) fn cg_display_id_at_physical_point(
3969
+ cx_phys: f64,
3970
+ cy_phys: f64,
3971
+ scale: f64,
3972
+ ) -> Option<u32> {
3973
+ let point = CGPoint::new(cx_phys / scale, cy_phys / scale);
3974
+ let (ids, _) = CGDisplay::displays_with_point(point, 4).ok()?;
3975
+ ids.into_iter().next()
3976
+ }
3977
+
3899
3978
  /// Return the `CGDirectDisplayID` of the display containing the centre of
3900
3979
  /// the last-clicked tray icon. Uses the Tauri monitor list to locate the
3901
3980
  /// right monitor and converts from physical pixels to the logical-point
@@ -3904,6 +3983,12 @@ pub(crate) fn clear_shared_clip_recording(app: &AppHandle, recording_id: &str, p
3904
3983
  /// fails — callers fall back to the first available display.
3905
3984
  #[cfg(target_os = "macos")]
3906
3985
  pub(crate) fn tray_display_id(app: &AppHandle) -> Option<u32> {
3986
+ // A multi-monitor picker pick always wins over the tray-anchor heuristic
3987
+ // below — see `SelectedRecordingDisplay`'s doc comment for its lifecycle.
3988
+ if let Some(id) = crate::state::SelectedRecordingDisplay::get(app) {
3989
+ return Some(id);
3990
+ }
3991
+
3907
3992
  let tray_rect = app
3908
3993
  .try_state::<crate::state::TrayAnchor>()
3909
3994
  .and_then(|a| a.0.lock().ok().and_then(|g| *g))?;
@@ -3946,9 +4031,43 @@ pub(crate) fn tray_display_id(app: &AppHandle) -> Option<u32> {
3946
4031
  .map(|m| m.scale_factor())
3947
4032
  .unwrap_or(2.0);
3948
4033
 
3949
- let point = CGPoint::new(cx_phys / scale, cy_phys / scale);
3950
- let (ids, _) = CGDisplay::displays_with_point(point, 4).ok()?;
3951
- ids.into_iter().next()
4034
+ cg_display_id_at_physical_point(cx_phys, cy_phys, scale)
4035
+ }
4036
+
4037
+ /// Reverse of the monitor-center-to-CGDisplayID resolution above: given a
4038
+ /// CGDirectDisplayID (as picked in the multi-monitor screen picker), find the
4039
+ /// Tauri monitor whose center resolves to it and return its physical rect.
4040
+ /// Used so overlay windows (countdown, toolbar, finalizing, ...) shown during
4041
+ /// a recording that used the picker land on the same screen the user picked,
4042
+ /// not the tray icon's screen.
4043
+ #[cfg(target_os = "macos")]
4044
+ pub(crate) fn monitor_rect_for_display_id(
4045
+ app: &AppHandle,
4046
+ display_id: u32,
4047
+ ) -> Option<(i32, i32, u32, u32)> {
4048
+ let monitors = app
4049
+ .get_webview_window("popover")?
4050
+ .available_monitors()
4051
+ .ok()?;
4052
+ for monitor in monitors {
4053
+ let pos = monitor.position();
4054
+ let size = monitor.size();
4055
+ let scale = monitor.scale_factor().max(1.0);
4056
+ let cx_phys = pos.x as f64 + size.width as f64 / 2.0;
4057
+ let cy_phys = pos.y as f64 + size.height as f64 / 2.0;
4058
+ if cg_display_id_at_physical_point(cx_phys, cy_phys, scale) == Some(display_id) {
4059
+ return Some((pos.x, pos.y, size.width, size.height));
4060
+ }
4061
+ }
4062
+ None
4063
+ }
4064
+
4065
+ #[cfg(not(target_os = "macos"))]
4066
+ pub(crate) fn monitor_rect_for_display_id(
4067
+ _app: &AppHandle,
4068
+ _display_id: u32,
4069
+ ) -> Option<(i32, i32, u32, u32)> {
4070
+ None
3952
4071
  }
3953
4072
 
3954
4073
  #[cfg(target_os = "macos")]