@agent-native/core 0.153.0 → 0.153.2

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 (38) hide show
  1. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +21 -19
  2. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -2
  3. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +3 -2
  4. package/corpus/templates/calendar/app/lib/event-layout.ts +16 -35
  5. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +27 -55
  6. package/corpus/templates/clips/app/components/player/share-dialog.tsx +104 -66
  7. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +45 -43
  8. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -1
  9. package/corpus/templates/clips/desktop/src/app.tsx +818 -442
  10. package/corpus/templates/clips/desktop/src/styles.css +244 -2
  11. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
  12. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +584 -1
  14. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +25 -0
  15. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +1 -1
  16. package/corpus/templates/factory/app/routes/factory.tsx +0 -3
  17. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
  18. package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +28 -25
  19. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +50 -0
  20. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +12 -60
  21. package/dist/agent/production-agent.d.ts +8 -0
  22. package/dist/agent/production-agent.js +25 -2
  23. package/dist/agent/run-manager.js +13 -3
  24. package/dist/client/AgentPanel.js +6 -4
  25. package/dist/client/org/WorkspaceNotice.js +7 -4
  26. package/dist/client/sharing/ShareButton.d.ts +2 -2
  27. package/dist/client/sharing/ShareButton.js +18 -13
  28. package/dist/client/sharing/ShareDialog.d.ts +1 -1
  29. package/dist/client/sharing/ShareDialog.js +16 -11
  30. package/dist/collab/awareness.d.ts +2 -2
  31. package/dist/collab/struct-routes.d.ts +1 -1
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/server/auth.js +18 -4
  34. package/dist/server/better-auth-instance.js +8 -1
  35. package/dist/server/magic-link-attribution.d.ts +21 -0
  36. package/dist/server/magic-link-attribution.js +115 -0
  37. package/dist/server/realtime-token.d.ts +1 -1
  38. package/package.json +1 -1
