@agent-native/core 0.114.5 → 0.114.7
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +6 -4
- package/corpus/core/src/cli/mcp.ts +124 -0
- package/corpus/core/src/cli/skills-content/help.ts +4 -3
- package/corpus/core/src/cli/skills-content/index.ts +1 -0
- package/corpus/core/src/cli/skills-content/rewind-skill.ts +52 -0
- package/corpus/core/src/cli/skills.ts +115 -13
- package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
- package/corpus/core/src/mcp/screen-memory-stdio.ts +1360 -104
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
- package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
- package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
- package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
- package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
- package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
- package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
- package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
- package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
- package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +1 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
- package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
- package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
- package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
- package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
- package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
- package/corpus/templates/clips/desktop/src/app.tsx +2478 -223
- package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
- package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
- package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
- package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
- package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
- package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
- package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
- package/corpus/templates/clips/desktop/src/styles.css +956 -134
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
- package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
- package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
- package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
- package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
- package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
- package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
- package/corpus/templates/clips/shared/meeting-join-url.ts +0 -5
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
- package/corpus/templates/content/AGENTS.md +4 -1
- package/corpus/templates/content/actions/_content-spaces.ts +6 -1
- package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
- package/corpus/templates/content/actions/_database-utils.ts +83 -15
- package/corpus/templates/content/actions/add-database-item.ts +3 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
- package/corpus/templates/content/actions/delete-content-database.ts +12 -9
- package/corpus/templates/content/actions/delete-database-items.ts +3 -2
- package/corpus/templates/content/actions/delete-document.ts +233 -6
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +4 -1
- package/corpus/templates/content/actions/list-content-databases.ts +1 -0
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
- package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
- package/corpus/templates/content/actions/pull-document.ts +15 -2
- package/corpus/templates/content/actions/restore-content-database.ts +48 -11
- package/corpus/templates/content/actions/restore-document.ts +28 -0
- package/corpus/templates/content/actions/search-documents.ts +2 -1
- package/corpus/templates/content/actions/view-screen.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
- package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
- package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
- package/corpus/templates/content/app/i18n-data.ts +83 -2
- package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
- package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +3 -0
- package/corpus/templates/content/server/db/schema.ts +2 -0
- package/corpus/templates/content/server/lib/public-documents.ts +8 -2
- package/corpus/templates/content/server/plugins/db.ts +52 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
- package/corpus/templates/content/shared/api.ts +10 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/ui/progress.tsx +3 -2
- package/dist/cli/mcp-config-writers.d.ts +2 -2
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +6 -6
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +79 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills-content/help.d.ts +1 -1
- package/dist/cli/skills-content/help.d.ts.map +1 -1
- package/dist/cli/skills-content/help.js +4 -3
- package/dist/cli/skills-content/help.js.map +1 -1
- package/dist/cli/skills-content/index.d.ts +1 -0
- package/dist/cli/skills-content/index.d.ts.map +1 -1
- package/dist/cli/skills-content/index.js +1 -0
- package/dist/cli/skills-content/index.js.map +1 -1
- package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
- package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/rewind-skill.js +53 -0
- package/dist/cli/skills-content/rewind-skill.js.map +1 -0
- package/dist/cli/skills.d.ts +6 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +90 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +3 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +4 -4
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +55 -6
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +365 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp/screen-memory-stdio.js +1014 -91
- package/dist/mcp/screen-memory-stdio.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +90 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
- package/dist/mcp-client/screen-memory-local.js +590 -12
- package/dist/mcp-client/screen-memory-local.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
- package/src/cli/mcp-config-writers.ts +6 -4
- package/src/cli/mcp.ts +124 -0
- package/src/cli/skills-content/help.ts +4 -3
- package/src/cli/skills-content/index.ts +1 -0
- package/src/cli/skills-content/rewind-skill.ts +52 -0
- package/src/cli/skills.ts +115 -13
- package/src/client/resources/ResourceTree.tsx +11 -5
- package/src/client/resources/ResourcesPanel.tsx +141 -18
- package/src/mcp/screen-memory-stdio.ts +1360 -104
- package/src/mcp-client/index.ts +9 -0
- package/src/mcp-client/screen-memory-local.ts +847 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use serde::Serialize;
|
|
1
2
|
#[cfg(target_os = "macos")]
|
|
2
3
|
use std::io::Write;
|
|
3
4
|
use std::path::PathBuf;
|
|
@@ -40,6 +41,7 @@ const COUNTDOWN_CONTROL_HIT_PAD: f64 = 8.0;
|
|
|
40
41
|
// countdown overlay so only the button zones are interactive.
|
|
41
42
|
static COUNTDOWN_CONTROL_TRACKING: AtomicBool = AtomicBool::new(false);
|
|
42
43
|
const BUBBLE_LABEL: &str = "bubble";
|
|
44
|
+
const PREPARING_LABEL: &str = "preparing";
|
|
43
45
|
const FINALIZING_LABEL: &str = "finalizing";
|
|
44
46
|
const FLOW_BAR_LABEL: &str = "flow-bar";
|
|
45
47
|
const REGION_GUIDES_LABEL: &str = "region-guides";
|
|
@@ -50,6 +52,7 @@ const OVERLAY_LABELS: &[&str] = &[
|
|
|
50
52
|
COUNTDOWN_LABEL,
|
|
51
53
|
TOOLBAR_LABEL,
|
|
52
54
|
BUBBLE_LABEL,
|
|
55
|
+
PREPARING_LABEL,
|
|
53
56
|
FINALIZING_LABEL,
|
|
54
57
|
FLOW_BAR_LABEL,
|
|
55
58
|
REGION_GUIDES_LABEL,
|
|
@@ -355,12 +358,11 @@ fn load_bubble_position(app: &AppHandle) -> Option<(i32, i32)> {
|
|
|
355
358
|
/// cursor events so the user can still click into whatever they're about to
|
|
356
359
|
/// record, and closes itself when the countdown finishes.
|
|
357
360
|
#[tauri::command]
|
|
358
|
-
pub async fn show_countdown(app: AppHandle) -> Result<
|
|
361
|
+
pub async fn show_countdown(app: AppHandle) -> Result<u64, String> {
|
|
359
362
|
dlog!("[clips-tray] show_countdown invoked");
|
|
360
363
|
mark_popover_shown(&app);
|
|
361
364
|
if let Some(existing) = app.get_webview_window(COUNTDOWN_LABEL) {
|
|
362
365
|
stop_countdown_control_tracking();
|
|
363
|
-
let _ = app.emit("clips:countdown-shortcuts-active", false);
|
|
364
366
|
let _ = existing.close();
|
|
365
367
|
}
|
|
366
368
|
let (mx, my, mw, mh) = tray_monitor_physical_rect(&app);
|
|
@@ -379,11 +381,10 @@ pub async fn show_countdown(app: AppHandle) -> Result<(), String> {
|
|
|
379
381
|
.skip_taskbar(true)
|
|
380
382
|
.shadow(false)
|
|
381
383
|
.visible(false)
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
.focused(false)
|
|
384
|
+
// The countdown is intentionally modal for three seconds so its
|
|
385
|
+
// advertised Return/Escape controls work without system-wide key
|
|
386
|
+
// monitoring. The recorder has already parked the popover.
|
|
387
|
+
.focused(true)
|
|
387
388
|
.build()
|
|
388
389
|
.map_err(|e| {
|
|
389
390
|
eprintln!("[clips-tray] countdown build failed: {}", e);
|
|
@@ -394,11 +395,26 @@ pub async fn show_countdown(app: AppHandle) -> Result<(), String> {
|
|
|
394
395
|
let _ = win.set_ignore_cursor_events(true);
|
|
395
396
|
set_capture_excluded(&win);
|
|
396
397
|
configure_overlay_behavior(&win);
|
|
397
|
-
let
|
|
398
|
-
|
|
398
|
+
let generation = match crate::shortcuts::prepare_countdown_shortcuts(app.clone()).await {
|
|
399
|
+
Ok(generation) => generation,
|
|
400
|
+
Err(error) => {
|
|
401
|
+
let _ = win.close();
|
|
402
|
+
return Err(error);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
// This is a short, explicit modal moment. Making the overlay the actual
|
|
406
|
+
// key window lets its ordinary DOM handler receive Return/Escape without
|
|
407
|
+
// broad, system-wide Input Monitoring. Closing it restores the user's
|
|
408
|
+
// prior application before capture begins.
|
|
409
|
+
present_interactive_window(&win);
|
|
399
410
|
start_countdown_control_tracking(&app);
|
|
400
411
|
dlog!("[clips-tray] countdown shown");
|
|
401
|
-
Ok(
|
|
412
|
+
Ok(generation)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#[tauri::command]
|
|
416
|
+
pub async fn finish_countdown_shortcuts(app: AppHandle, generation: u64) -> Result<(), String> {
|
|
417
|
+
crate::shortcuts::finish_countdown_shortcuts(app, generation).await
|
|
402
418
|
}
|
|
403
419
|
|
|
404
420
|
/// True when the global cursor sits inside either circular cancel/skip button
|
|
@@ -458,6 +474,65 @@ fn stop_countdown_control_tracking() {
|
|
|
458
474
|
COUNTDOWN_CONTROL_TRACKING.store(false, Ordering::SeqCst);
|
|
459
475
|
}
|
|
460
476
|
|
|
477
|
+
/// Compact visible readiness state for the slow work that intentionally runs
|
|
478
|
+
/// before the numeric countdown. This capture-excluded card briefly takes
|
|
479
|
+
/// focus as the first stage of the explicit modal start flow, so the user sees
|
|
480
|
+
/// that Start was accepted without changing the exact countdown-zero boundary.
|
|
481
|
+
#[tauri::command]
|
|
482
|
+
pub async fn show_preparing(app: AppHandle) -> Result<(), String> {
|
|
483
|
+
if let Some(existing) = app.get_webview_window(PREPARING_LABEL) {
|
|
484
|
+
let _ = existing.close();
|
|
485
|
+
}
|
|
486
|
+
let (mx, my, mw, mh) = tray_monitor_physical_rect(&app);
|
|
487
|
+
let scale = overlay_scale_factor(&app);
|
|
488
|
+
let content_w: u32 = (260.0 * scale).round() as u32;
|
|
489
|
+
let content_h: u32 = (58.0 * scale).round() as u32;
|
|
490
|
+
let margin: i32 = (14.0 * scale).round() as i32;
|
|
491
|
+
let gutter = overlay_shadow_gutter_physical(&app);
|
|
492
|
+
let w = content_w + gutter * 2;
|
|
493
|
+
let h = content_h + gutter * 2;
|
|
494
|
+
let x = (mx + (mw.saturating_sub(w) / 2) as i32).max(mx);
|
|
495
|
+
let y = (my + mh as i32 - h as i32 - margin).max(my);
|
|
496
|
+
let win = WebviewWindowBuilder::new(&app, PREPARING_LABEL, build_overlay_url("preparing"))
|
|
497
|
+
.title("Preparing recording")
|
|
498
|
+
.decorations(false)
|
|
499
|
+
.transparent(true)
|
|
500
|
+
.always_on_top(true)
|
|
501
|
+
.skip_taskbar(true)
|
|
502
|
+
.resizable(false)
|
|
503
|
+
.shadow(false)
|
|
504
|
+
.visible(false)
|
|
505
|
+
.focused(true)
|
|
506
|
+
.build()
|
|
507
|
+
.map_err(|error| format!("preparing window build failed: {error}"))?;
|
|
508
|
+
let _ = win.set_size(tauri::Size::Physical(PhysicalSize::new(w, h)));
|
|
509
|
+
let _ = win.set_position(PhysicalPosition::new(x, y));
|
|
510
|
+
let _ = win.set_ignore_cursor_events(true);
|
|
511
|
+
set_capture_excluded(&win);
|
|
512
|
+
configure_overlay_behavior(&win);
|
|
513
|
+
// Preparation and countdown are one explicit modal start flow. Making the
|
|
514
|
+
// compact readiness card key ensures it gets a real first paint and is
|
|
515
|
+
// announced before the full-screen countdown replaces it.
|
|
516
|
+
present_interactive_window(&win);
|
|
517
|
+
let _ = app.emit("clips:toolbar-preparing", true);
|
|
518
|
+
// A newly-created webview needs one paint before the async preparation can
|
|
519
|
+
// race ahead and replace it with the countdown. Without this brief yield,
|
|
520
|
+
// fast machines can show only a disabled toolbar and never render the
|
|
521
|
+
// promised textual readiness state.
|
|
522
|
+
tokio::time::sleep(Duration::from_millis(120)).await;
|
|
523
|
+
dlog!("[clips-tray] preparing recording shown");
|
|
524
|
+
Ok(())
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
#[tauri::command]
|
|
528
|
+
pub async fn hide_preparing(app: AppHandle) -> Result<(), String> {
|
|
529
|
+
let _ = app.emit("clips:toolbar-preparing", false);
|
|
530
|
+
if let Some(window) = app.get_webview_window(PREPARING_LABEL) {
|
|
531
|
+
let _ = window.close();
|
|
532
|
+
}
|
|
533
|
+
Ok(())
|
|
534
|
+
}
|
|
535
|
+
|
|
461
536
|
/// Compact bottom-left status window shown while the recorder flushes its
|
|
462
537
|
/// final chunks and awaits the server finalize. Keeping the native window near
|
|
463
538
|
/// the card's bounds makes its open/dismiss controls clickable without placing
|
|
@@ -988,7 +1063,6 @@ pub async fn hide_overlays(
|
|
|
988
1063
|
preserve_finalizing: Option<bool>,
|
|
989
1064
|
) -> Result<(), String> {
|
|
990
1065
|
stop_countdown_control_tracking();
|
|
991
|
-
let _ = app.emit("clips:countdown-shortcuts-active", false);
|
|
992
1066
|
for label in overlay_labels_to_hide(preserve_finalizing.unwrap_or(false)) {
|
|
993
1067
|
if let Some(w) = app.get_webview_window(label) {
|
|
994
1068
|
let _ = w.close();
|
|
@@ -1011,7 +1085,6 @@ pub async fn hide_overlays(
|
|
|
1011
1085
|
#[tauri::command]
|
|
1012
1086
|
pub async fn hide_recording_chrome(app: AppHandle) -> Result<(), String> {
|
|
1013
1087
|
stop_countdown_control_tracking();
|
|
1014
|
-
let _ = app.emit("clips:countdown-shortcuts-active", false);
|
|
1015
1088
|
// The countdown + toolbar always tear down on recording stop. The region
|
|
1016
1089
|
// guides only tear down when they aren't pinned on-screen via the always-on
|
|
1017
1090
|
// toggle — otherwise we'd flicker close→reopen right after stop.
|
|
@@ -1128,21 +1201,21 @@ pub fn open_macos_privacy_settings(pane: String) -> Result<(), String> {
|
|
|
1128
1201
|
#[cfg(target_os = "macos")]
|
|
1129
1202
|
{
|
|
1130
1203
|
let url = match pane.as_str() {
|
|
1131
|
-
"camera" => "x-apple.systempreferences:com.apple.
|
|
1204
|
+
"camera" => "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Camera",
|
|
1132
1205
|
"microphone" => {
|
|
1133
|
-
"x-apple.systempreferences:com.apple.
|
|
1206
|
+
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Microphone"
|
|
1134
1207
|
}
|
|
1135
1208
|
"screen" => {
|
|
1136
|
-
"x-apple.systempreferences:com.apple.
|
|
1209
|
+
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ScreenCapture"
|
|
1137
1210
|
}
|
|
1138
1211
|
"speech" => {
|
|
1139
|
-
"x-apple.systempreferences:com.apple.
|
|
1212
|
+
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_SpeechRecognition"
|
|
1140
1213
|
}
|
|
1141
1214
|
"accessibility" => {
|
|
1142
|
-
"x-apple.systempreferences:com.apple.
|
|
1215
|
+
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Accessibility"
|
|
1143
1216
|
}
|
|
1144
1217
|
"input-monitoring" => {
|
|
1145
|
-
"x-apple.systempreferences:com.apple.
|
|
1218
|
+
"x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_ListenEvent"
|
|
1146
1219
|
}
|
|
1147
1220
|
_ => return Err(format!("Unknown macOS privacy pane: {pane}")),
|
|
1148
1221
|
};
|
|
@@ -1154,6 +1227,157 @@ pub fn open_macos_privacy_settings(pane: String) -> Result<(), String> {
|
|
|
1154
1227
|
}
|
|
1155
1228
|
}
|
|
1156
1229
|
|
|
1230
|
+
#[derive(Clone, Debug, Serialize)]
|
|
1231
|
+
#[serde(rename_all = "camelCase")]
|
|
1232
|
+
pub struct RewindExcludedApplication {
|
|
1233
|
+
bundle_id: String,
|
|
1234
|
+
name: String,
|
|
1235
|
+
path: Option<String>,
|
|
1236
|
+
installed: bool,
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
#[cfg(target_os = "macos")]
|
|
1240
|
+
fn app_bundle_id(path: &str) -> Option<String> {
|
|
1241
|
+
let output = Command::new("/usr/bin/mdls")
|
|
1242
|
+
.args(["-name", "kMDItemCFBundleIdentifier", "-raw", path])
|
|
1243
|
+
.output()
|
|
1244
|
+
.ok()?;
|
|
1245
|
+
if !output.status.success() {
|
|
1246
|
+
return None;
|
|
1247
|
+
}
|
|
1248
|
+
let bundle_id = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
1249
|
+
(!bundle_id.is_empty() && bundle_id != "(null)").then_some(bundle_id)
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
#[cfg(target_os = "macos")]
|
|
1253
|
+
fn app_name_from_path(path: &str) -> String {
|
|
1254
|
+
std::path::Path::new(path)
|
|
1255
|
+
.file_stem()
|
|
1256
|
+
.and_then(|name| name.to_str())
|
|
1257
|
+
.unwrap_or("Application")
|
|
1258
|
+
.to_string()
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
fn fallback_app_name(bundle_id: &str) -> String {
|
|
1262
|
+
match bundle_id {
|
|
1263
|
+
"com.1password.1password" | "com.agilebits.onepassword7" => "1Password".to_string(),
|
|
1264
|
+
"com.bitwarden.desktop" => "Bitwarden".to_string(),
|
|
1265
|
+
"com.dashlane.dashlane" => "Dashlane".to_string(),
|
|
1266
|
+
"com.lastpass.lastpass" => "LastPass".to_string(),
|
|
1267
|
+
_ => bundle_id
|
|
1268
|
+
.rsplit('.')
|
|
1269
|
+
.next()
|
|
1270
|
+
.filter(|name| !name.is_empty())
|
|
1271
|
+
.unwrap_or("Application")
|
|
1272
|
+
.to_string(),
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
#[cfg(target_os = "macos")]
|
|
1277
|
+
fn find_installed_app(bundle_id: &str) -> Option<String> {
|
|
1278
|
+
let query = format!("kMDItemCFBundleIdentifier == '{bundle_id}'");
|
|
1279
|
+
let output = Command::new("/usr/bin/mdfind").arg(query).output().ok()?;
|
|
1280
|
+
if !output.status.success() {
|
|
1281
|
+
return None;
|
|
1282
|
+
}
|
|
1283
|
+
String::from_utf8_lossy(&output.stdout)
|
|
1284
|
+
.lines()
|
|
1285
|
+
.find(|path| path.ends_with(".app"))
|
|
1286
|
+
.map(str::to_string)
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
#[tauri::command]
|
|
1290
|
+
pub fn resolve_rewind_excluded_apps(
|
|
1291
|
+
bundle_ids: Vec<String>,
|
|
1292
|
+
) -> Result<Vec<RewindExcludedApplication>, String> {
|
|
1293
|
+
#[cfg(target_os = "macos")]
|
|
1294
|
+
{
|
|
1295
|
+
return Ok(bundle_ids
|
|
1296
|
+
.into_iter()
|
|
1297
|
+
.map(|bundle_id| {
|
|
1298
|
+
let path = find_installed_app(&bundle_id);
|
|
1299
|
+
let name = path
|
|
1300
|
+
.as_deref()
|
|
1301
|
+
.map(app_name_from_path)
|
|
1302
|
+
.unwrap_or_else(|| fallback_app_name(&bundle_id));
|
|
1303
|
+
RewindExcludedApplication {
|
|
1304
|
+
bundle_id,
|
|
1305
|
+
name,
|
|
1306
|
+
installed: path.is_some(),
|
|
1307
|
+
path,
|
|
1308
|
+
}
|
|
1309
|
+
})
|
|
1310
|
+
.collect());
|
|
1311
|
+
}
|
|
1312
|
+
#[cfg(not(target_os = "macos"))]
|
|
1313
|
+
{
|
|
1314
|
+
Ok(bundle_ids
|
|
1315
|
+
.into_iter()
|
|
1316
|
+
.map(|bundle_id| RewindExcludedApplication {
|
|
1317
|
+
name: fallback_app_name(&bundle_id),
|
|
1318
|
+
bundle_id,
|
|
1319
|
+
path: None,
|
|
1320
|
+
installed: false,
|
|
1321
|
+
})
|
|
1322
|
+
.collect())
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
#[cfg(target_os = "macos")]
|
|
1327
|
+
fn choose_rewind_excluded_apps_blocking() -> Result<Vec<RewindExcludedApplication>, String> {
|
|
1328
|
+
let script = r#"
|
|
1329
|
+
set chosenApps to choose application with prompt "Choose apps Rewind should never remember" with multiple selections allowed
|
|
1330
|
+
set chosenPaths to {}
|
|
1331
|
+
repeat with chosenApp in chosenApps
|
|
1332
|
+
set end of chosenPaths to POSIX path of (chosenApp as alias)
|
|
1333
|
+
end repeat
|
|
1334
|
+
set AppleScript's text item delimiters to linefeed
|
|
1335
|
+
return chosenPaths as text
|
|
1336
|
+
"#;
|
|
1337
|
+
let output = Command::new("/usr/bin/osascript")
|
|
1338
|
+
.args(["-e", script])
|
|
1339
|
+
.output()
|
|
1340
|
+
.map_err(|error| format!("Could not open the application picker: {error}"))?;
|
|
1341
|
+
if !output.status.success() {
|
|
1342
|
+
let error = String::from_utf8_lossy(&output.stderr);
|
|
1343
|
+
if error.contains("-128") || error.to_ascii_lowercase().contains("canceled") {
|
|
1344
|
+
return Ok(Vec::new());
|
|
1345
|
+
}
|
|
1346
|
+
return Err(format!("Could not choose applications: {}", error.trim()));
|
|
1347
|
+
}
|
|
1348
|
+
let mut apps = Vec::new();
|
|
1349
|
+
for path in String::from_utf8_lossy(&output.stdout)
|
|
1350
|
+
.lines()
|
|
1351
|
+
.map(str::trim)
|
|
1352
|
+
.filter(|path| !path.is_empty())
|
|
1353
|
+
{
|
|
1354
|
+
let Some(bundle_id) = app_bundle_id(path) else {
|
|
1355
|
+
continue;
|
|
1356
|
+
};
|
|
1357
|
+
apps.push(RewindExcludedApplication {
|
|
1358
|
+
bundle_id,
|
|
1359
|
+
name: app_name_from_path(path),
|
|
1360
|
+
path: Some(path.to_string()),
|
|
1361
|
+
installed: true,
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
Ok(apps)
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
#[tauri::command]
|
|
1368
|
+
pub async fn choose_rewind_excluded_apps() -> Result<Vec<RewindExcludedApplication>, String> {
|
|
1369
|
+
#[cfg(target_os = "macos")]
|
|
1370
|
+
{
|
|
1371
|
+
return tauri::async_runtime::spawn_blocking(choose_rewind_excluded_apps_blocking)
|
|
1372
|
+
.await
|
|
1373
|
+
.map_err(|error| format!("The application picker stopped unexpectedly: {error}"))?;
|
|
1374
|
+
}
|
|
1375
|
+
#[cfg(not(target_os = "macos"))]
|
|
1376
|
+
{
|
|
1377
|
+
Err("Choosing excluded applications is currently available on macOS only.".to_string())
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1157
1381
|
#[tauri::command]
|
|
1158
1382
|
pub fn open_local_recording_folder(path: String) -> Result<(), String> {
|
|
1159
1383
|
let trimmed = path.trim();
|
|
@@ -1902,9 +2126,9 @@ fn type_text_unicode(text: &str, target_bundle_id: Option<String>) {
|
|
|
1902
2126
|
#[cfg(not(target_os = "macos"))]
|
|
1903
2127
|
fn type_text_unicode(_text: &str) {}
|
|
1904
2128
|
|
|
1905
|
-
/// Record the popover's current recording state.
|
|
1906
|
-
/// tray
|
|
1907
|
-
///
|
|
2129
|
+
/// Record the popover's current recording state. While active, ordinary app
|
|
2130
|
+
/// and tray opens restore the parked popover; stopping remains an explicit
|
|
2131
|
+
/// action in the popover, toolbar, or tray menu.
|
|
1908
2132
|
#[tauri::command]
|
|
1909
2133
|
pub async fn set_recording_state(app: AppHandle, active: bool) -> Result<(), String> {
|
|
1910
2134
|
dlog!("[clips-tray] set_recording_state active={}", active);
|
|
@@ -2254,7 +2478,10 @@ fn is_pinhole_popover(window: &WebviewWindow) -> bool {
|
|
|
2254
2478
|
|
|
2255
2479
|
fn present_popover(app: &AppHandle, window: &WebviewWindow) {
|
|
2256
2480
|
clear_voice_wake_state(app);
|
|
2257
|
-
|
|
2481
|
+
// Reopening Clips must not silently override the user's capture-visibility
|
|
2482
|
+
// preference. `set_capture_excluded` keeps the window private by default
|
|
2483
|
+
// and includes it only when "Show Clips in screen captures" is enabled.
|
|
2484
|
+
set_capture_excluded(window);
|
|
2258
2485
|
// Re-apply Space behavior — `orderOut:` resets it, so without this the
|
|
2259
2486
|
// popover sticks to whichever Space it was first shown on.
|
|
2260
2487
|
configure_overlay_behavior(window);
|
|
@@ -49,6 +49,18 @@ pub struct ScreenMemoryConfig {
|
|
|
49
49
|
pub segment_seconds: u64,
|
|
50
50
|
#[serde(default = "default_screen_memory_sample_interval_seconds")]
|
|
51
51
|
pub sample_interval_seconds: u64,
|
|
52
|
+
#[serde(default)]
|
|
53
|
+
pub capture_mode: RewindCaptureMode,
|
|
54
|
+
#[serde(default = "default_rewind_review_before_sending")]
|
|
55
|
+
pub review_before_sending: bool,
|
|
56
|
+
#[serde(default = "default_rewind_auto_preview_before_sending")]
|
|
57
|
+
pub auto_preview_before_sending: bool,
|
|
58
|
+
#[serde(default)]
|
|
59
|
+
pub agent_clip_retention: RewindAgentClipRetention,
|
|
60
|
+
#[serde(default = "default_screen_memory_excluded_bundle_ids")]
|
|
61
|
+
pub excluded_bundle_ids: Vec<String>,
|
|
62
|
+
#[serde(default = "default_screen_memory_exclude_private_windows")]
|
|
63
|
+
pub exclude_private_windows: bool,
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
impl Default for ScreenMemoryConfig {
|
|
@@ -60,10 +72,39 @@ impl Default for ScreenMemoryConfig {
|
|
|
60
72
|
max_bytes: default_screen_memory_max_bytes(),
|
|
61
73
|
segment_seconds: default_screen_memory_segment_seconds(),
|
|
62
74
|
sample_interval_seconds: default_screen_memory_sample_interval_seconds(),
|
|
75
|
+
capture_mode: RewindCaptureMode::default(),
|
|
76
|
+
review_before_sending: default_rewind_review_before_sending(),
|
|
77
|
+
auto_preview_before_sending: default_rewind_auto_preview_before_sending(),
|
|
78
|
+
agent_clip_retention: RewindAgentClipRetention::default(),
|
|
79
|
+
excluded_bundle_ids: default_screen_memory_excluded_bundle_ids(),
|
|
80
|
+
exclude_private_windows: default_screen_memory_exclude_private_windows(),
|
|
63
81
|
}
|
|
64
82
|
}
|
|
65
83
|
}
|
|
66
84
|
|
|
85
|
+
/// The local capture tracks Rewind is allowed to retain. Audio collection is
|
|
86
|
+
/// explicit so an existing local buffer never begins recording sound by default.
|
|
87
|
+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
88
|
+
#[serde(rename_all = "kebab-case")]
|
|
89
|
+
pub enum RewindCaptureMode {
|
|
90
|
+
#[default]
|
|
91
|
+
Visuals,
|
|
92
|
+
VisualsAudio,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
96
|
+
#[serde(rename_all = "kebab-case")]
|
|
97
|
+
pub enum RewindAgentClipRetention {
|
|
98
|
+
#[default]
|
|
99
|
+
Forever,
|
|
100
|
+
#[serde(rename = "24-hours")]
|
|
101
|
+
Hours24,
|
|
102
|
+
#[serde(rename = "7-days")]
|
|
103
|
+
Days7,
|
|
104
|
+
#[serde(rename = "30-days")]
|
|
105
|
+
Days30,
|
|
106
|
+
}
|
|
107
|
+
|
|
67
108
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
68
109
|
#[serde(rename_all = "camelCase")]
|
|
69
110
|
pub struct FeatureConfig {
|
|
@@ -130,7 +171,24 @@ fn default_whisper_model_enabled() -> bool {
|
|
|
130
171
|
}
|
|
131
172
|
|
|
132
173
|
fn default_screen_memory_retention_hours() -> u32 {
|
|
133
|
-
|
|
174
|
+
8
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn default_screen_memory_excluded_bundle_ids() -> Vec<String> {
|
|
178
|
+
[
|
|
179
|
+
"com.1password.1password",
|
|
180
|
+
"com.agilebits.onepassword7",
|
|
181
|
+
"com.bitwarden.desktop",
|
|
182
|
+
"com.dashlane.dashlane",
|
|
183
|
+
"com.lastpass.lastpass",
|
|
184
|
+
]
|
|
185
|
+
.into_iter()
|
|
186
|
+
.map(str::to_string)
|
|
187
|
+
.collect()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn default_screen_memory_exclude_private_windows() -> bool {
|
|
191
|
+
false
|
|
134
192
|
}
|
|
135
193
|
|
|
136
194
|
fn default_screen_memory_max_bytes() -> u64 {
|
|
@@ -145,6 +203,14 @@ fn default_screen_memory_sample_interval_seconds() -> u64 {
|
|
|
145
203
|
10
|
|
146
204
|
}
|
|
147
205
|
|
|
206
|
+
fn default_rewind_review_before_sending() -> bool {
|
|
207
|
+
true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn default_rewind_auto_preview_before_sending() -> bool {
|
|
211
|
+
true
|
|
212
|
+
}
|
|
213
|
+
|
|
148
214
|
impl Default for FeatureConfig {
|
|
149
215
|
fn default() -> Self {
|
|
150
216
|
Self {
|
|
@@ -268,6 +334,14 @@ pub async fn get_feature_config(app: AppHandle) -> Result<FeatureConfig, String>
|
|
|
268
334
|
#[tauri::command]
|
|
269
335
|
pub async fn set_feature_config(app: AppHandle, config: FeatureConfig) -> Result<(), String> {
|
|
270
336
|
let previous = load_config(&app);
|
|
337
|
+
if (crate::rewind_clip::is_active(&app) || crate::util::is_recording_active(&app))
|
|
338
|
+
&& rewind_capture_contract_changed(&previous.screen_memory, &config.screen_memory)
|
|
339
|
+
{
|
|
340
|
+
return Err(
|
|
341
|
+
"Rewind capture settings stay unchanged while a Clip is recording. Stop the Clip before turning Rewind on or off, pausing it, or changing what it remembers."
|
|
342
|
+
.to_string(),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
271
345
|
if previous.launch_at_login_enabled != config.launch_at_login_enabled {
|
|
272
346
|
if let Err(err) = apply_launch_at_login(&app, config.launch_at_login_enabled) {
|
|
273
347
|
eprintln!("[clips-tray] launch-at-login apply failed: {err}");
|
|
@@ -296,3 +370,76 @@ pub async fn set_feature_config(app: AppHandle, config: FeatureConfig) -> Result
|
|
|
296
370
|
let _ = app.emit("app:feature-config-changed", config);
|
|
297
371
|
Ok(())
|
|
298
372
|
}
|
|
373
|
+
|
|
374
|
+
fn rewind_capture_contract_changed(
|
|
375
|
+
previous: &ScreenMemoryConfig,
|
|
376
|
+
next: &ScreenMemoryConfig,
|
|
377
|
+
) -> bool {
|
|
378
|
+
previous.enabled != next.enabled
|
|
379
|
+
|| previous.paused != next.paused
|
|
380
|
+
|| previous.capture_mode != next.capture_mode
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#[cfg(test)]
|
|
384
|
+
mod tests {
|
|
385
|
+
use super::*;
|
|
386
|
+
|
|
387
|
+
#[test]
|
|
388
|
+
fn screen_memory_config_defaults_agent_handoff_fields_when_loading_legacy_json() {
|
|
389
|
+
let config: ScreenMemoryConfig = serde_json::from_value(serde_json::json!({
|
|
390
|
+
"enabled": true,
|
|
391
|
+
"retentionHours": 24
|
|
392
|
+
}))
|
|
393
|
+
.unwrap();
|
|
394
|
+
|
|
395
|
+
assert_eq!(config.retention_hours, 24);
|
|
396
|
+
assert_eq!(config.capture_mode, RewindCaptureMode::Visuals);
|
|
397
|
+
assert!(config.review_before_sending);
|
|
398
|
+
assert!(config.auto_preview_before_sending);
|
|
399
|
+
assert_eq!(
|
|
400
|
+
config.agent_clip_retention,
|
|
401
|
+
RewindAgentClipRetention::Forever
|
|
402
|
+
);
|
|
403
|
+
assert!(!config.exclude_private_windows);
|
|
404
|
+
assert!(config
|
|
405
|
+
.excluded_bundle_ids
|
|
406
|
+
.contains(&"com.1password.1password".to_string()));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
#[test]
|
|
410
|
+
fn rewind_enums_use_stable_kebab_case_values() {
|
|
411
|
+
assert_eq!(
|
|
412
|
+
serde_json::to_string(&RewindCaptureMode::VisualsAudio).unwrap(),
|
|
413
|
+
"\"visuals-audio\""
|
|
414
|
+
);
|
|
415
|
+
assert_eq!(
|
|
416
|
+
serde_json::to_string(&RewindAgentClipRetention::Days7).unwrap(),
|
|
417
|
+
"\"7-days\""
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#[test]
|
|
422
|
+
fn active_clip_interlock_only_blocks_capture_contract_changes() {
|
|
423
|
+
let previous = ScreenMemoryConfig {
|
|
424
|
+
enabled: true,
|
|
425
|
+
paused: false,
|
|
426
|
+
capture_mode: RewindCaptureMode::VisualsAudio,
|
|
427
|
+
..ScreenMemoryConfig::default()
|
|
428
|
+
};
|
|
429
|
+
let mut retention_only = previous.clone();
|
|
430
|
+
retention_only.retention_hours = 24;
|
|
431
|
+
assert!(!rewind_capture_contract_changed(&previous, &retention_only));
|
|
432
|
+
|
|
433
|
+
let mut paused = previous.clone();
|
|
434
|
+
paused.paused = true;
|
|
435
|
+
assert!(rewind_capture_contract_changed(&previous, &paused));
|
|
436
|
+
|
|
437
|
+
let mut disabled = previous.clone();
|
|
438
|
+
disabled.enabled = false;
|
|
439
|
+
assert!(rewind_capture_contract_changed(&previous, &disabled));
|
|
440
|
+
|
|
441
|
+
let mut mode_changed = previous.clone();
|
|
442
|
+
mode_changed.capture_mode = RewindCaptureMode::Visuals;
|
|
443
|
+
assert!(rewind_capture_contract_changed(&previous, &mode_changed));
|
|
444
|
+
}
|
|
445
|
+
}
|