@agent-native/core 0.178.0 → 0.178.1-nightly-20260909221943
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +1 -0
- package/corpus/templates/clips/DESIGN.md +10 -7
- package/corpus/templates/clips/actions/list-organization-state.ts +56 -3
- package/corpus/templates/clips/actions/list-recordings.ts +2 -0
- package/corpus/templates/clips/actions/search-dictations.ts +81 -0
- package/corpus/templates/clips/app/components/agent-destination-logos.tsx +50 -0
- package/corpus/templates/clips/app/components/bug-report/bug-report-dialog.tsx +60 -0
- package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +206 -0
- package/corpus/templates/clips/app/components/clips-command-menu.tsx +653 -0
- package/corpus/templates/clips/app/components/library/empty-state.tsx +52 -17
- package/corpus/templates/clips/app/components/library/folder-card.tsx +34 -0
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +109 -64
- package/corpus/templates/clips/app/components/library/library-action-hrefs.ts +27 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +295 -103
- package/corpus/templates/clips/app/components/library/library-layout.tsx +381 -203
- package/corpus/templates/clips/app/components/library/library-primary-actions.tsx +48 -0
- package/corpus/templates/clips/app/components/library/page-header.tsx +48 -8
- package/corpus/templates/clips/app/components/library/recording-card.tsx +58 -55
- package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -19
- package/corpus/templates/clips/app/components/library/sidebar-feedback-button.tsx +48 -0
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -2
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +344 -260
- package/corpus/templates/clips/app/components/player/viewer-controls.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +0 -33
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -1
- package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -1
- package/corpus/templates/clips/app/hooks/use-command-search.ts +53 -0
- package/corpus/templates/clips/app/hooks/use-navigation-state.ts +32 -13
- package/corpus/templates/clips/app/hooks/use-upload-video-picker.tsx +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
- package/corpus/templates/clips/app/lib/agent-share.ts +16 -0
- package/corpus/templates/clips/app/lib/command-events.ts +7 -0
- package/corpus/templates/clips/app/root.tsx +6 -82
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +25 -5
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +2 -30
- package/corpus/templates/clips/app/routes/_app.library.folder.$folderId.tsx +13 -2
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +4 -17
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +111 -114
- package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.folder.$folderId.tsx +25 -2
- package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.tsx +14 -61
- package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +34 -24
- package/corpus/templates/clips/app/routes/_app.trash.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.tsx +2 -4
- package/corpus/templates/clips/app/routes/bug-report.tsx +11 -189
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +148 -123
- package/corpus/templates/clips/changelog/2026-09-08-comments-and-desktop-window-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-08-global-command-search.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-agent-sidebar-fits-below-toolbar.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-desktop-switches-stay-clear-in-dark-mode.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-09-recording-agent-uses-global-panel.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +2 -2
- package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +5 -3
- package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
- package/corpus/templates/clips/desktop/src/components/Switch.tsx +2 -5
- package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +21 -3
- package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +72 -52
- package/corpus/templates/clips/desktop/src/styles.css +18 -19
- package/corpus/templates/clips/desktop/src/tailwind.css +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +18 -10
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +3 -5
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -3
- package/corpus/templates/design/AGENTS.md +2 -1
- package/corpus/templates/design/actions/export-png.ts +119 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +47 -13
- package/corpus/templates/design/server/plugins/agent-chat.ts +3 -1
- package/corpus/templates/factory/actions/dispatch-factory-item.ts +51 -22
- package/corpus/templates/factory/actions/list-triage-items.ts +21 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +7 -2
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-keeps-claimed-markers-after-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-preserves-started-claimed-threads.md +6 -0
- package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-skips-claimed-parents.md +6 -0
- package/corpus/templates/factory/server/lib/factory-automation-config.ts +1 -1
- package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +47 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +5 -4
- package/corpus/templates/factory/server/triage/review-skill-alignment.ts +12 -7
- package/corpus/templates/factory/server/triage/slack-review-window.ts +39 -0
- package/dist/client/CommandMenu.js +13 -1
- package/dist/client/analytics.js +10 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/vite/client.js +25 -7
- package/dist/vite/sentry-source-maps.d.ts +12 -0
- package/dist/vite/sentry-source-maps.js +105 -0
- package/package.json +2 -1
|
@@ -18,9 +18,7 @@ import {
|
|
|
18
18
|
IconExternalLink,
|
|
19
19
|
IconLink,
|
|
20
20
|
IconMail,
|
|
21
|
-
IconMessage,
|
|
22
21
|
IconPhoto,
|
|
23
|
-
IconRefresh,
|
|
24
22
|
IconShare3,
|
|
25
23
|
} from "@tabler/icons-react";
|
|
26
24
|
import {
|
|
@@ -33,6 +31,11 @@ import {
|
|
|
33
31
|
} from "react";
|
|
34
32
|
import { toast } from "sonner";
|
|
35
33
|
|
|
34
|
+
import {
|
|
35
|
+
ClaudeCodeLogo,
|
|
36
|
+
ClaudeLogo,
|
|
37
|
+
CodexLogo,
|
|
38
|
+
} from "@/components/agent-destination-logos";
|
|
36
39
|
import {
|
|
37
40
|
PageHeaderActionGroup,
|
|
38
41
|
PageHeaderPrimaryAction,
|
|
@@ -64,7 +67,7 @@ import {
|
|
|
64
67
|
PopoverTrigger,
|
|
65
68
|
PopoverContent,
|
|
66
69
|
} from "@/components/ui/popover";
|
|
67
|
-
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
70
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
68
71
|
import {
|
|
69
72
|
Tooltip,
|
|
70
73
|
TooltipContent,
|
|
@@ -76,6 +79,10 @@ import { buildAgentApiUrls } from "../../../shared/agent-context";
|
|
|
76
79
|
import { buildEmailPreviewMarkup } from "../../../shared/email-preview";
|
|
77
80
|
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
78
81
|
import { preferredThumbnailVariant } from "../../../shared/share-meta";
|
|
82
|
+
import {
|
|
83
|
+
buildAgentShareDeepLink,
|
|
84
|
+
type AgentShareDestination,
|
|
85
|
+
} from "../../lib/agent-share";
|
|
79
86
|
import { buildSocialShareUrl } from "../../lib/social-share";
|
|
80
87
|
import { ViewerSwitch } from "./viewer-controls";
|
|
81
88
|
|
|
@@ -216,7 +223,7 @@ export function ShareRecordingPopover({
|
|
|
216
223
|
<PopoverContent
|
|
217
224
|
align="end"
|
|
218
225
|
{...nestedLayerDismissGuards()}
|
|
219
|
-
className="z-[260] w-[
|
|
226
|
+
className="z-[260] w-[360px] max-w-[calc(100vw-1rem)] overflow-hidden border-border p-0"
|
|
220
227
|
>
|
|
221
228
|
<ShareRecordingContent
|
|
222
229
|
recordingId={recordingId}
|
|
@@ -259,7 +266,7 @@ export function ShareRecordingDialog({
|
|
|
259
266
|
const t = useT();
|
|
260
267
|
return (
|
|
261
268
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
262
|
-
<DialogContent className="w-[calc(100vw-2rem)] overflow-hidden border-border p-0 sm:max-w-[
|
|
269
|
+
<DialogContent className="w-[calc(100vw-2rem)] overflow-hidden border-border p-0 sm:max-w-[360px] [&>button]:top-1.5">
|
|
263
270
|
<DialogTitle className="sr-only">
|
|
264
271
|
{recordingTitle
|
|
265
272
|
? t("shareDialog.sharePlainTitle", { title: recordingTitle })
|
|
@@ -314,6 +321,7 @@ function ShareRecordingContent({
|
|
|
314
321
|
}) {
|
|
315
322
|
const t = useT();
|
|
316
323
|
const [view, setView] = useState<"main" | "social" | "embed">("main");
|
|
324
|
+
const [shareMode, setShareMode] = useState<"people" | "agents">("people");
|
|
317
325
|
const [passwordProtected, setPasswordProtected] = useState(
|
|
318
326
|
Boolean(hasPassword),
|
|
319
327
|
);
|
|
@@ -326,6 +334,11 @@ function ShareRecordingContent({
|
|
|
326
334
|
useEffect(() => {
|
|
327
335
|
setCurrentExpiry(expiresAt ?? null);
|
|
328
336
|
}, [expiresAt]);
|
|
337
|
+
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
setShareMode("people");
|
|
340
|
+
}, [recordingId]);
|
|
341
|
+
|
|
329
342
|
const sharesQuery = useActionQuery<SharesResponse>(
|
|
330
343
|
"list-resource-shares",
|
|
331
344
|
{ resourceType: "recording", resourceId: recordingId },
|
|
@@ -378,65 +391,109 @@ function ShareRecordingContent({
|
|
|
378
391
|
: view === "embed"
|
|
379
392
|
? t("shareDialog.embed")
|
|
380
393
|
: t("shareDialog.shareRecording");
|
|
394
|
+
const peopleTab = (
|
|
395
|
+
<PeopleTab
|
|
396
|
+
recordingId={recordingId}
|
|
397
|
+
sharesQuery={sharesQuery}
|
|
398
|
+
visibility={visibility}
|
|
399
|
+
visibilityPending={visibilityPending}
|
|
400
|
+
onVisibilityChange={setResourceVisibility}
|
|
401
|
+
canManage={canManage}
|
|
402
|
+
hasPassword={passwordProtected}
|
|
403
|
+
expiresAt={currentExpiry}
|
|
404
|
+
onPasswordChange={setPasswordProtected}
|
|
405
|
+
onExpiryChange={setCurrentExpiry}
|
|
406
|
+
canViewShares={canViewShares}
|
|
407
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
408
|
+
canEmbed={canEmbed}
|
|
409
|
+
onOpenSocial={() => setView("social")}
|
|
410
|
+
onOpenEmbed={() => setView("embed")}
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
381
413
|
|
|
382
414
|
return (
|
|
383
415
|
<div className="min-w-0">
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
416
|
+
{view !== "main" || reserveCloseButton ? (
|
|
417
|
+
<div
|
|
418
|
+
className={cn(
|
|
419
|
+
"flex h-10 items-center gap-2 border-b border-border px-3",
|
|
420
|
+
reserveCloseButton && "pe-10",
|
|
421
|
+
)}
|
|
422
|
+
>
|
|
423
|
+
{view === "main" ? (
|
|
424
|
+
<span className="min-w-0 flex-1 truncate text-sm font-semibold">
|
|
425
|
+
{viewTitle}
|
|
426
|
+
</span>
|
|
427
|
+
) : (
|
|
428
|
+
<Button
|
|
429
|
+
type="button"
|
|
430
|
+
variant="ghost"
|
|
431
|
+
size="sm"
|
|
432
|
+
className="-ms-1 h-7 min-w-0 gap-1.5 px-1.5"
|
|
433
|
+
onClick={() => setView("main")}
|
|
434
|
+
>
|
|
435
|
+
<IconArrowLeft className="size-3.5 shrink-0" />
|
|
436
|
+
<span className="truncate font-semibold">{viewTitle}</span>
|
|
437
|
+
</Button>
|
|
438
|
+
)}
|
|
439
|
+
{view === "main" && showHeaderCopy ? (
|
|
440
|
+
<CopyButton
|
|
441
|
+
value={shareUrl}
|
|
442
|
+
disabled={visibilityPending || !sharesLoaded}
|
|
443
|
+
variant="ghost"
|
|
444
|
+
className="h-7 shrink-0 px-2 text-xs text-primary hover:text-primary"
|
|
445
|
+
resourceType="recording"
|
|
446
|
+
resourceId={recordingId}
|
|
447
|
+
linkType="share"
|
|
448
|
+
>
|
|
449
|
+
{t("shareUi.copyLink")}
|
|
450
|
+
</CopyButton>
|
|
451
|
+
) : null}
|
|
452
|
+
</div>
|
|
453
|
+
) : null}
|
|
420
454
|
|
|
421
455
|
<div className="px-3 py-2">
|
|
422
456
|
{view === "main" ? (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
457
|
+
viewerReshareOnly ? (
|
|
458
|
+
peopleTab
|
|
459
|
+
) : (
|
|
460
|
+
<Tabs
|
|
461
|
+
value={shareMode}
|
|
462
|
+
onValueChange={(value) =>
|
|
463
|
+
setShareMode(value as "people" | "agents")
|
|
464
|
+
}
|
|
465
|
+
className="gap-3"
|
|
466
|
+
>
|
|
467
|
+
<TabsList
|
|
468
|
+
variant="line"
|
|
469
|
+
className="h-8 w-full justify-start gap-1 rounded-none px-0 py-0"
|
|
470
|
+
>
|
|
471
|
+
<TabsTrigger
|
|
472
|
+
value="people"
|
|
473
|
+
className="h-8 min-w-0 flex-none rounded-none px-2 py-0 text-sm data-[state=active]:after:bottom-0 data-[state=active]:after:inset-x-2"
|
|
474
|
+
>
|
|
475
|
+
{t("shareDialog.people")}
|
|
476
|
+
</TabsTrigger>
|
|
477
|
+
<TabsTrigger
|
|
478
|
+
value="agents"
|
|
479
|
+
className="h-8 min-w-0 flex-none rounded-none px-2 py-0 text-sm data-[state=active]:after:bottom-0 data-[state=active]:after:inset-x-2"
|
|
480
|
+
>
|
|
481
|
+
{t("shareDialog.agents")}
|
|
482
|
+
</TabsTrigger>
|
|
483
|
+
</TabsList>
|
|
484
|
+
<TabsContent value="people" className="m-0">
|
|
485
|
+
{peopleTab}
|
|
486
|
+
</TabsContent>
|
|
487
|
+
<TabsContent value="agents" className="m-0">
|
|
488
|
+
<AgentTab
|
|
489
|
+
recordingId={recordingId}
|
|
490
|
+
visibility={visibility}
|
|
491
|
+
hasPassword={passwordProtected}
|
|
492
|
+
active={shareMode === "agents"}
|
|
493
|
+
/>
|
|
494
|
+
</TabsContent>
|
|
495
|
+
</Tabs>
|
|
496
|
+
)
|
|
440
497
|
) : view === "social" ? (
|
|
441
498
|
<SocialTab
|
|
442
499
|
shareUrl={shareUrl}
|
|
@@ -488,7 +545,7 @@ function ShareOptionRow({
|
|
|
488
545
|
// Primary view — invite, access, and progressively disclosed destinations
|
|
489
546
|
// ---------------------------------------------------------------------------
|
|
490
547
|
|
|
491
|
-
function
|
|
548
|
+
function PeopleTab({
|
|
492
549
|
recordingId,
|
|
493
550
|
sharesQuery,
|
|
494
551
|
visibility,
|
|
@@ -525,8 +582,130 @@ function LinkTab({
|
|
|
525
582
|
onOpenEmbed: () => void;
|
|
526
583
|
}) {
|
|
527
584
|
const t = useT();
|
|
585
|
+
|
|
586
|
+
if (viewerReshareOnly) {
|
|
587
|
+
return (
|
|
588
|
+
<div className="-mx-1.5 flex flex-col">
|
|
589
|
+
<ShareOptionRow
|
|
590
|
+
icon={<IconShare3 className="size-3.5" />}
|
|
591
|
+
label={t("shareDialog.social")}
|
|
592
|
+
onClick={onOpenSocial}
|
|
593
|
+
/>
|
|
594
|
+
{canEmbed ? (
|
|
595
|
+
<ShareOptionRow
|
|
596
|
+
icon={<IconExternalLink className="size-3.5" />}
|
|
597
|
+
label={t("shareDialog.embed")}
|
|
598
|
+
onClick={onOpenEmbed}
|
|
599
|
+
/>
|
|
600
|
+
) : null}
|
|
601
|
+
</div>
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return (
|
|
606
|
+
<div className="flex flex-col gap-3">
|
|
607
|
+
<div className="flex flex-col gap-3">
|
|
608
|
+
{/* `share-resource` requires owner/admin, so anyone else would only
|
|
609
|
+
get a rejected submission. */}
|
|
610
|
+
{canManage ? (
|
|
611
|
+
<InvitePeopleField
|
|
612
|
+
resourceType="recording"
|
|
613
|
+
resourceId={recordingId}
|
|
614
|
+
resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
|
|
615
|
+
sharesQuery={sharesQuery}
|
|
616
|
+
onError={(err) =>
|
|
617
|
+
toast.error(
|
|
618
|
+
err instanceof Error
|
|
619
|
+
? err.message
|
|
620
|
+
: t("clipsFinalRaw.inviteFailed"),
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
/>
|
|
624
|
+
) : null}
|
|
625
|
+
|
|
626
|
+
{canViewShares ? (
|
|
627
|
+
visibility ? (
|
|
628
|
+
<div className="flex flex-col gap-1.5">
|
|
629
|
+
<ShareSectionLabel>{t("shareUi.whoHasAccess")}</ShareSectionLabel>
|
|
630
|
+
<div className="flex flex-col gap-0.5">
|
|
631
|
+
<PeopleAccessSection
|
|
632
|
+
resourceType="recording"
|
|
633
|
+
resourceId={recordingId}
|
|
634
|
+
sharesQuery={sharesQuery}
|
|
635
|
+
canManage={canManage}
|
|
636
|
+
roleCopy={{
|
|
637
|
+
commenter: {
|
|
638
|
+
label: t("shareUi.recordingCommenter.label"),
|
|
639
|
+
description: t("shareUi.recordingCommenter.description"),
|
|
640
|
+
},
|
|
641
|
+
}}
|
|
642
|
+
onError={(err, action) =>
|
|
643
|
+
toast.error(
|
|
644
|
+
err instanceof Error
|
|
645
|
+
? err.message
|
|
646
|
+
: action === "permission"
|
|
647
|
+
? t("clipsFinalRaw.permissionUpdateFailed")
|
|
648
|
+
: t("clipsFinalRaw.removePersonFailed"),
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
/>
|
|
652
|
+
<GeneralAccessSelect
|
|
653
|
+
visibility={visibility}
|
|
654
|
+
canManage={canManage}
|
|
655
|
+
isPending={visibilityPending}
|
|
656
|
+
onChange={onVisibilityChange}
|
|
657
|
+
/>
|
|
658
|
+
<RecordingAccessControls
|
|
659
|
+
recordingId={recordingId}
|
|
660
|
+
hasPassword={Boolean(hasPassword)}
|
|
661
|
+
expiresAt={expiresAt}
|
|
662
|
+
canEdit={canViewShares}
|
|
663
|
+
onPasswordChange={onPasswordChange}
|
|
664
|
+
onExpiryChange={onExpiryChange}
|
|
665
|
+
/>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
) : (
|
|
669
|
+
<div className="flex flex-col gap-1.5" aria-hidden>
|
|
670
|
+
<div className="h-3 w-24 animate-pulse rounded bg-muted" />
|
|
671
|
+
<div className="h-8 w-full animate-pulse rounded bg-muted" />
|
|
672
|
+
<div className="h-8 w-full animate-pulse rounded bg-muted" />
|
|
673
|
+
</div>
|
|
674
|
+
)
|
|
675
|
+
) : null}
|
|
676
|
+
</div>
|
|
677
|
+
|
|
678
|
+
<div className="-mx-3 border-t border-border px-1.5 pt-1.5">
|
|
679
|
+
<ShareOptionRow
|
|
680
|
+
icon={<IconShare3 className="size-3.5" />}
|
|
681
|
+
label={t("shareDialog.social")}
|
|
682
|
+
onClick={onOpenSocial}
|
|
683
|
+
/>
|
|
684
|
+
{canEmbed ? (
|
|
685
|
+
<ShareOptionRow
|
|
686
|
+
icon={<IconExternalLink className="size-3.5" />}
|
|
687
|
+
label={t("shareDialog.embed")}
|
|
688
|
+
onClick={onOpenEmbed}
|
|
689
|
+
/>
|
|
690
|
+
) : null}
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function AgentTab({
|
|
697
|
+
recordingId,
|
|
698
|
+
visibility,
|
|
699
|
+
hasPassword,
|
|
700
|
+
active,
|
|
701
|
+
}: {
|
|
702
|
+
recordingId: string;
|
|
703
|
+
visibility: Visibility | null;
|
|
704
|
+
hasPassword?: boolean;
|
|
705
|
+
active: boolean;
|
|
706
|
+
}) {
|
|
707
|
+
const t = useT();
|
|
528
708
|
const isPublic = visibility === "public";
|
|
529
|
-
const sharesLoaded = visibility !== null;
|
|
530
709
|
const needsScopedAgentContext = !isPublic || hasPassword !== false;
|
|
531
710
|
const publicAgentContextUrl = useMemo(
|
|
532
711
|
() =>
|
|
@@ -542,7 +721,6 @@ function LinkTab({
|
|
|
542
721
|
const agentLinkRequestIdRef = useRef(0);
|
|
543
722
|
const [agentContextUrl, setAgentContextUrl] = useState("");
|
|
544
723
|
const [agentLinkError, setAgentLinkError] = useState(false);
|
|
545
|
-
const [agentShareOpen, setAgentShareOpen] = useState(false);
|
|
546
724
|
|
|
547
725
|
useEffect(() => {
|
|
548
726
|
createAgentLinkAsyncRef.current = createAgentLink.mutateAsync;
|
|
@@ -551,7 +729,6 @@ function LinkTab({
|
|
|
551
729
|
const loadAgentContextUrl = useCallback(async () => {
|
|
552
730
|
const requestId = agentLinkRequestIdRef.current + 1;
|
|
553
731
|
agentLinkRequestIdRef.current = requestId;
|
|
554
|
-
|
|
555
732
|
setAgentContextUrl("");
|
|
556
733
|
setAgentLinkError(false);
|
|
557
734
|
|
|
@@ -573,32 +750,27 @@ function LinkTab({
|
|
|
573
750
|
}, [recordingId]);
|
|
574
751
|
|
|
575
752
|
useEffect(() => {
|
|
753
|
+
if (!active) {
|
|
754
|
+
agentLinkRequestIdRef.current += 1;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
576
758
|
setAgentContextUrl("");
|
|
577
759
|
setAgentLinkError(false);
|
|
578
|
-
if (
|
|
579
|
-
|
|
580
|
-
if (needsScopedAgentContext) {
|
|
760
|
+
if (visibility !== null && needsScopedAgentContext) {
|
|
581
761
|
void loadAgentContextUrl();
|
|
582
762
|
}
|
|
583
763
|
|
|
584
764
|
return () => {
|
|
585
765
|
agentLinkRequestIdRef.current += 1;
|
|
586
766
|
};
|
|
587
|
-
}, [
|
|
588
|
-
loadAgentContextUrl,
|
|
589
|
-
needsScopedAgentContext,
|
|
590
|
-
agentShareOpen,
|
|
591
|
-
recordingId,
|
|
592
|
-
sharesLoaded,
|
|
593
|
-
visibility,
|
|
594
|
-
]);
|
|
767
|
+
}, [active, loadAgentContextUrl, needsScopedAgentContext, visibility]);
|
|
595
768
|
|
|
596
769
|
const agentLink = isPublic
|
|
597
770
|
? publicAgentContextUrl || agentContextUrl
|
|
598
771
|
: agentContextUrl;
|
|
599
772
|
const agentShareDisabled =
|
|
600
|
-
|
|
601
|
-
!sharesLoaded ||
|
|
773
|
+
visibility === null ||
|
|
602
774
|
!agentLink ||
|
|
603
775
|
(needsScopedAgentContext &&
|
|
604
776
|
(createAgentLink.isPending || !agentContextUrl));
|
|
@@ -606,150 +778,95 @@ function LinkTab({
|
|
|
606
778
|
? t("shareDialog.agentPrompt", { agentContextUrl: agentLink })
|
|
607
779
|
: "";
|
|
608
780
|
|
|
609
|
-
|
|
781
|
+
const openAgentDestination = (destination: AgentShareDestination) => {
|
|
782
|
+
if (
|
|
783
|
+
agentShareDisabled ||
|
|
784
|
+
!agentCopyValue ||
|
|
785
|
+
typeof window === "undefined"
|
|
786
|
+
) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
610
789
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
sharesQuery={sharesQuery}
|
|
621
|
-
onError={(err) =>
|
|
622
|
-
toast.error(
|
|
623
|
-
err instanceof Error
|
|
624
|
-
? err.message
|
|
625
|
-
: t("clipsFinalRaw.inviteFailed"),
|
|
626
|
-
)
|
|
627
|
-
}
|
|
628
|
-
/>
|
|
629
|
-
) : null}
|
|
790
|
+
trackEvent("agent_share_opened", {
|
|
791
|
+
resource_type: "recording",
|
|
792
|
+
resource_id: recordingId,
|
|
793
|
+
destination,
|
|
794
|
+
});
|
|
795
|
+
window.location.assign(
|
|
796
|
+
buildAgentShareDeepLink(destination, agentCopyValue),
|
|
797
|
+
);
|
|
798
|
+
};
|
|
630
799
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
<div className="flex flex-col gap-0.5">
|
|
636
|
-
<PeopleAccessSection
|
|
637
|
-
resourceType="recording"
|
|
638
|
-
resourceId={recordingId}
|
|
639
|
-
sharesQuery={sharesQuery}
|
|
640
|
-
canManage={canManage}
|
|
641
|
-
roleCopy={{
|
|
642
|
-
commenter: {
|
|
643
|
-
label: t("shareUi.recordingCommenter.label"),
|
|
644
|
-
description: t("shareUi.recordingCommenter.description"),
|
|
645
|
-
},
|
|
646
|
-
}}
|
|
647
|
-
onError={(err, action) =>
|
|
648
|
-
toast.error(
|
|
649
|
-
err instanceof Error
|
|
650
|
-
? err.message
|
|
651
|
-
: action === "permission"
|
|
652
|
-
? t("clipsFinalRaw.permissionUpdateFailed")
|
|
653
|
-
: t("clipsFinalRaw.removePersonFailed"),
|
|
654
|
-
)
|
|
655
|
-
}
|
|
656
|
-
/>
|
|
657
|
-
<GeneralAccessSelect
|
|
658
|
-
visibility={visibility}
|
|
659
|
-
canManage={canManage}
|
|
660
|
-
isPending={visibilityPending}
|
|
661
|
-
onChange={onVisibilityChange}
|
|
662
|
-
/>
|
|
663
|
-
<RecordingAccessControls
|
|
664
|
-
recordingId={recordingId}
|
|
665
|
-
hasPassword={Boolean(hasPassword)}
|
|
666
|
-
expiresAt={expiresAt}
|
|
667
|
-
canEdit={canViewShares}
|
|
668
|
-
onPasswordChange={onPasswordChange}
|
|
669
|
-
onExpiryChange={onExpiryChange}
|
|
670
|
-
/>
|
|
671
|
-
</div>
|
|
672
|
-
</div>
|
|
673
|
-
) : (
|
|
674
|
-
<div className="space-y-1.5" aria-hidden>
|
|
675
|
-
<div className="h-3 w-24 animate-pulse rounded bg-muted" />
|
|
676
|
-
<div className="h-8 w-full animate-pulse rounded bg-muted" />
|
|
677
|
-
<div className="h-8 w-full animate-pulse rounded bg-muted" />
|
|
678
|
-
</div>
|
|
679
|
-
)
|
|
680
|
-
) : null}
|
|
800
|
+
const copyAgentPrompt = async () => {
|
|
801
|
+
if (agentShareDisabled || !agentCopyValue) return;
|
|
802
|
+
const copied = await writeClipboardText(agentCopyValue);
|
|
803
|
+
if (copied === false) return;
|
|
681
804
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
<ShareOptionRow
|
|
690
|
-
icon={<IconExternalLink className="size-3.5" />}
|
|
691
|
-
label={t("shareDialog.embed")}
|
|
692
|
-
onClick={onOpenEmbed}
|
|
693
|
-
/>
|
|
694
|
-
) : null}
|
|
695
|
-
</div>
|
|
805
|
+
trackEvent("share_link_copied", {
|
|
806
|
+
resource_type: "recording",
|
|
807
|
+
resource_id: recordingId,
|
|
808
|
+
link_type: "agent_context",
|
|
809
|
+
});
|
|
810
|
+
toast.success(t("shareUi.copied"));
|
|
811
|
+
};
|
|
696
812
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
813
|
+
if (agentLinkError) {
|
|
814
|
+
return (
|
|
815
|
+
<Button
|
|
816
|
+
type="button"
|
|
817
|
+
variant="ghost"
|
|
818
|
+
className="h-9 w-full justify-start px-1.5 text-sm font-normal"
|
|
819
|
+
onClick={() => void loadAgentContextUrl()}
|
|
820
|
+
disabled={createAgentLink.isPending}
|
|
821
|
+
>
|
|
822
|
+
{t("shareDialog.retryAgentLink")}
|
|
823
|
+
</Button>
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return (
|
|
828
|
+
<div className="-mx-1.5 flex flex-col gap-0.5">
|
|
829
|
+
<Button
|
|
830
|
+
type="button"
|
|
831
|
+
variant="ghost"
|
|
832
|
+
className="h-9 w-full justify-start gap-2 px-1.5 text-sm font-normal"
|
|
833
|
+
disabled={agentShareDisabled}
|
|
834
|
+
onClick={() => void copyAgentPrompt()}
|
|
835
|
+
>
|
|
836
|
+
<IconLink className="size-4 text-muted-foreground" />
|
|
837
|
+
{t("shareDialog.copyAgentPrompt")}
|
|
838
|
+
</Button>
|
|
839
|
+
<div className="my-1 border-t border-border" />
|
|
840
|
+
<Button
|
|
841
|
+
type="button"
|
|
842
|
+
variant="ghost"
|
|
843
|
+
className="h-9 w-full justify-start gap-2 px-1.5 text-sm font-normal"
|
|
844
|
+
disabled={agentShareDisabled}
|
|
845
|
+
onClick={() => openAgentDestination("claude")}
|
|
846
|
+
>
|
|
847
|
+
<ClaudeLogo className="size-4 text-muted-foreground" />
|
|
848
|
+
{t("shareDialog.openInClaude")}
|
|
849
|
+
</Button>
|
|
850
|
+
<Button
|
|
851
|
+
type="button"
|
|
852
|
+
variant="ghost"
|
|
853
|
+
className="h-9 w-full justify-start gap-2 px-1.5 text-sm font-normal"
|
|
854
|
+
disabled={agentShareDisabled}
|
|
855
|
+
onClick={() => openAgentDestination("claude-code")}
|
|
856
|
+
>
|
|
857
|
+
<ClaudeCodeLogo className="size-4 text-muted-foreground" />
|
|
858
|
+
{t("shareDialog.openInClaudeCode")}
|
|
859
|
+
</Button>
|
|
860
|
+
<Button
|
|
861
|
+
type="button"
|
|
862
|
+
variant="ghost"
|
|
863
|
+
className="h-9 w-full justify-start gap-2 px-1.5 text-sm font-normal"
|
|
864
|
+
disabled={agentShareDisabled}
|
|
865
|
+
onClick={() => openAgentDestination("codex")}
|
|
866
|
+
>
|
|
867
|
+
<CodexLogo className="size-4 text-muted-foreground" />
|
|
868
|
+
{t("shareDialog.openInCodex")}
|
|
869
|
+
</Button>
|
|
753
870
|
</div>
|
|
754
871
|
);
|
|
755
872
|
}
|
|
@@ -799,32 +916,18 @@ function RecordingAccessControls({
|
|
|
799
916
|
<div className="flex flex-col gap-0.5">
|
|
800
917
|
<div className="rounded-md px-1.5 py-1">
|
|
801
918
|
<div className="flex min-h-8 items-center gap-2">
|
|
802
|
-
<
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
</Label>
|
|
815
|
-
</div>
|
|
816
|
-
{passwordEnabled && canEdit ? (
|
|
817
|
-
<Button
|
|
818
|
-
type="button"
|
|
819
|
-
variant="ghost"
|
|
820
|
-
size="sm"
|
|
821
|
-
className="h-7 shrink-0 gap-1.5 px-2 text-xs"
|
|
822
|
-
onClick={() => setPassword(generateSecurePassword())}
|
|
823
|
-
>
|
|
824
|
-
<IconRefresh className="size-3.5" />
|
|
825
|
-
{t("playerSettings.generatePassword")}
|
|
826
|
-
</Button>
|
|
827
|
-
) : null}
|
|
919
|
+
<Label
|
|
920
|
+
htmlFor="share-password-required"
|
|
921
|
+
className="min-w-0 flex-1 cursor-pointer text-sm font-normal"
|
|
922
|
+
>
|
|
923
|
+
{t("embedRoute.passwordRequired")}
|
|
924
|
+
</Label>
|
|
925
|
+
<ViewerSwitch
|
|
926
|
+
id="share-password-required"
|
|
927
|
+
checked={passwordEnabled}
|
|
928
|
+
disabled={!canEdit || updateRecording.isPending}
|
|
929
|
+
onCheckedChange={setPasswordRequired}
|
|
930
|
+
/>
|
|
828
931
|
</div>
|
|
829
932
|
|
|
830
933
|
{passwordEnabled ? (
|
|
@@ -957,25 +1060,6 @@ function formatExpiry(iso: string | null): string {
|
|
|
957
1060
|
}).format(date);
|
|
958
1061
|
}
|
|
959
1062
|
|
|
960
|
-
export function generateSecurePassword(length = 20): string {
|
|
961
|
-
const alphabet =
|
|
962
|
-
"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789-._~";
|
|
963
|
-
const limit = Math.floor(256 / alphabet.length) * alphabet.length;
|
|
964
|
-
const bytes = new Uint8Array(length * 2);
|
|
965
|
-
let password = "";
|
|
966
|
-
|
|
967
|
-
while (password.length < length) {
|
|
968
|
-
crypto.getRandomValues(bytes);
|
|
969
|
-
for (const byte of bytes) {
|
|
970
|
-
if (byte >= limit) continue;
|
|
971
|
-
password += alphabet[byte % alphabet.length];
|
|
972
|
-
if (password.length === length) break;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
return password;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
1063
|
// ---------------------------------------------------------------------------
|
|
980
1064
|
// Social tab — destination-first share intents, no provider account required
|
|
981
1065
|
// ---------------------------------------------------------------------------
|