@@ -1,7 +1,10 @@
1
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
1
2
  import {
2
3
  IconAlertTriangle,
3
4
  IconArrowLeft,
4
5
  IconCalendarEvent,
6
+ IconCheck,
7
+ IconChevronDown,
5
8
  IconCircleCheck,
6
9
  IconCopy,
7
10
  IconDownload,
@@ -24,6 +27,7 @@ import { emit, listen } from "@tauri-apps/api/event";
24
27
  import { getCurrentWindow } from "@tauri-apps/api/window";
25
28
  import { open as openExternal } from "@tauri-apps/plugin-shell";
26
29
  import {
30
+ type ReactElement,
27
31
  type ReactNode,
28
32
  type RefObject,
29
33
  useCallback,
@@ -796,6 +800,10 @@ function measurePopoverHeight(el: HTMLElement): number {
796
800
  // by the current viewport height.
797
801
  let lowestBottom = rect.bottom;
798
802
  for (const child of Array.from(el.querySelectorAll<HTMLElement>("*"))) {
803
+ // Floating settings layers are intentionally independent of the native
804
+ // window size. Their content can scroll inside the layer without changing
805
+ // the tray popover underneath it.
806
+ if (child.closest('[data-popover-overlay="true"]')) continue;
799
807
  const childStyle = window.getComputedStyle(child);
800
808
  if (childStyle.display === "none") continue;
801
809
  const childRect = child.getBoundingClientRect();
@@ -2625,11 +2633,12 @@ export function App() {
2625
2633
  const appRef = useRef<HTMLDivElement | null>(null);
2626
2634
  usePopoverAutoSize(appRef, {
2627
2635
  disabled:
2636
+ popoverView === "rewind-settings" ||
2628
2637
  (popoverView !== "settings" && !popoverVisible) ||
2629
2638
  isRecording ||
2630
2639
  recordingFlowActive,
2631
2640
  width:
2632
- popoverView === "settings" ? 920 : popoverView === "memory" ? 440 : 360,
2641
+ popoverView === "settings" ? 920 : popoverView === "memory" ? 440 : 320,
2633
2642
  });
2634
2643
 
2635
2644
  const loadPendingUploads = useCallback(async () => {
@@ -2694,6 +2703,29 @@ export function App() {
2694
2703
  loadPendingUploads();
2695
2704
  }, [loadPendingUploads, popoverVisible]);
2696
2705
 
2706
+ useEffect(() => {
2707
+ let cancelled = false;
2708
+ void invoke<{ recovered: number }>(
2709
+ "native_fullscreen_recover_orphaned_uploads",
2710
+ { serverUrl },
2711
+ )
2712
+ .then((result) => {
2713
+ if (!cancelled && result.recovered > 0) {
2714
+ return loadPendingUploads();
2715
+ }
2716
+ return undefined;
2717
+ })
2718
+ .catch((error) => {
2719
+ console.warn(
2720
+ "[clips-tray] orphaned recording recovery lookup failed:",
2721
+ error,
2722
+ );
2723
+ });
2724
+ return () => {
2725
+ cancelled = true;
2726
+ };
2727
+ }, [loadPendingUploads, serverUrl]);
2728
+
2697
2729
  // ---- persist selections -------------------------------------------------
2698
2730
 
2699
2731
  useEffect(() => saveString(MODE_KEY, mode), [mode]);
@@ -3741,8 +3773,6 @@ export function App() {
3741
3773
  rewindAgentPromptCopied={rewindAgentPromptCopied}
3742
3774
  onCopyRewindAgentPrompt={copyRewindAgentPrompt}
3743
3775
  onOpenRewindDocs={openRewindDocs}
3744
- onOpenRewind={() => setPopoverView("rewind-settings")}
3745
- onOpenMemory={() => setPopoverView("memory")}
3746
3776
  onCancel={() => {
3747
3777
  setPromptRewindEnable(false);
3748
3778
  setPopoverView(
@@ -3790,11 +3820,6 @@ export function App() {
3790
3820
  rewindAgentPromptCopied={rewindAgentPromptCopied}
3791
3821
  onCopyRewindAgentPrompt={copyRewindAgentPrompt}
3792
3822
  onOpenRewindDocs={openRewindDocs}
3793
- onOpenRewind={() => {
3794
- setPromptRewindEnable(false);
3795
- setRewindSettingsReturnView("settings");
3796
- setPopoverView("rewind-settings");
3797
- }}
3798
3823
  onCancel={() => setPopoverView("recorder")}
3799
3824
  />
3800
3825
  </div>
@@ -4386,9 +4411,10 @@ function PendingUploadBanner({
4386
4411
  ) : null}
4387
4412
  <button
4388
4413
  type="button"
4389
- className="pending-upload-retry"
4414
+ className={`pending-upload-retry${retrying ? " pending-upload-retry-spinning" : ""}`}
4390
4415
  disabled={actionsDisabled}
4391
4416
  onClick={() => onRetry(latest)}
4417
+ aria-busy={retrying}
4392
4418
  >
4393
4419
  <IconRefresh size={14} stroke={2} />
4394
4420
  {retrying ? (retryingUploadStatus ?? "Retrying") : "Retry"}
@@ -5284,8 +5310,6 @@ function Setup({
5284
5310
  rewindAgentPromptCopied,
5285
5311
  onCopyRewindAgentPrompt,
5286
5312
  onOpenRewindDocs,
5287
- onOpenRewind,
5288
- onOpenMemory,
5289
5313
  onCancel,
5290
5314
  onSignOut,
5291
5315
  }: {
@@ -5314,14 +5338,19 @@ function Setup({
5314
5338
  rewindAgentPromptCopied: boolean;
5315
5339
  onCopyRewindAgentPrompt: () => void;
5316
5340
  onOpenRewindDocs: () => void;
5317
- onOpenRewind?: () => void;
5318
- onOpenMemory?: () => void;
5319
5341
  onCancel?: () => void;
5320
5342
  onSignOut?: () => void;
5321
5343
  }) {
5322
5344
  const [url, setUrl] = useState(initial ?? DEFAULT_URL);
5323
5345
  const [readinessOpen, setReadinessOpen] = useState(false);
5324
5346
  const [settingsTab, setSettingsTab] = useState<SettingsTabId>("general");
5347
+ const [rewindSettingsOpen, setRewindSettingsOpen] = useState(false);
5348
+ const [rewindActivityOpen, setRewindActivityOpen] = useState(false);
5349
+ const [rewindMemoryOpen, setRewindMemoryOpen] = useState(false);
5350
+ const [rewindSearchOpen, setRewindSearchOpen] = useState(false);
5351
+ const [rewindPrivacyOpen, setRewindPrivacyOpen] = useState(false);
5352
+ const [rewindAgentSetupOpen, setRewindAgentSetupOpen] = useState(false);
5353
+ const [rewindHandoffOpen, setRewindHandoffOpen] = useState(false);
5325
5354
  const featureConfig = useFeatureConfig();
5326
5355
  const updateStatus = useUpdateStatus();
5327
5356
  const voiceEnabled = featureConfig?.voiceEnabled !== false;
@@ -5390,7 +5419,6 @@ function Setup({
5390
5419
  const [rewindEgressEvents, setRewindEgressEvents] = useState<
5391
5420
  RewindEgressEvent[]
5392
5421
  >([]);
5393
- const [rewindEgressOpen, setRewindEgressOpen] = useState(false);
5394
5422
  const [rewindLocalQuery, setRewindLocalQuery] = useState("");
5395
5423
  const [rewindLocalResult, setRewindLocalResult] =
5396
5424
  useState<RewindLocalAskResult | null>(null);
@@ -6314,6 +6342,13 @@ function Setup({
6314
6342
  }
6315
6343
 
6316
6344
  if (surface === "rewind") {
6345
+ const captureDisabled = screenMemoryConfigBusy || captureControlsLocked;
6346
+ const excludedAppsSummary =
6347
+ excludedAppGroups.length === 0
6348
+ ? "No apps excluded"
6349
+ : `${excludedAppGroups.length} app${excludedAppGroups.length === 1 ? "" : "s"} excluded`;
6350
+ const storageLabel = formatStorageBytes(screenMemory.maxBytes);
6351
+
6317
6352
  return (
6318
6353
  <div className="setup popover-view rewind-settings-surface">
6319
6354
  <div className="setup-header">
@@ -6327,465 +6362,650 @@ function Setup({
6327
6362
  </button>
6328
6363
  <h2>Rewind settings</h2>
6329
6364
  </div>
6330
- <div className="rewind-setting-row">
6331
- <SettingLabel
6332
- label="Rewind"
6333
- hint={
6334
- !screenMemory.enabled
6335
- ? "Off"
6336
- : screenMemory.paused
6337
- ? "Paused · existing local memory is still available"
6338
- : "Remembering locally"
6339
- }
6340
- />
6341
- <Switch
6342
- on={screenMemory.enabled}
6343
- disabled={screenMemoryConfigBusy || captureControlsLocked}
6344
- onChange={(enabled) => {
6345
- if (enabled) setRewindConsentOpen(true);
6346
- else
6347
- void setScreenMemoryConfig({ enabled: false, paused: false });
6348
- }}
6349
- label="Enable Rewind"
6350
- />
6365
+ <div className="rewind-settings-section">
6366
+ <div className="rewind-setting-row">
6367
+ <SettingLabel
6368
+ label="Rewind"
6369
+ hint={
6370
+ !screenMemory.enabled
6371
+ ? "Off"
6372
+ : screenMemory.paused
6373
+ ? "Paused · existing local memory is still available"
6374
+ : "Remembering locally"
6375
+ }
6376
+ />
6377
+ <Switch
6378
+ on={screenMemory.enabled}
6379
+ disabled={captureDisabled}
6380
+ onChange={(enabled) => {
6381
+ if (enabled) setRewindConsentOpen(true);
6382
+ else
6383
+ void setScreenMemoryConfig({ enabled: false, paused: false });
6384
+ }}
6385
+ label="Enable Rewind"
6386
+ />
6387
+ </div>
6388
+ {captureControlsLocked ? (
6389
+ <p className="rewind-capture-lock-note" role="status">
6390
+ Capture settings unlock when this Clip ends.
6391
+ </p>
6392
+ ) : null}
6393
+ {screenMemory.enabled ? (
6394
+ <>
6395
+ <div className="rewind-setting-row">
6396
+ <SettingLabel
6397
+ label="Remember"
6398
+ hint="Choose whether audio joins the local screen memory."
6399
+ />
6400
+ <RewindChoicePopover
6401
+ title="Remember"
6402
+ value={screenMemory.captureMode}
6403
+ options={[
6404
+ { value: "visuals", label: "Visuals" },
6405
+ { value: "visuals-audio", label: "Visuals + audio" },
6406
+ ]}
6407
+ disabled={captureDisabled}
6408
+ onChange={(value) =>
6409
+ void setScreenMemoryConfig({
6410
+ captureMode: value as "visuals" | "visuals-audio",
6411
+ })
6412
+ }
6413
+ />
6414
+ </div>
6415
+ <div className="rewind-setting-row">
6416
+ <SettingLabel
6417
+ label="Remember the last…"
6418
+ hint="How long Rewind keeps recent moments available."
6419
+ />
6420
+ <RewindChoicePopover
6421
+ title="Remember the last"
6422
+ value={String(screenMemory.retentionHours)}
6423
+ options={[
6424
+ { value: "8", label: "8 hours" },
6425
+ { value: "24", label: "24 hours" },
6426
+ ]}
6427
+ disabled={screenMemoryConfigBusy}
6428
+ onChange={(value) =>
6429
+ void setScreenMemoryConfig({
6430
+ retentionHours: Number(value),
6431
+ })
6432
+ }
6433
+ />
6434
+ </div>
6435
+ <div className="rewind-setting-row">
6436
+ <SettingLabel
6437
+ label="Keep up to"
6438
+ hint="Older moments are removed when this local limit is reached."
6439
+ />
6440
+ <RewindChoicePopover
6441
+ title="Keep up to"
6442
+ value={String(screenMemory.maxBytes)}
6443
+ options={[
6444
+ {
6445
+ value: String(5 * 1024 * 1024 * 1024),
6446
+ label: "5 GB",
6447
+ },
6448
+ {
6449
+ value: String(20 * 1024 * 1024 * 1024),
6450
+ label: "20 GB",
6451
+ },
6452
+ {
6453
+ value: String(50 * 1024 * 1024 * 1024),
6454
+ label: "50 GB",
6455
+ },
6456
+ ]}
6457
+ disabled={screenMemoryConfigBusy}
6458
+ onChange={(value) =>
6459
+ void setScreenMemoryConfig({ maxBytes: Number(value) })
6460
+ }
6461
+ />
6462
+ </div>
6463
+ <div className="rewind-settings-status">
6464
+ <span
6465
+ className={`rewind-home-dot ${rewindStatusPresentation.isLive ? "is-live" : ""}`}
6466
+ />
6467
+ <span className="rewind-settings-status-copy">
6468
+ <strong>{rewindStatusPresentation.title}</strong>
6469
+ <span>
6470
+ {rewindStatusPresentation.kind === "recording" &&
6471
+ !rewindStatusPresentation.hasError
6472
+ ? `${screenMemorySegments.length} retained segment${screenMemorySegments.length === 1 ? "" : "s"} · ${formatStorageBytes(screenMemoryTotalBytes)}`
6473
+ : rewindStatusPresentation.detail}
6474
+ </span>
6475
+ </span>
6476
+ </div>
6477
+ </>
6478
+ ) : null}
6351
6479
  </div>
6352
- {captureControlsLocked ? (
6353
- <p className="rewind-capture-lock-note" role="status">
6354
- Rewind capture settings unlock when this Clip ends. This keeps one
6355
- screen and audio recorder running at a time.
6356
- </p>
6357
- ) : null}
6358
6480
  {screenMemory.enabled ? (
6359
6481
  <>
6360
- <div className="rewind-setting-row">
6361
- <SettingLabel
6362
- label="Remember"
6363
- hint="Choose whether audio joins the local screen memory."
6364
- htmlFor="rewind-capture-mode"
6365
- />
6366
- <select
6367
- id="rewind-capture-mode"
6368
- className="setup-select rewind-setting-control"
6369
- disabled={screenMemoryConfigBusy || captureControlsLocked}
6370
- value={screenMemory.captureMode}
6371
- onChange={(event) =>
6372
- void setScreenMemoryConfig({
6373
- captureMode: event.target.value as
6374
- | "visuals"
6375
- | "visuals-audio",
6376
- })
6377
- }
6378
- >
6379
- <option value="visuals">Visuals</option>
6380
- <option value="visuals-audio">Visuals + audio</option>
6381
- </select>
6382
- </div>
6383
- <div className="rewind-setting-row">
6384
- <SettingLabel
6385
- label="Remember the last…"
6386
- hint={`${formatStorageBytes(screenMemory.maxBytes)} maximum on disk`}
6387
- htmlFor="rewind-retention"
6388
- />
6389
- <select
6390
- id="rewind-retention"
6391
- className="setup-select rewind-setting-control"
6392
- disabled={screenMemoryConfigBusy}
6393
- value={screenMemory.retentionHours}
6394
- onChange={(event) =>
6395
- void setScreenMemoryConfig({
6396
- retentionHours: Number(event.target.value),
6397
- })
6398
- }
6399
- >
6400
- <option value={8}>8 hours</option>
6401
- <option value={24}>24 hours</option>
6402
- </select>
6403
- </div>
6404
- <div className="rewind-setting-row">
6405
- <SettingLabel
6406
- label="Keep up to"
6407
- hint="Older moments are removed automatically when this limit is reached."
6408
- htmlFor="rewind-disk-cap"
6409
- />
6410
- <select
6411
- id="rewind-disk-cap"
6412
- className="setup-select rewind-setting-control"
6413
- disabled={screenMemoryConfigBusy}
6414
- value={screenMemory.maxBytes}
6415
- onChange={(event) =>
6416
- void setScreenMemoryConfig({
6417
- maxBytes: Number(event.target.value),
6418
- })
6419
- }
6420
- >
6421
- <option value={5 * 1024 * 1024 * 1024}>5 GB</option>
6422
- <option value={20 * 1024 * 1024 * 1024}>20 GB</option>
6423
- <option value={50 * 1024 * 1024 * 1024}>50 GB</option>
6424
- </select>
6425
- </div>
6426
- <div className="rewind-settings-status">
6427
- <span
6428
- className={`rewind-home-dot ${rewindStatusPresentation.isLive ? "is-live" : ""}`}
6429
- />
6430
- <span className="rewind-settings-status-copy">
6431
- <strong>{rewindStatusPresentation.title}</strong>
6432
- <span>
6433
- {rewindStatusPresentation.kind === "recording" &&
6434
- !rewindStatusPresentation.hasError
6435
- ? `${screenMemorySegments.length} retained segment${screenMemorySegments.length === 1 ? "" : "s"} · ${formatStorageBytes(screenMemoryTotalBytes)}`
6436
- : rewindStatusPresentation.detail}
6437
- </span>
6438
- </span>
6439
- </div>
6440
6482
  <div className="setup-section-heading">Privacy</div>
6441
- <div className="rewind-excluded-apps">
6442
- <div className="rewind-excluded-apps-header">
6443
- <SettingLabel
6444
- label="Excluded apps"
6445
- hint="Rewind never remembers these applications."
6446
- />
6447
- <button
6448
- type="button"
6449
- className="secondary rewind-choose-apps"
6450
- disabled={excludedAppsBusy || screenMemoryConfigBusy}
6451
- onClick={() => void chooseExcludedApplications()}
6483
+ <div className="rewind-settings-section">
6484
+ <div className="rewind-setting-row rewind-setting-row--action">
6485
+ <div className="rewind-setting-copy">
6486
+ <SettingLabel
6487
+ label="Excluded apps"
6488
+ hint="Rewind never remembers these applications."
6489
+ />
6490
+ <span>{excludedAppsSummary}</span>
6491
+ </div>
6492
+ <DesktopSettingsPopover
6493
+ title="Excluded apps"
6494
+ open={rewindPrivacyOpen}
6495
+ onOpenChange={setRewindPrivacyOpen}
6496
+ side="bottom"
6497
+ contentClassName="rewind-popover-content"
6498
+ trigger={
6499
+ <button
6500
+ type="button"
6501
+ className="secondary rewind-manage-button"
6502
+ disabled={screenMemoryConfigBusy}
6503
+ >
6504
+ Manage
6505
+ </button>
6506
+ }
6452
6507
  >
6453
- <IconFolderOpen size={14} stroke={1.9} />
6454
- {excludedAppsBusy ? "Choosing…" : "Choose applications…"}
6455
- </button>
6508
+ <div className="rewind-popover-stack">
6509
+ <p className="rewind-popover-hint">
6510
+ These applications are always excluded from local Rewind
6511
+ memory.
6512
+ </p>
6513
+ <button
6514
+ type="button"
6515
+ className="secondary"
6516
+ disabled={excludedAppsBusy || screenMemoryConfigBusy}
6517
+ onClick={() => void chooseExcludedApplications()}
6518
+ >
6519
+ <IconFolderOpen size={14} stroke={1.9} />
6520
+ {excludedAppsBusy ? "Choosing…" : "Add applications…"}
6521
+ </button>
6522
+ {excludedAppGroups.length > 0 ? (
6523
+ <div className="rewind-excluded-app-list">
6524
+ {excludedAppGroups.map((app) => (
6525
+ <div
6526
+ className={`rewind-excluded-app ${app.installed ? "" : "is-missing"}`}
6527
+ key={app.bundleId}
6528
+ >
6529
+ <div
6530
+ className="rewind-excluded-app-mark"
6531
+ aria-hidden="true"
6532
+ >
6533
+ {app.name.slice(0, 1).toUpperCase()}
6534
+ </div>
6535
+ <div className="rewind-excluded-app-copy">
6536
+ <strong>{app.name}</strong>
6537
+ <span>
6538
+ {app.installed
6539
+ ? "Excluded"
6540
+ : "Not currently installed · still excluded"}
6541
+ </span>
6542
+ </div>
6543
+ <button
6544
+ type="button"
6545
+ className="rewind-excluded-app-remove"
6546
+ aria-label={`Remove ${app.name}`}
6547
+ disabled={screenMemoryConfigBusy}
6548
+ onClick={() =>
6549
+ removeExcludedApplications(app.bundleIds)
6550
+ }
6551
+ >
6552
+ <IconX size={14} stroke={2} />
6553
+ </button>
6554
+ </div>
6555
+ ))}
6556
+ </div>
6557
+ ) : (
6558
+ <p className="setup-hint">No apps are excluded.</p>
6559
+ )}
6560
+ </div>
6561
+ </DesktopSettingsPopover>
6456
6562
  </div>
6457
- {excludedAppGroups.length > 0 ? (
6458
- <div className="rewind-excluded-app-list">
6459
- {excludedAppGroups.map((app) => (
6460
- <div
6461
- className={`rewind-excluded-app ${app.installed ? "" : "is-missing"}`}
6462
- key={app.bundleId}
6563
+ </div>
6564
+ <div className="setup-section-heading">Agent</div>
6565
+ <div className="rewind-settings-section">
6566
+ <div className="rewind-setting-row rewind-setting-row--action">
6567
+ <div className="rewind-setting-copy">
6568
+ <SettingLabel
6569
+ label="Agent setup"
6570
+ hint="Install the reusable Rewind instructions for a local agent."
6571
+ />
6572
+ <span>Prompt and local connection</span>
6573
+ </div>
6574
+ <DesktopSettingsPopover
6575
+ title="Agent setup"
6576
+ open={rewindAgentSetupOpen}
6577
+ onOpenChange={setRewindAgentSetupOpen}
6578
+ side="bottom"
6579
+ contentClassName="rewind-popover-content"
6580
+ trigger={
6581
+ <button
6582
+ type="button"
6583
+ className="secondary rewind-manage-button"
6463
6584
  >
6464
- <div
6465
- className="rewind-excluded-app-mark"
6466
- aria-hidden="true"
6467
- >
6468
- {app.name.slice(0, 1).toUpperCase()}
6585
+ Manage
6586
+ </button>
6587
+ }
6588
+ >
6589
+ <div className="rewind-popover-stack">
6590
+ <div className="rewind-agent-guide">
6591
+ <div className="rewind-agent-guide-icon">
6592
+ <IconHistory size={17} stroke={1.8} />
6469
6593
  </div>
6470
- <div className="rewind-excluded-app-copy">
6471
- <strong>{app.name}</strong>
6472
- <span>
6473
- {app.installed
6474
- ? "Excluded"
6475
- : "Not currently installed · still excluded"}
6476
- </span>
6594
+ <div>
6595
+ <strong>Set up your agent once</strong>
6596
+ <p>
6597
+ Copy the setup prompt into a compatible agent. It
6598
+ installs Rewind's instructions and repairs the local
6599
+ connection.
6600
+ </p>
6477
6601
  </div>
6602
+ </div>
6603
+ <div className="setup-button-row">
6478
6604
  <button
6479
6605
  type="button"
6480
- className="rewind-excluded-app-remove"
6481
- aria-label={`Remove ${app.name}`}
6482
- disabled={screenMemoryConfigBusy}
6483
- onClick={() =>
6484
- removeExcludedApplications(app.bundleIds)
6485
- }
6606
+ className="secondary"
6607
+ onClick={onCopyRewindAgentPrompt}
6486
6608
  >
6487
- <IconX size={14} stroke={2} />
6609
+ <IconCopy size={14} stroke={1.9} />
6610
+ {rewindAgentPromptCopied
6611
+ ? "Setup prompt copied"
6612
+ : "Copy setup prompt"}
6613
+ </button>
6614
+ <button
6615
+ type="button"
6616
+ className="secondary"
6617
+ onClick={onOpenRewindDocs}
6618
+ >
6619
+ <IconExternalLink size={14} stroke={1.9} />
6620
+ Learn about Rewind
6488
6621
  </button>
6489
6622
  </div>
6490
- ))}
6491
- </div>
6492
- ) : (
6493
- <p className="setup-hint">No additional apps are excluded.</p>
6494
- )}
6495
- </div>
6496
- <div className="setup-section-heading">Agent handoff</div>
6497
- <div className="rewind-agent-guide">
6498
- <div className="rewind-agent-guide-icon">
6499
- <IconHistory size={17} stroke={1.8} />
6500
- </div>
6501
- <div>
6502
- <strong>Set up your agent once</strong>
6503
- <p>
6504
- Copy the setup prompt into any compatible agent. It installs
6505
- Rewind's reusable instructions and repairs the local
6506
- connection, so later you can simply say “Look at Rewind.”
6507
- </p>
6508
- <div className="setup-button-row">
6509
- <button
6510
- type="button"
6511
- className="secondary"
6512
- onClick={onCopyRewindAgentPrompt}
6513
- >
6514
- <IconCopy size={14} stroke={1.9} />
6515
- {rewindAgentPromptCopied
6516
- ? "Setup prompt copied"
6517
- : "Copy setup prompt"}
6518
- </button>
6519
- <button
6520
- type="button"
6521
- className="secondary"
6522
- onClick={onOpenRewindDocs}
6523
- >
6524
- <IconExternalLink size={14} stroke={1.9} />
6525
- Learn about Rewind
6526
- </button>
6527
- </div>
6528
- </div>
6529
- </div>
6530
- <details className="setup-advanced rewind-agent-repair">
6531
- <summary className="setup-advanced-summary">
6532
- Repair an agent connection
6533
- </summary>
6534
- <div className="setup-advanced-body">
6535
- <p className="setup-hint">
6536
- Usually your agent can install Rewind from the copied prompt.
6537
- These buttons are a manual repair for known clients.
6538
- </p>
6539
- <div className="rewind-memory-actions">
6540
- <button
6541
- type="button"
6542
- className="secondary"
6543
- disabled={agentConnectionBusy !== null}
6544
- onClick={() => void installRewindAgentConnection("codex")}
6545
- >
6546
- {agentConnectionBusy === "codex"
6547
- ? "Repairing…"
6548
- : "Repair Codex connection"}
6549
- </button>
6550
- <button
6551
- type="button"
6552
- className="secondary"
6553
- disabled={agentConnectionBusy !== null}
6554
- onClick={() =>
6555
- void installRewindAgentConnection("claude-code")
6556
- }
6557
- >
6558
- {agentConnectionBusy === "claude-code"
6559
- ? "Repairing…"
6560
- : "Repair Claude Code connection"}
6561
- </button>
6562
- </div>
6563
- {agentConnectionMessage ? (
6564
- <p
6565
- className={
6566
- agentConnectionMessage.kind === "error"
6567
- ? "setup-error"
6568
- : "setup-hint"
6569
- }
6570
- role="status"
6571
- >
6572
- {agentConnectionMessage.text}
6573
- </p>
6574
- ) : null}
6575
- </div>
6576
- </details>
6577
- <div className="rewind-setting-row rewind-agent-row">
6578
- <SettingLabel
6579
- label="Review before sending"
6580
- hint="Preview and trim any visual or audio range before it becomes a private Clip."
6581
- />
6582
- <Switch
6583
- on={screenMemory.reviewBeforeSending}
6584
- disabled={screenMemoryConfigBusy}
6585
- onChange={(enabled) =>
6586
- void setScreenMemoryConfig({
6587
- reviewBeforeSending: enabled,
6588
- })
6589
- }
6590
- label="Review visual and audio ranges before sending"
6591
- />
6592
- </div>
6593
- <div className="rewind-setting-row rewind-agent-row">
6594
- <SettingLabel
6595
- label="Open local preview automatically"
6596
- hint="When review is on, prepare the selected range in QuickTime when an agent asks for it."
6597
- />
6598
- <Switch
6599
- on={screenMemory.autoPreviewBeforeSending}
6600
- disabled={
6601
- screenMemoryConfigBusy || !screenMemory.reviewBeforeSending
6602
- }
6603
- onChange={(enabled) =>
6604
- void setScreenMemoryConfig({
6605
- autoPreviewBeforeSending: enabled,
6606
- })
6607
- }
6608
- label="Open a local preview automatically before sending"
6609
- />
6610
- </div>
6611
- <p className="rewind-boundary-note">
6612
- Asking your agent authorizes bounded matching text. Raw Rewind
6613
- files stay local. If this review is off, an agent-requested media
6614
- range becomes a private Clip immediately and leaves a receipt.
6615
- </p>
6616
- <div className="rewind-setting-row">
6617
- <SettingLabel
6618
- label="Agent-created Clip retention"
6619
- hint="Applies to future private Clips created for an agent."
6620
- htmlFor="rewind-agent-clip-retention"
6621
- />
6622
- <select
6623
- id="rewind-agent-clip-retention"
6624
- className="setup-select rewind-setting-control"
6625
- disabled={screenMemoryConfigBusy}
6626
- value={screenMemory.agentClipRetention}
6627
- onChange={(event) =>
6628
- void setScreenMemoryConfig({
6629
- agentClipRetention: event.target.value as
6630
- | "forever"
6631
- | "24-hours"
6632
- | "7-days"
6633
- | "30-days",
6634
- })
6635
- }
6636
- >
6637
- <option value="forever">Keep forever</option>
6638
- <option value="24-hours">Delete after 24 hours</option>
6639
- <option value="7-days">Delete after 7 days</option>
6640
- <option value="30-days">Delete after 30 days</option>
6641
- </select>
6642
- </div>
6643
- <div className="rewind-agent-guide">
6644
- <div className="rewind-agent-guide-icon">
6645
- <IconHistory size={17} stroke={1.8} />
6646
- </div>
6647
- <div>
6648
- <strong>Ask your agent</strong>
6649
- <p>
6650
- Try “What was the Terminal error?” or “Replay Tuesday’s design
6651
- review.”
6652
- </p>
6653
- <button
6654
- type="button"
6655
- className="rewind-text-button"
6656
- onClick={onOpenMemory}
6657
- >
6658
- Search manually
6659
- </button>
6660
- </div>
6661
- </div>
6662
- <details
6663
- className="setup-advanced"
6664
- open={rewindEgressOpen}
6665
- onToggle={(event) => {
6666
- const open = event.currentTarget.open;
6667
- setRewindEgressOpen(open);
6668
- if (open) refreshRewindEgressLog();
6669
- }}
6670
- >
6671
- <summary className="setup-advanced-summary">
6672
- Agent activity
6673
- </summary>
6674
- <div className="setup-advanced-body">
6675
- <p className="setup-hint">
6676
- See when an agent searched bounded local evidence. Raw Rewind
6677
- media remains on this Mac unless you explicitly create a
6678
- private Clip.
6679
- </p>
6680
- {rewindEgressEvents.length === 0 ? (
6681
- <p className="setup-hint">No matching-text requests yet.</p>
6682
- ) : (
6683
- rewindEgressEvents.slice(0, 10).map((event) => (
6684
- <p
6685
- className="setup-hint"
6686
- key={`${event.requestId}-${event.state}`}
6687
- >
6688
- <strong>
6689
- {new Date(event.occurredAt).toLocaleString()}
6690
- </strong>
6691
- {` · ${event.state} · ${event.evidenceCount} item${event.evidenceCount === 1 ? "" : "s"}`}
6692
- </p>
6693
- ))
6694
- )}
6695
- </div>
6696
- </details>
6697
- <details className="setup-advanced">
6698
- <summary className="setup-advanced-summary">
6699
- Manage local memory
6700
- </summary>
6701
- <div className="setup-advanced-body">
6702
- <div className="rewind-memory-actions">
6703
- <div className="rewind-memory-action">
6704
- <div className="rewind-memory-action-copy">
6705
- <strong>Save a local Clip</strong>
6706
- <p>
6707
- Export the previous five minutes as a video on this Mac.
6708
- Nothing is uploaded.
6709
- </p>
6710
- {screenMemoryExportResult ? (
6711
- <div className="rewind-inline-receipt" role="status">
6712
- <span>Saved locally</span>
6623
+ <details className="setup-advanced rewind-agent-repair">
6624
+ <summary className="setup-advanced-summary">
6625
+ Repair an agent connection
6626
+ </summary>
6627
+ <div className="setup-advanced-body">
6628
+ <p className="setup-hint">
6629
+ Use this only when the copied setup prompt cannot
6630
+ repair a known client.
6631
+ </p>
6632
+ <div className="rewind-memory-actions">
6713
6633
  <button
6714
6634
  type="button"
6715
- className="rewind-text-button"
6635
+ className="secondary"
6636
+ disabled={agentConnectionBusy !== null}
6716
6637
  onClick={() =>
6717
- void invoke("open_local_recording_folder", {
6718
- path: screenMemoryExportResult.folderPath,
6719
- })
6638
+ void installRewindAgentConnection("codex")
6720
6639
  }
6721
6640
  >
6722
- Show in Finder
6641
+ {agentConnectionBusy === "codex"
6642
+ ? "Repairing…"
6643
+ : "Repair Codex connection"}
6644
+ </button>
6645
+ <button
6646
+ type="button"
6647
+ className="secondary"
6648
+ disabled={agentConnectionBusy !== null}
6649
+ onClick={() =>
6650
+ void installRewindAgentConnection("claude-code")
6651
+ }
6652
+ >
6653
+ {agentConnectionBusy === "claude-code"
6654
+ ? "Repairing…"
6655
+ : "Repair Claude Code connection"}
6723
6656
  </button>
6724
6657
  </div>
6725
- ) : null}
6726
- </div>
6658
+ {agentConnectionMessage ? (
6659
+ <p
6660
+ className={
6661
+ agentConnectionMessage.kind === "error"
6662
+ ? "setup-error"
6663
+ : "setup-hint"
6664
+ }
6665
+ role="status"
6666
+ >
6667
+ {agentConnectionMessage.text}
6668
+ </p>
6669
+ ) : null}
6670
+ </div>
6671
+ </details>
6672
+ </div>
6673
+ </DesktopSettingsPopover>
6674
+ </div>
6675
+ <div className="rewind-setting-row rewind-setting-row--action">
6676
+ <div className="rewind-setting-copy">
6677
+ <SettingLabel
6678
+ label="Before sending"
6679
+ hint="Control how an agent-created private Clip is prepared."
6680
+ />
6681
+ <span>
6682
+ {screenMemory.reviewBeforeSending
6683
+ ? "Review media before sending"
6684
+ : "Send private Clips immediately"}
6685
+ </span>
6686
+ </div>
6687
+ <DesktopSettingsPopover
6688
+ title="Before sending"
6689
+ open={rewindHandoffOpen}
6690
+ onOpenChange={setRewindHandoffOpen}
6691
+ side="bottom"
6692
+ contentClassName="rewind-popover-content"
6693
+ trigger={
6727
6694
  <button
6728
6695
  type="button"
6729
- className="secondary"
6730
- disabled={
6731
- screenMemoryBusy || screenMemorySegments.length === 0
6732
- }
6733
- onClick={exportScreenMemoryRecent}
6696
+ className="secondary rewind-manage-button"
6734
6697
  >
6735
- <IconDownload size={15} stroke={1.9} /> Save previous 5
6736
- minutes
6698
+ Manage
6737
6699
  </button>
6738
- </div>
6739
- <div className="rewind-memory-action">
6740
- <div>
6741
- <strong>View Rewind files</strong>
6742
- <p>
6743
- Open the private folder where Rewind keeps its temporary
6744
- local memory.
6745
- </p>
6700
+ }
6701
+ >
6702
+ <div className="rewind-popover-stack">
6703
+ <div className="rewind-popover-setting-row">
6704
+ <div className="rewind-setting-copy">
6705
+ <strong>Review before sending</strong>
6706
+ <span>
6707
+ Preview and trim a visual or audio range before it
6708
+ becomes a private Clip.
6709
+ </span>
6710
+ </div>
6711
+ <Switch
6712
+ on={screenMemory.reviewBeforeSending}
6713
+ disabled={screenMemoryConfigBusy}
6714
+ onChange={(enabled) =>
6715
+ void setScreenMemoryConfig({
6716
+ reviewBeforeSending: enabled,
6717
+ })
6718
+ }
6719
+ label="Review visual and audio ranges before sending"
6720
+ />
6721
+ </div>
6722
+ <div className="rewind-popover-setting-row">
6723
+ <div className="rewind-setting-copy">
6724
+ <strong>Open local preview automatically</strong>
6725
+ <span>
6726
+ Prepare the selected range in QuickTime when an agent
6727
+ asks for it.
6728
+ </span>
6729
+ </div>
6730
+ <Switch
6731
+ on={screenMemory.autoPreviewBeforeSending}
6732
+ disabled={
6733
+ screenMemoryConfigBusy ||
6734
+ !screenMemory.reviewBeforeSending
6735
+ }
6736
+ onChange={(enabled) =>
6737
+ void setScreenMemoryConfig({
6738
+ autoPreviewBeforeSending: enabled,
6739
+ })
6740
+ }
6741
+ label="Open a local preview automatically before sending"
6742
+ />
6743
+ </div>
6744
+ <div className="rewind-popover-setting-row">
6745
+ <div className="rewind-setting-copy">
6746
+ <strong>Agent-created Clip retention</strong>
6747
+ <span>Applies to future private Clips.</span>
6748
+ </div>
6749
+ <RewindChoicePopover
6750
+ title="Agent-created Clip retention"
6751
+ value={screenMemory.agentClipRetention}
6752
+ options={[
6753
+ { value: "forever", label: "Keep forever" },
6754
+ {
6755
+ value: "24-hours",
6756
+ label: "Delete after 24 hours",
6757
+ },
6758
+ { value: "7-days", label: "Delete after 7 days" },
6759
+ {
6760
+ value: "30-days",
6761
+ label: "Delete after 30 days",
6762
+ },
6763
+ ]}
6764
+ disabled={screenMemoryConfigBusy}
6765
+ onChange={(value) =>
6766
+ void setScreenMemoryConfig({
6767
+ agentClipRetention: value as
6768
+ | "forever"
6769
+ | "24-hours"
6770
+ | "7-days"
6771
+ | "30-days",
6772
+ })
6773
+ }
6774
+ />
6746
6775
  </div>
6776
+ <p className="rewind-boundary-note">
6777
+ Raw Rewind files stay local. If review is off, an
6778
+ agent-requested media range becomes a private Clip
6779
+ immediately.
6780
+ </p>
6781
+ </div>
6782
+ </DesktopSettingsPopover>
6783
+ </div>
6784
+ <div className="rewind-setting-row rewind-setting-row--action">
6785
+ <div className="rewind-setting-copy">
6786
+ <SettingLabel
6787
+ label="Agent activity"
6788
+ hint="Review when an agent searched bounded local evidence."
6789
+ />
6790
+ <span>
6791
+ {rewindEgressEvents.length === 0
6792
+ ? "No matching-text requests yet"
6793
+ : `${rewindEgressEvents.length} recent request${rewindEgressEvents.length === 1 ? "" : "s"}`}
6794
+ </span>
6795
+ </div>
6796
+ <DesktopSettingsPopover
6797
+ title="Agent activity"
6798
+ open={rewindActivityOpen}
6799
+ onOpenChange={(open) => {
6800
+ setRewindActivityOpen(open);
6801
+ if (open) refreshRewindEgressLog();
6802
+ }}
6803
+ side="bottom"
6804
+ contentClassName="rewind-popover-content"
6805
+ trigger={
6747
6806
  <button
6748
6807
  type="button"
6749
- className="secondary"
6750
- onClick={openScreenMemoryFolder}
6808
+ className="secondary rewind-manage-button"
6751
6809
  >
6752
- <IconFolderOpen size={15} stroke={1.9} /> Open Rewind
6753
- folder
6810
+ View
6754
6811
  </button>
6755
- </div>
6756
- <div className="rewind-memory-action is-danger">
6757
- <div>
6758
- <strong>Erase Rewind memory</strong>
6759
- <p>
6760
- Permanently delete all retained media and indexes from
6761
- this Mac.
6812
+ }
6813
+ >
6814
+ <div className="rewind-popover-stack">
6815
+ <p className="rewind-popover-hint">
6816
+ Raw Rewind media remains on this Mac unless you explicitly
6817
+ create a private Clip.
6818
+ </p>
6819
+ {rewindEgressEvents.length === 0 ? (
6820
+ <p className="setup-hint">
6821
+ No matching-text requests yet.
6762
6822
  </p>
6763
- </div>
6823
+ ) : (
6824
+ rewindEgressEvents.slice(0, 10).map((event) => (
6825
+ <p
6826
+ className="setup-hint"
6827
+ key={`${event.requestId}-${event.state}`}
6828
+ >
6829
+ <strong>
6830
+ {new Date(event.occurredAt).toLocaleString()}
6831
+ </strong>
6832
+ {` · ${event.state} · ${event.evidenceCount} item${event.evidenceCount === 1 ? "" : "s"}`}
6833
+ </p>
6834
+ ))
6835
+ )}
6836
+ </div>
6837
+ </DesktopSettingsPopover>
6838
+ </div>
6839
+ <div className="rewind-setting-row rewind-setting-row--action">
6840
+ <div className="rewind-setting-copy">
6841
+ <SettingLabel
6842
+ label="Manual search"
6843
+ hint="Search retained local evidence without asking an agent."
6844
+ />
6845
+ <span>Find and replay a source moment</span>
6846
+ </div>
6847
+ <DesktopSettingsPopover
6848
+ title="Manual search"
6849
+ open={rewindSearchOpen}
6850
+ onOpenChange={setRewindSearchOpen}
6851
+ side="bottom"
6852
+ contentClassName="rewind-memory-launcher"
6853
+ trigger={
6764
6854
  <button
6765
6855
  type="button"
6766
- className="secondary rewind-danger-button"
6767
- disabled={
6768
- screenMemoryBusy || screenMemorySegments.length === 0
6769
- }
6770
- onClick={clearScreenMemory}
6856
+ className="secondary rewind-manage-button"
6771
6857
  >
6772
- <IconTrash size={15} stroke={1.9} /> Erase all memory…
6858
+ Search
6773
6859
  </button>
6774
- </div>
6860
+ }
6861
+ >
6862
+ <Setup
6863
+ surface="memory"
6864
+ recordingActive={recordingActive}
6865
+ promptRewindEnable={false}
6866
+ initial={initial}
6867
+ serverUrl={serverUrl}
6868
+ signedInAs={signedInAs}
6869
+ voiceShortcut={voiceShortcut}
6870
+ voiceCustomShortcut={voiceCustomShortcut}
6871
+ popoverCustomShortcut={popoverCustomShortcut}
6872
+ recordCustomShortcut={recordCustomShortcut}
6873
+ voiceMode={voiceMode}
6874
+ voiceProvider={voiceProvider}
6875
+ voiceInstructions={voiceInstructions}
6876
+ shortcutRegistrationError={shortcutRegistrationError}
6877
+ onVoiceShortcutChange={onVoiceShortcutChange}
6878
+ onVoiceCustomShortcutChange={onVoiceCustomShortcutChange}
6879
+ onPopoverCustomShortcutChange={
6880
+ onPopoverCustomShortcutChange
6881
+ }
6882
+ onRecordCustomShortcutChange={onRecordCustomShortcutChange}
6883
+ onVoiceModeChange={onVoiceModeChange}
6884
+ onVoiceProviderChange={onVoiceProviderChange}
6885
+ onVoiceInstructionsChange={onVoiceInstructionsChange}
6886
+ onConnect={onConnect}
6887
+ rewindAgentPromptCopied={rewindAgentPromptCopied}
6888
+ onCopyRewindAgentPrompt={onCopyRewindAgentPrompt}
6889
+ onOpenRewindDocs={onOpenRewindDocs}
6890
+ onCancel={() => setRewindSearchOpen(false)}
6891
+ />
6892
+ </DesktopSettingsPopover>
6893
+ </div>
6894
+ <div className="rewind-setting-row rewind-setting-row--action">
6895
+ <div className="rewind-setting-copy">
6896
+ <SettingLabel
6897
+ label="Local memory"
6898
+ hint="Save, open, or erase the private Rewind archive on this Mac."
6899
+ />
6900
+ <span>
6901
+ {screenMemorySegments.length} retained segment
6902
+ {screenMemorySegments.length === 1 ? "" : "s"} ·{" "}
6903
+ {storageLabel}
6904
+ </span>
6775
6905
  </div>
6906
+ <DesktopSettingsPopover
6907
+ title="Local memory"
6908
+ open={rewindMemoryOpen}
6909
+ onOpenChange={setRewindMemoryOpen}
6910
+ side="bottom"
6911
+ contentClassName="rewind-popover-content"
6912
+ trigger={
6913
+ <button
6914
+ type="button"
6915
+ className="secondary rewind-manage-button"
6916
+ >
6917
+ Manage
6918
+ </button>
6919
+ }
6920
+ >
6921
+ <div className="rewind-popover-stack">
6922
+ <div className="rewind-memory-actions">
6923
+ <div className="rewind-memory-action">
6924
+ <div className="rewind-memory-action-copy">
6925
+ <strong>Save a local Clip</strong>
6926
+ <p>
6927
+ Export the previous five minutes as a video on this
6928
+ Mac. Nothing is uploaded.
6929
+ </p>
6930
+ {screenMemoryExportResult ? (
6931
+ <div
6932
+ className="rewind-inline-receipt"
6933
+ role="status"
6934
+ >
6935
+ <span>Saved locally</span>
6936
+ <button
6937
+ type="button"
6938
+ className="rewind-text-button"
6939
+ onClick={() =>
6940
+ void invoke("open_local_recording_folder", {
6941
+ path: screenMemoryExportResult.folderPath,
6942
+ })
6943
+ }
6944
+ >
6945
+ Show in Finder
6946
+ </button>
6947
+ </div>
6948
+ ) : null}
6949
+ </div>
6950
+ <button
6951
+ type="button"
6952
+ className="secondary"
6953
+ disabled={
6954
+ screenMemoryBusy ||
6955
+ screenMemorySegments.length === 0
6956
+ }
6957
+ onClick={exportScreenMemoryRecent}
6958
+ >
6959
+ <IconDownload size={15} stroke={1.9} /> Save previous
6960
+ 5 minutes
6961
+ </button>
6962
+ </div>
6963
+ <div className="rewind-memory-action">
6964
+ <div>
6965
+ <strong>View Rewind files</strong>
6966
+ <p>Open the private folder for local memory.</p>
6967
+ </div>
6968
+ <button
6969
+ type="button"
6970
+ className="secondary"
6971
+ onClick={openScreenMemoryFolder}
6972
+ >
6973
+ <IconFolderOpen size={15} stroke={1.9} /> Open folder
6974
+ </button>
6975
+ </div>
6976
+ <div className="rewind-memory-action is-danger">
6977
+ <div>
6978
+ <strong>Erase Rewind memory</strong>
6979
+ <p>Permanently delete retained media and indexes.</p>
6980
+ </div>
6981
+ <button
6982
+ type="button"
6983
+ className="secondary rewind-danger-button"
6984
+ disabled={
6985
+ screenMemoryBusy ||
6986
+ screenMemorySegments.length === 0
6987
+ }
6988
+ onClick={clearScreenMemory}
6989
+ >
6990
+ <IconTrash size={15} stroke={1.9} /> Erase all memory…
6991
+ </button>
6992
+ </div>
6993
+ </div>
6994
+ {screenMemoryMessage ? (
6995
+ <p
6996
+ className={
6997
+ screenMemoryMessage.kind === "ok"
6998
+ ? "setup-success"
6999
+ : "setup-warning"
7000
+ }
7001
+ >
7002
+ {screenMemoryMessage.text}
7003
+ </p>
7004
+ ) : null}
7005
+ </div>
7006
+ </DesktopSettingsPopover>
6776
7007
  </div>
6777
- </details>
6778
- {screenMemoryMessage ? (
6779
- <p
6780
- className={
6781
- screenMemoryMessage.kind === "ok"
6782
- ? "setup-success"
6783
- : "setup-warning"
6784
- }
6785
- >
6786
- {screenMemoryMessage.text}
6787
- </p>
6788
- ) : null}
7008
+ </div>
6789
7009
  </>
6790
7010
  ) : (
6791
7011
  <div className="popover-empty-card rewind-memory-empty">
@@ -6804,6 +7024,17 @@ function Setup({
6804
7024
  </button>
6805
7025
  </div>
6806
7026
  )}
7027
+ {screenMemoryMessage && screenMemory.enabled ? (
7028
+ <p
7029
+ className={
7030
+ screenMemoryMessage.kind === "ok"
7031
+ ? "setup-success"
7032
+ : "setup-warning"
7033
+ }
7034
+ >
7035
+ {screenMemoryMessage.text}
7036
+ </p>
7037
+ ) : null}
6807
7038
  </div>
6808
7039
  );
6809
7040
  }
@@ -6994,13 +7225,47 @@ function Setup({
6994
7225
  label="Rewind"
6995
7226
  description={rewindStatusPresentation.title}
6996
7227
  control={
6997
- <button
6998
- type="button"
6999
- className="secondary"
7000
- onClick={() => onOpenRewind?.()}
7228
+ <DesktopSettingsPopover
7229
+ title="Rewind settings"
7230
+ open={rewindSettingsOpen}
7231
+ onOpenChange={setRewindSettingsOpen}
7232
+ side="left"
7233
+ contentClassName="rewind-settings-launcher"
7234
+ trigger={
7235
+ <button type="button" className="secondary">
7236
+ Manage
7237
+ </button>
7238
+ }
7001
7239
  >
7002
- Open Rewind settings
7003
- </button>
7240
+ <Setup
7241
+ surface="rewind"
7242
+ recordingActive={recordingActive}
7243
+ promptRewindEnable={false}
7244
+ initial={initial}
7245
+ serverUrl={serverUrl}
7246
+ signedInAs={signedInAs}
7247
+ voiceShortcut={voiceShortcut}
7248
+ voiceCustomShortcut={voiceCustomShortcut}
7249
+ popoverCustomShortcut={popoverCustomShortcut}
7250
+ recordCustomShortcut={recordCustomShortcut}
7251
+ voiceMode={voiceMode}
7252
+ voiceProvider={voiceProvider}
7253
+ voiceInstructions={voiceInstructions}
7254
+ shortcutRegistrationError={shortcutRegistrationError}
7255
+ onVoiceShortcutChange={onVoiceShortcutChange}
7256
+ onVoiceCustomShortcutChange={onVoiceCustomShortcutChange}
7257
+ onPopoverCustomShortcutChange={onPopoverCustomShortcutChange}
7258
+ onRecordCustomShortcutChange={onRecordCustomShortcutChange}
7259
+ onVoiceModeChange={onVoiceModeChange}
7260
+ onVoiceProviderChange={onVoiceProviderChange}
7261
+ onVoiceInstructionsChange={onVoiceInstructionsChange}
7262
+ onConnect={onConnect}
7263
+ rewindAgentPromptCopied={rewindAgentPromptCopied}
7264
+ onCopyRewindAgentPrompt={onCopyRewindAgentPrompt}
7265
+ onOpenRewindDocs={onOpenRewindDocs}
7266
+ onCancel={() => setRewindSettingsOpen(false)}
7267
+ />
7268
+ </DesktopSettingsPopover>
7004
7269
  }
7005
7270
  />
7006
7271
  <DesktopSettingsRow
@@ -7600,6 +7865,117 @@ function Setup({
7600
7865
  );
7601
7866
  }
7602
7867
 
7868
+ function DesktopSettingsPopover({
7869
+ title,
7870
+ trigger,
7871
+ children,
7872
+ open,
7873
+ onOpenChange,
7874
+ side = "left",
7875
+ contentClassName,
7876
+ }: {
7877
+ title: string;
7878
+ trigger: ReactElement;
7879
+ children: ReactNode;
7880
+ open: boolean;
7881
+ onOpenChange: (open: boolean) => void;
7882
+ side?: "bottom" | "left" | "right" | "top";
7883
+ contentClassName?: string;
7884
+ }) {
7885
+ return (
7886
+ <PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
7887
+ <PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
7888
+ <PopoverPrimitive.Content
7889
+ side={side}
7890
+ align="start"
7891
+ sideOffset={10}
7892
+ collisionPadding={12}
7893
+ className={`desktop-settings-popover ${contentClassName ?? ""}`}
7894
+ data-popover-overlay="true"
7895
+ >
7896
+ <div className="desktop-settings-popover-header">
7897
+ <div
7898
+ className="desktop-settings-popover-title"
7899
+ role="heading"
7900
+ aria-level={2}
7901
+ >
7902
+ {title}
7903
+ </div>
7904
+ <PopoverPrimitive.Close
7905
+ type="button"
7906
+ className="desktop-settings-popover-close"
7907
+ aria-label={`Close ${title}`}
7908
+ >
7909
+ <IconX size={15} stroke={1.9} />
7910
+ </PopoverPrimitive.Close>
7911
+ </div>
7912
+ {children}
7913
+ </PopoverPrimitive.Content>
7914
+ </PopoverPrimitive.Root>
7915
+ );
7916
+ }
7917
+
7918
+ function RewindChoicePopover({
7919
+ title,
7920
+ value,
7921
+ options,
7922
+ onChange,
7923
+ disabled = false,
7924
+ }: {
7925
+ title: string;
7926
+ value: string;
7927
+ options: Array<{ value: string; label: string }>;
7928
+ onChange: (value: string) => void;
7929
+ disabled?: boolean;
7930
+ }) {
7931
+ const [open, setOpen] = useState(false);
7932
+ const selectedOption =
7933
+ options.find((option) => option.value === value)?.label ?? value;
7934
+
7935
+ return (
7936
+ <DesktopSettingsPopover
7937
+ title={title}
7938
+ open={open}
7939
+ onOpenChange={setOpen}
7940
+ side="bottom"
7941
+ trigger={
7942
+ <button
7943
+ type="button"
7944
+ className="rewind-setting-trigger"
7945
+ disabled={disabled}
7946
+ aria-haspopup="listbox"
7947
+ aria-expanded={open}
7948
+ >
7949
+ <span>{selectedOption}</span>
7950
+ <IconChevronDown size={15} stroke={1.8} aria-hidden="true" />
7951
+ </button>
7952
+ }
7953
+ >
7954
+ <div className="rewind-choice-list" role="listbox" aria-label={title}>
7955
+ {options.map((option) => {
7956
+ const selected = option.value === value;
7957
+ return (
7958
+ <button
7959
+ type="button"
7960
+ role="option"
7961
+ aria-selected={selected}
7962
+ className={`rewind-choice-option ${selected ? "is-selected" : ""}`}
7963
+ key={option.value}
7964
+ onClick={() => {
7965
+ onChange(option.value);
7966
+ setOpen(false);
7967
+ }}
7968
+ >
7969
+ <span>{option.label}</span>
7970
+ {selected ? <IconCheck size={15} stroke={2.1} /> : null}
7971
+ </button>
7972
+ );
7973
+ })}
7974
+ </div>
7975
+ </DesktopSettingsPopover>
7976
+ );
7977
+ }
7978
+
7603
7979
  function DesktopSettingsGroup({
7604
7980
  title,
7605
7981
  description,