@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.
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +21 -19
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -2
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +3 -2
- package/corpus/templates/calendar/app/lib/event-layout.ts +16 -35
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +27 -55
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +104 -66
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +45 -43
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -1
- package/corpus/templates/clips/desktop/src/app.tsx +818 -442
- package/corpus/templates/clips/desktop/src/styles.css +244 -2
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +584 -1
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +25 -0
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +1 -1
- package/corpus/templates/factory/app/routes/factory.tsx +0 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +28 -25
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +50 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +12 -60
- package/dist/agent/production-agent.d.ts +8 -0
- package/dist/agent/production-agent.js +25 -2
- package/dist/agent/run-manager.js +13 -3
- package/dist/client/AgentPanel.js +6 -4
- package/dist/client/org/WorkspaceNotice.js +7 -4
- package/dist/client/sharing/ShareButton.d.ts +2 -2
- package/dist/client/sharing/ShareButton.js +18 -13
- package/dist/client/sharing/ShareDialog.d.ts +1 -1
- package/dist/client/sharing/ShareDialog.js +16 -11
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/auth.js +18 -4
- package/dist/server/better-auth-instance.js +8 -1
- package/dist/server/magic-link-attribution.d.ts +21 -0
- package/dist/server/magic-link-attribution.js +115 -0
- package/dist/server/realtime-token.d.ts +1 -1
- 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 :
|
|
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-
|
|
6331
|
-
<
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
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-
|
|
6442
|
-
<div className="rewind-
|
|
6443
|
-
<
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
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
|
-
<
|
|
6454
|
-
|
|
6455
|
-
|
|
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
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
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
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
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
|
|
6471
|
-
<strong>
|
|
6472
|
-
<
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
</
|
|
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="
|
|
6481
|
-
|
|
6482
|
-
disabled={screenMemoryConfigBusy}
|
|
6483
|
-
onClick={() =>
|
|
6484
|
-
removeExcludedApplications(app.bundleIds)
|
|
6485
|
-
}
|
|
6606
|
+
className="secondary"
|
|
6607
|
+
onClick={onCopyRewindAgentPrompt}
|
|
6486
6608
|
>
|
|
6487
|
-
<
|
|
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
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
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="
|
|
6635
|
+
className="secondary"
|
|
6636
|
+
disabled={agentConnectionBusy !== null}
|
|
6716
6637
|
onClick={() =>
|
|
6717
|
-
void
|
|
6718
|
-
path: screenMemoryExportResult.folderPath,
|
|
6719
|
-
})
|
|
6638
|
+
void installRewindAgentConnection("codex")
|
|
6720
6639
|
}
|
|
6721
6640
|
>
|
|
6722
|
-
|
|
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
|
-
|
|
6726
|
-
|
|
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
|
-
|
|
6736
|
-
minutes
|
|
6698
|
+
Manage
|
|
6737
6699
|
</button>
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
<
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
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
|
-
|
|
6753
|
-
folder
|
|
6810
|
+
View
|
|
6754
6811
|
</button>
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
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
|
-
|
|
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-
|
|
6767
|
-
disabled={
|
|
6768
|
-
screenMemoryBusy || screenMemorySegments.length === 0
|
|
6769
|
-
}
|
|
6770
|
-
onClick={clearScreenMemory}
|
|
6856
|
+
className="secondary rewind-manage-button"
|
|
6771
6857
|
>
|
|
6772
|
-
|
|
6858
|
+
Search
|
|
6773
6859
|
</button>
|
|
6774
|
-
|
|
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
|
-
</
|
|
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
|
-
<
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
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
|
-
|
|
7003
|
-
|
|
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,
|