@agent-native/core 0.141.2 → 0.141.4
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/analytics/.agents/skills/analysis-workspace/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/gong-calls.ts +56 -18
- package/corpus/templates/analytics/changelog/2026-08-05-analytics-queries-keep-every-table-tenant-scoped.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-durable-a2a-workers-avoid-startup-migration-contention.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-fixed-sign-in-and-dashboard-requests-failing-when-background.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/emails.ts +51 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +8 -12
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +262 -13
- package/corpus/templates/analytics/server/lib/gong.ts +132 -33
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/server/plugins/auth.ts +21 -2
- package/corpus/templates/analytics/server/plugins/db.ts +35 -10
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +17 -7
- package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
- package/corpus/templates/calendar/server/lib/emails.ts +167 -0
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/clips/actions/invite-member.ts +39 -16
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +73 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-08-05-activity-is-now-the-default-clips-recording-tab-and-ai-setup.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-can-copy-a-linked-email-preview-for-public-recordings.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-preparing-recording-now-uses-a-clean-shadow-free-status-card.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +0 -3
- package/corpus/templates/clips/server/lib/emails.ts +256 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +4 -1
- package/corpus/templates/clips/shared/email-preview.ts +61 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
- package/corpus/templates/content/server/lib/emails.ts +70 -0
- package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +11 -8
- package/corpus/templates/design/actions/index-design-tokens.ts +13 -6
- package/corpus/templates/design/changelog/2026-08-05-design-token-imports-classify-typography-dimensions-correctly.md +6 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/changelog/2026-08-05-transactional-email-catalogs-and-delivery-activity-are-available-again.md +6 -0
- package/corpus/templates/factory/AGENTS.md +18 -17
- package/corpus/templates/factory/actions/get-factory-automation-health.ts +50 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +8 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +26 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +27 -0
- package/corpus/templates/factory/app/routes/factory.tsx +376 -206
- package/corpus/templates/factory/changelog/2026-08-05-factory-now-keeps-production-automations-running-shows-sched.md +6 -0
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +10 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +141 -2
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +41 -21
- package/corpus/templates/forms/changelog/2026-08-05-form-links-can-now-be-cmd-clicked-to-open-in-a-new-tab.md +6 -0
- package/corpus/templates/forms/server/lib/emails.ts +52 -0
- package/corpus/templates/forms/server/lib/response-email.ts +2 -0
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
- package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
- package/corpus/templates/plan/server/lib/emails.ts +67 -0
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/slides/actions/import-from-url.ts +1 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +492 -381
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +8 -7
- package/corpus/templates/slides/app/components/layout/Layout.tsx +27 -23
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +313 -0
- package/corpus/templates/slides/app/components/ui/command.tsx +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -2
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +305 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -12
- package/corpus/templates/slides/app/pages/Index.tsx +127 -137
- package/corpus/templates/slides/app/root.tsx +8 -0
- package/corpus/templates/slides/changelog/2026-08-05-deck-browsing-keeps-all-and-mine-in-the-header-with-a-tighte.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-new-deck-creation-now-flows-through-a-clean-full-screen-refe.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-imports-now-include-linked-stylesheets-for-more-accu.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
- package/corpus/templates/slides/server/lib/emails.ts +40 -0
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +91 -0
- package/dist/brand-kit/tokens.js +279 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/client/AssistantChat.js +7 -7
- package/dist/client/onboarding/FirstRunOnboarding.js +26 -2
- package/dist/client/onboarding/first-run-registry.d.ts +25 -0
- package/dist/client/onboarding/first-run-registry.js +18 -0
- package/dist/client/onboarding/index.d.ts +2 -0
- package/dist/client/onboarding/index.js +1 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/migrations.d.ts +13 -3
- package/dist/db/migrations.js +19 -4
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.js +92 -3
- package/dist/deploy/workspace-deploy.js +86 -3
- package/dist/email-catalog/actions/list-email-activity.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-activity.js +19 -0
- package/dist/email-catalog/actions/list-email-engagement.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-engagement.js +15 -0
- package/dist/email-catalog/actions/list-email-log.d.ts +7 -0
- package/dist/email-catalog/actions/list-email-log.js +23 -0
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +22 -0
- package/dist/email-catalog/actions/list-transactional-emails.js +67 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +9 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.js +25 -0
- package/dist/email-catalog/authorize.d.ts +1 -0
- package/dist/email-catalog/authorize.js +9 -0
- package/dist/email-catalog/log.d.ts +60 -0
- package/dist/email-catalog/log.js +142 -0
- package/dist/email-catalog/provider-metrics.d.ts +34 -0
- package/dist/email-catalog/provider-metrics.js +182 -0
- package/dist/email-catalog/registry.d.ts +78 -0
- package/dist/email-catalog/registry.js +70 -0
- package/dist/email-catalog/schema.d.ts +231 -0
- package/dist/email-catalog/schema.js +53 -0
- package/dist/email-catalog/system-emails.d.ts +13 -0
- package/dist/email-catalog/system-emails.js +67 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +2 -0
- package/dist/jobs/run-history.js +48 -1
- package/dist/jobs/scheduler-dispatch.d.ts +7 -0
- package/dist/jobs/scheduler-dispatch.js +7 -0
- package/dist/jobs/scheduler-health.d.ts +22 -0
- package/dist/jobs/scheduler-health.js +172 -0
- package/dist/jobs/scheduler.js +67 -2
- package/dist/localization/default-messages.d.ts +45 -0
- package/dist/localization/default-messages.js +45 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +9 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/server/action-discovery.js +22 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +7 -1
- package/dist/server/agent-chat/recurring-jobs-runtime.js +12 -0
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.js +69 -22
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/design-token-utils.d.ts +6 -1
- package/dist/server/design-token-utils.js +252 -59
- package/dist/server/email.d.ts +11 -0
- package/dist/server/email.js +54 -3
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +3 -2
- package/dist/vite/client.js +20 -14
- package/package.json +2 -1
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
} from "@agent-native/core/server";
|
|
7
7
|
|
|
8
8
|
import type { CalendarEvent, DeleteEventScope } from "../../shared/api.js";
|
|
9
|
+
import {
|
|
10
|
+
CALENDAR_EVENT_CANCELLATION_NOTE_EMAIL_ID,
|
|
11
|
+
CALENDAR_EVENT_UPDATE_NOTE_EMAIL_ID,
|
|
12
|
+
} from "./emails.js";
|
|
9
13
|
|
|
10
14
|
export interface GuestNotificationResult {
|
|
11
15
|
requested: boolean;
|
|
@@ -96,6 +100,53 @@ function scopeLabel(scope: DeleteEventScope | undefined): string | undefined {
|
|
|
96
100
|
return "this and following events";
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
export function renderEventGuestNote({
|
|
104
|
+
title,
|
|
105
|
+
organizer,
|
|
106
|
+
message,
|
|
107
|
+
when,
|
|
108
|
+
kind,
|
|
109
|
+
appliesTo,
|
|
110
|
+
htmlLink,
|
|
111
|
+
}: {
|
|
112
|
+
title: string;
|
|
113
|
+
organizer: string;
|
|
114
|
+
message: string;
|
|
115
|
+
when: string;
|
|
116
|
+
kind: GuestNotificationKind;
|
|
117
|
+
appliesTo?: string;
|
|
118
|
+
htmlLink?: string | null;
|
|
119
|
+
}) {
|
|
120
|
+
const heading =
|
|
121
|
+
kind === "cancellation" ? "Event cancellation note" : "Event update note";
|
|
122
|
+
const subjectPrefix =
|
|
123
|
+
kind === "cancellation" ? "Cancellation note" : "Update note";
|
|
124
|
+
const eventAction = kind === "cancellation" ? "cancelling" : "updating";
|
|
125
|
+
const paragraphs = [
|
|
126
|
+
`${emailStrong(organizer)} added this note while ${eventAction} ${emailStrong(title)}.`,
|
|
127
|
+
messageParagraph(message),
|
|
128
|
+
`When: ${emailStrong(when)}.`,
|
|
129
|
+
];
|
|
130
|
+
if (appliesTo) {
|
|
131
|
+
paragraphs.push(`Applies to: ${emailStrong(appliesTo)}.`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
subject: `${subjectPrefix}: ${title}`,
|
|
136
|
+
...renderEmail({
|
|
137
|
+
preheader: `${subjectPrefix}: ${title}`,
|
|
138
|
+
heading,
|
|
139
|
+
paragraphs,
|
|
140
|
+
cta:
|
|
141
|
+
kind === "update" && htmlLink
|
|
142
|
+
? { label: "Open in Google Calendar", url: htmlLink }
|
|
143
|
+
: undefined,
|
|
144
|
+
footer:
|
|
145
|
+
"Google Calendar sends the calendar update separately. This message carries the organizer note.",
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
99
150
|
export async function sendEventGuestNotificationNote({
|
|
100
151
|
event,
|
|
101
152
|
organizerEmail,
|
|
@@ -138,33 +189,14 @@ export async function sendEventGuestNotificationNote({
|
|
|
138
189
|
};
|
|
139
190
|
}
|
|
140
191
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
kind
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`${emailStrong(organizer)} added this note while ${eventAction} ${emailStrong(title)}.`,
|
|
150
|
-
messageParagraph(normalizedMessage),
|
|
151
|
-
`When: ${emailStrong(formatWhen(event))}.`,
|
|
152
|
-
];
|
|
153
|
-
const appliesTo = scopeLabel(scope);
|
|
154
|
-
if (appliesTo) {
|
|
155
|
-
paragraphs.push(`Applies to: ${emailStrong(appliesTo)}.`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const rendered = renderEmail({
|
|
159
|
-
preheader: `${subjectPrefix}: ${title}`,
|
|
160
|
-
heading,
|
|
161
|
-
paragraphs,
|
|
162
|
-
cta:
|
|
163
|
-
kind === "update" && event.htmlLink
|
|
164
|
-
? { label: "Open in Google Calendar", url: event.htmlLink }
|
|
165
|
-
: undefined,
|
|
166
|
-
footer:
|
|
167
|
-
"Google Calendar sends the calendar update separately. This message carries the organizer note.",
|
|
192
|
+
const rendered = renderEventGuestNote({
|
|
193
|
+
title: stripCrlf(event.title) || "Calendar event",
|
|
194
|
+
organizer: stripCrlf(event.organizer?.displayName) || organizerEmail,
|
|
195
|
+
message: normalizedMessage,
|
|
196
|
+
when: formatWhen(event),
|
|
197
|
+
kind,
|
|
198
|
+
appliesTo: scopeLabel(scope),
|
|
199
|
+
htmlLink: event.htmlLink,
|
|
168
200
|
});
|
|
169
201
|
|
|
170
202
|
let sentCount = 0;
|
|
@@ -174,10 +206,14 @@ export async function sendEventGuestNotificationNote({
|
|
|
174
206
|
try {
|
|
175
207
|
await sendEmail({
|
|
176
208
|
to,
|
|
177
|
-
subject:
|
|
209
|
+
subject: rendered.subject,
|
|
178
210
|
html: rendered.html,
|
|
179
211
|
text: rendered.text,
|
|
180
212
|
replyTo: organizerEmail,
|
|
213
|
+
templateId:
|
|
214
|
+
kind === "cancellation"
|
|
215
|
+
? CALENDAR_EVENT_CANCELLATION_NOTE_EMAIL_ID
|
|
216
|
+
: CALENDAR_EVENT_UPDATE_NOTE_EMAIL_ID,
|
|
181
217
|
});
|
|
182
218
|
sentCount += 1;
|
|
183
219
|
} catch (error) {
|
|
@@ -24,6 +24,7 @@ import { and, eq, sql } from "drizzle-orm";
|
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
|
|
26
26
|
import { getDb } from "../server/db/index.js";
|
|
27
|
+
import { CLIPS_ORGANIZATION_INVITE_EMAIL_ID } from "../server/lib/emails.js";
|
|
27
28
|
import {
|
|
28
29
|
getCurrentOwnerEmail,
|
|
29
30
|
nanoid,
|
|
@@ -67,6 +68,36 @@ async function fetchOrgName(orgId: string): Promise<string> {
|
|
|
67
68
|
return row?.name ?? "Organization";
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
export function renderClipsInviteEmail({
|
|
72
|
+
appName,
|
|
73
|
+
orgName,
|
|
74
|
+
inviter,
|
|
75
|
+
role,
|
|
76
|
+
inviteUrl,
|
|
77
|
+
}: {
|
|
78
|
+
appName: string;
|
|
79
|
+
orgName: string;
|
|
80
|
+
inviter: string;
|
|
81
|
+
role: "admin" | "member";
|
|
82
|
+
inviteUrl: string;
|
|
83
|
+
}) {
|
|
84
|
+
return {
|
|
85
|
+
subject: `You're invited to ${orgName} on ${appName}`,
|
|
86
|
+
...renderEmail({
|
|
87
|
+
brandName: appName,
|
|
88
|
+
preheader: `${inviter} invited you to ${orgName} on ${appName}.`,
|
|
89
|
+
heading: `You're invited to join ${orgName}`,
|
|
90
|
+
paragraphs: [
|
|
91
|
+
`${emailStrong(inviter)} invited you to the ${emailStrong(orgName)} organization on ${emailStrong(appName)} as ${emailStrong(role)}.`,
|
|
92
|
+
`Click the button below to accept the invite and start collaborating.`,
|
|
93
|
+
],
|
|
94
|
+
cta: { label: "Accept invite", url: inviteUrl },
|
|
95
|
+
// guard:allow-raw-color - email HTML cannot reference CSS theme tokens.
|
|
96
|
+
brandColor: "#18181B",
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
export default defineAction({
|
|
71
102
|
description:
|
|
72
103
|
"Invite someone to the active organization by email. Creates a pending invitation. Role 'admin' maps to admin; all other Clips roles collapse to 'member'. Sends an email when a provider is configured.",
|
|
@@ -123,25 +154,17 @@ export default defineAction({
|
|
|
123
154
|
const orgName = await fetchOrgName(organizationId);
|
|
124
155
|
const inviteUrl = `${baseUrl()}/invite/${token}`;
|
|
125
156
|
|
|
126
|
-
const appName = getAppName();
|
|
127
|
-
const { html, text } = renderEmail({
|
|
128
|
-
brandName: appName,
|
|
129
|
-
preheader: `${inviter} invited you to ${orgName} on ${appName}.`,
|
|
130
|
-
heading: `You're invited to join ${orgName}`,
|
|
131
|
-
paragraphs: [
|
|
132
|
-
`${emailStrong(inviter)} invited you to the ${emailStrong(orgName)} organization on ${emailStrong(appName)} as ${emailStrong(role)}.`,
|
|
133
|
-
`Click the button below to accept the invite and start collaborating.`,
|
|
134
|
-
],
|
|
135
|
-
cta: { label: "Accept invite", url: inviteUrl },
|
|
136
|
-
// guard:allow-raw-color - email HTML cannot reference CSS theme tokens.
|
|
137
|
-
brandColor: "#18181B",
|
|
138
|
-
});
|
|
139
157
|
try {
|
|
140
158
|
await sendEmail({
|
|
159
|
+
...renderClipsInviteEmail({
|
|
160
|
+
appName: getAppName(),
|
|
161
|
+
orgName,
|
|
162
|
+
inviter,
|
|
163
|
+
role,
|
|
164
|
+
inviteUrl,
|
|
165
|
+
}),
|
|
141
166
|
to: args.email,
|
|
142
|
-
|
|
143
|
-
html,
|
|
144
|
-
text,
|
|
167
|
+
templateId: CLIPS_ORGANIZATION_INVITE_EMAIL_ID,
|
|
145
168
|
});
|
|
146
169
|
} catch (err) {
|
|
147
170
|
console.warn("[invite-member] email send failed:", err);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
2
|
+
import { writeClipboardText } from "@agent-native/core/client/clipboard";
|
|
2
3
|
import {
|
|
3
4
|
useActionMutation,
|
|
4
5
|
useActionQuery,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
useState,
|
|
20
21
|
type ReactNode,
|
|
21
22
|
} from "react";
|
|
23
|
+
import { toast } from "sonner";
|
|
22
24
|
|
|
23
25
|
import {
|
|
24
26
|
CopyField,
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
import { Switch } from "@/components/ui/switch";
|
|
45
47
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
46
48
|
|
|
49
|
+
import { buildEmailPreviewMarkup } from "../../../shared/email-preview";
|
|
47
50
|
import { isLoomEmbedUrl } from "../../../shared/loom";
|
|
48
51
|
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
49
52
|
|
|
@@ -58,6 +61,7 @@ export interface ShareRecordingPopoverProps {
|
|
|
58
61
|
initialVisibility?: Visibility | null;
|
|
59
62
|
initialRole?: "owner" | "admin" | "editor" | "viewer";
|
|
60
63
|
videoUrl?: string | null;
|
|
64
|
+
thumbnailUrl?: string | null;
|
|
61
65
|
animatedThumbnailUrl?: string | null;
|
|
62
66
|
isLoomRecording?: boolean;
|
|
63
67
|
hasPassword?: boolean;
|
|
@@ -88,8 +92,10 @@ export function ShareRecordingPopover({
|
|
|
88
92
|
initialVisibility,
|
|
89
93
|
initialRole,
|
|
90
94
|
videoUrl,
|
|
95
|
+
thumbnailUrl,
|
|
91
96
|
animatedThumbnailUrl,
|
|
92
97
|
isLoomRecording = false,
|
|
98
|
+
hasPassword,
|
|
93
99
|
children,
|
|
94
100
|
open,
|
|
95
101
|
onOpenChange,
|
|
@@ -108,8 +114,10 @@ export function ShareRecordingPopover({
|
|
|
108
114
|
initialVisibility={initialVisibility}
|
|
109
115
|
initialRole={initialRole}
|
|
110
116
|
videoUrl={videoUrl}
|
|
117
|
+
thumbnailUrl={thumbnailUrl}
|
|
111
118
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
112
119
|
isLoomRecording={isLoomRecording}
|
|
120
|
+
hasPassword={hasPassword}
|
|
113
121
|
/>
|
|
114
122
|
</PopoverContent>
|
|
115
123
|
</Popover>
|
|
@@ -127,10 +135,12 @@ export function ShareRecordingDialog({
|
|
|
127
135
|
initialVisibility,
|
|
128
136
|
initialRole,
|
|
129
137
|
videoUrl,
|
|
138
|
+
thumbnailUrl,
|
|
130
139
|
animatedThumbnailUrl,
|
|
131
140
|
isLoomRecording = false,
|
|
132
141
|
open,
|
|
133
142
|
onOpenChange,
|
|
143
|
+
hasPassword,
|
|
134
144
|
}: ShareRecordingDialogProps) {
|
|
135
145
|
const t = useT();
|
|
136
146
|
return (
|
|
@@ -147,8 +157,10 @@ export function ShareRecordingDialog({
|
|
|
147
157
|
initialVisibility={initialVisibility}
|
|
148
158
|
initialRole={initialRole}
|
|
149
159
|
videoUrl={videoUrl}
|
|
160
|
+
thumbnailUrl={thumbnailUrl}
|
|
150
161
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
151
162
|
isLoomRecording={isLoomRecording}
|
|
163
|
+
hasPassword={hasPassword}
|
|
152
164
|
reserveCloseButton
|
|
153
165
|
/>
|
|
154
166
|
</DialogContent>
|
|
@@ -162,18 +174,22 @@ function ShareRecordingContent({
|
|
|
162
174
|
initialVisibility,
|
|
163
175
|
initialRole,
|
|
164
176
|
videoUrl,
|
|
177
|
+
thumbnailUrl,
|
|
165
178
|
animatedThumbnailUrl,
|
|
166
179
|
isLoomRecording = false,
|
|
167
180
|
reserveCloseButton = false,
|
|
181
|
+
hasPassword,
|
|
168
182
|
}: {
|
|
169
183
|
recordingId: string;
|
|
170
184
|
recordingTitle?: string;
|
|
171
185
|
initialVisibility?: Visibility | null;
|
|
172
186
|
initialRole?: "owner" | "admin" | "editor" | "viewer";
|
|
173
187
|
videoUrl?: string | null;
|
|
188
|
+
thumbnailUrl?: string | null;
|
|
174
189
|
animatedThumbnailUrl?: string | null;
|
|
175
190
|
isLoomRecording?: boolean;
|
|
176
191
|
reserveCloseButton?: boolean;
|
|
192
|
+
hasPassword?: boolean;
|
|
177
193
|
}) {
|
|
178
194
|
const t = useT();
|
|
179
195
|
const sharesQuery = useActionQuery<SharesResponse>("list-resource-shares", {
|
|
@@ -236,13 +252,16 @@ function ShareRecordingContent({
|
|
|
236
252
|
<TabsContent value="link" className="mt-3">
|
|
237
253
|
<LinkTab
|
|
238
254
|
recordingId={recordingId}
|
|
255
|
+
recordingTitle={recordingTitle}
|
|
239
256
|
shareUrl={shareUrl}
|
|
240
257
|
sharesQuery={sharesQuery}
|
|
241
258
|
visibility={visibility}
|
|
242
259
|
canManage={canManage}
|
|
243
260
|
videoUrl={videoUrl}
|
|
261
|
+
thumbnailUrl={thumbnailUrl}
|
|
244
262
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
245
263
|
isLoomRecording={isLoomRecording}
|
|
264
|
+
hasPassword={hasPassword}
|
|
246
265
|
/>
|
|
247
266
|
</TabsContent>
|
|
248
267
|
|
|
@@ -276,22 +295,28 @@ function ShareRecordingContent({
|
|
|
276
295
|
|
|
277
296
|
function LinkTab({
|
|
278
297
|
recordingId,
|
|
298
|
+
recordingTitle,
|
|
279
299
|
shareUrl,
|
|
280
300
|
sharesQuery,
|
|
281
301
|
visibility,
|
|
282
302
|
canManage,
|
|
283
303
|
videoUrl,
|
|
304
|
+
thumbnailUrl,
|
|
284
305
|
animatedThumbnailUrl,
|
|
285
306
|
isLoomRecording: isLoomRecordingProp,
|
|
307
|
+
hasPassword,
|
|
286
308
|
}: {
|
|
287
309
|
recordingId: string;
|
|
310
|
+
recordingTitle?: string;
|
|
288
311
|
shareUrl: string;
|
|
289
312
|
sharesQuery: SharesQuery;
|
|
290
313
|
visibility: Visibility | null;
|
|
291
314
|
canManage: boolean;
|
|
292
315
|
videoUrl?: string | null;
|
|
316
|
+
thumbnailUrl?: string | null;
|
|
293
317
|
animatedThumbnailUrl?: string | null;
|
|
294
318
|
isLoomRecording?: boolean;
|
|
319
|
+
hasPassword?: boolean;
|
|
295
320
|
}) {
|
|
296
321
|
const t = useT();
|
|
297
322
|
const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
|
|
@@ -303,6 +328,44 @@ function LinkTab({
|
|
|
303
328
|
const sharesLoaded = visibility !== null;
|
|
304
329
|
const visibilityPending = isPending || sharesQuery.isLoading;
|
|
305
330
|
const isLoomRecording = isLoomRecordingProp || isLoomEmbedUrl(videoUrl);
|
|
331
|
+
const emailPreviewThumbnailUrl = useMemo(() => {
|
|
332
|
+
if (!isPublic || hasPassword !== false || !sharesLoaded) return null;
|
|
333
|
+
const candidate = animatedThumbnailUrl || thumbnailUrl;
|
|
334
|
+
if (!candidate) return null;
|
|
335
|
+
|
|
336
|
+
const query = animatedThumbnailUrl ? "?animated=1" : "";
|
|
337
|
+
return absoluteAppUrl(
|
|
338
|
+
`/api/thumbnail/${encodeURIComponent(recordingId)}${query}`,
|
|
339
|
+
);
|
|
340
|
+
}, [
|
|
341
|
+
animatedThumbnailUrl,
|
|
342
|
+
hasPassword,
|
|
343
|
+
isPublic,
|
|
344
|
+
recordingId,
|
|
345
|
+
sharesLoaded,
|
|
346
|
+
thumbnailUrl,
|
|
347
|
+
]);
|
|
348
|
+
const copyEmailPreview = useCallback(async () => {
|
|
349
|
+
if (!emailPreviewThumbnailUrl || !shareUrl) return;
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const markup = buildEmailPreviewMarkup({
|
|
353
|
+
title: recordingTitle?.trim() || t("recordingPage.untitledClip"),
|
|
354
|
+
shareUrl,
|
|
355
|
+
thumbnailUrl: emailPreviewThumbnailUrl,
|
|
356
|
+
});
|
|
357
|
+
const copied = await writeClipboardText(markup.plainText, {
|
|
358
|
+
html: markup.html,
|
|
359
|
+
});
|
|
360
|
+
if (copied) {
|
|
361
|
+
toast.success(t("shareDialog.emailPreviewCopied"));
|
|
362
|
+
} else {
|
|
363
|
+
toast.error(t("shareDialog.emailPreviewCopyFailed"));
|
|
364
|
+
}
|
|
365
|
+
} catch {
|
|
366
|
+
toast.error(t("shareDialog.emailPreviewCopyFailed"));
|
|
367
|
+
}
|
|
368
|
+
}, [emailPreviewThumbnailUrl, recordingTitle, shareUrl, t]);
|
|
306
369
|
const createAgentLink = useActionMutation(
|
|
307
370
|
"create-recording-agent-link" as any,
|
|
308
371
|
);
|
|
@@ -439,8 +502,17 @@ function LinkTab({
|
|
|
439
502
|
/>
|
|
440
503
|
) : null}
|
|
441
504
|
|
|
442
|
-
{videoUrl || animatedThumbnailUrl ? (
|
|
505
|
+
{emailPreviewThumbnailUrl || videoUrl || animatedThumbnailUrl ? (
|
|
443
506
|
<div className="flex flex-wrap gap-2">
|
|
507
|
+
{emailPreviewThumbnailUrl ? (
|
|
508
|
+
<Button
|
|
509
|
+
variant="outline"
|
|
510
|
+
size="sm"
|
|
511
|
+
onClick={() => void copyEmailPreview()}
|
|
512
|
+
>
|
|
513
|
+
{t("shareDialog.copyEmailPreview")}
|
|
514
|
+
</Button>
|
|
515
|
+
) : null}
|
|
444
516
|
{animatedThumbnailUrl ? (
|
|
445
517
|
<Button
|
|
446
518
|
variant="outline"
|
|
@@ -452,6 +452,9 @@ const messages = {
|
|
|
452
452
|
agentLinkUnavailable: "Couldn't create the agent link.",
|
|
453
453
|
retryAgentLink: "Retry",
|
|
454
454
|
gifPreview: "GIF preview",
|
|
455
|
+
copyEmailPreview: "Copy email preview",
|
|
456
|
+
emailPreviewCopied: "Email preview copied",
|
|
457
|
+
emailPreviewCopyFailed: "Couldn't copy email preview",
|
|
455
458
|
openPlayer: "Open player",
|
|
456
459
|
chooseFile: "Choose file",
|
|
457
460
|
remove: "Remove",
|
|
@@ -235,7 +235,7 @@ export default function RecordingPage() {
|
|
|
235
235
|
const { session, isLoading: sessionLoading } = useSession();
|
|
236
236
|
const playerRef = useRef<VideoPlayerHandle | null>(null);
|
|
237
237
|
|
|
238
|
-
const [panel, setPanel] = useState<SidePanel>("
|
|
238
|
+
const [panel, setPanel] = useState<SidePanel>("comments");
|
|
239
239
|
const [theaterMode, setTheaterMode] = useState(false);
|
|
240
240
|
const [editing, setEditing] = useState(false);
|
|
241
241
|
const [currentMs, setCurrentMs] = useState(0);
|
|
@@ -311,15 +311,6 @@ export default function RecordingPage() {
|
|
|
311
311
|
}
|
|
312
312
|
}, [panelParam]);
|
|
313
313
|
|
|
314
|
-
const appliedCompactDefaultRef = useRef(false);
|
|
315
|
-
useEffect(() => {
|
|
316
|
-
if (appliedCompactDefaultRef.current) return;
|
|
317
|
-
if (!panelParam && isCompactLayout) {
|
|
318
|
-
appliedCompactDefaultRef.current = true;
|
|
319
|
-
setPanel("comments");
|
|
320
|
-
}
|
|
321
|
-
}, [isCompactLayout, panelParam]);
|
|
322
|
-
|
|
323
314
|
const playerDataQ = useActionQuery<any>(
|
|
324
315
|
"get-recording-player-data",
|
|
325
316
|
{
|
|
@@ -1154,21 +1145,13 @@ export default function RecordingPage() {
|
|
|
1154
1145
|
{label}
|
|
1155
1146
|
</TabsTrigger>
|
|
1156
1147
|
);
|
|
1157
|
-
const triggers =
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
]
|
|
1165
|
-
: [
|
|
1166
|
-
trigger("agent", t("recordingPage.agent")),
|
|
1167
|
-
trigger("comments", t("recordingPage.activity")),
|
|
1168
|
-
trigger("transcript", t("recordingPage.transcript")),
|
|
1169
|
-
trigger("insights", t("recordingPage.insights")),
|
|
1170
|
-
canEdit ? trigger("settings", t("recordingPage.settings")) : null,
|
|
1171
|
-
];
|
|
1148
|
+
const triggers = [
|
|
1149
|
+
trigger("comments", t("recordingPage.activity")),
|
|
1150
|
+
trigger("transcript", t("recordingPage.transcript")),
|
|
1151
|
+
trigger("agent", t("recordingPage.agent")),
|
|
1152
|
+
trigger("insights", t("recordingPage.insights")),
|
|
1153
|
+
canEdit ? trigger("settings", t("recordingPage.settings")) : null,
|
|
1154
|
+
];
|
|
1172
1155
|
|
|
1173
1156
|
return (
|
|
1174
1157
|
<Tabs
|
|
@@ -1197,6 +1180,7 @@ export default function RecordingPage() {
|
|
|
1197
1180
|
scope={recordingScope}
|
|
1198
1181
|
showHeader={false}
|
|
1199
1182
|
showTabBar={false}
|
|
1183
|
+
missingApiKeySetupLayout="sidebar"
|
|
1200
1184
|
emptyStateText={t("recordingPage.askAboutClip")}
|
|
1201
1185
|
dynamicSuggestions={false}
|
|
1202
1186
|
suggestions={
|
|
@@ -1553,6 +1537,7 @@ export default function RecordingPage() {
|
|
|
1553
1537
|
initialVisibility={recording.visibility}
|
|
1554
1538
|
initialRole={role}
|
|
1555
1539
|
videoUrl={recording.videoUrl}
|
|
1540
|
+
thumbnailUrl={recording.thumbnailUrl}
|
|
1556
1541
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1557
1542
|
isLoomRecording={isLoomEmbedBacked}
|
|
1558
1543
|
hasPassword={Boolean(recording.hasPassword)}
|
|
@@ -985,6 +985,7 @@ export default function ShareRoute() {
|
|
|
985
985
|
initialVisibility={recording.visibility}
|
|
986
986
|
initialRole={viewerIsOwner ? "owner" : undefined}
|
|
987
987
|
videoUrl={recording.videoUrl}
|
|
988
|
+
thumbnailUrl={recording.thumbnailUrl}
|
|
988
989
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
989
990
|
isLoomRecording={isLoomEmbedBacked}
|
|
990
991
|
hasPassword={Boolean(recording.hasPassword)}
|
|
@@ -1128,6 +1129,7 @@ export default function ShareRoute() {
|
|
|
1128
1129
|
<AgentPanel
|
|
1129
1130
|
emptyStateText={t("recordingPage.askAboutClip")}
|
|
1130
1131
|
dynamicSuggestions={false}
|
|
1132
|
+
missingApiKeySetupLayout="sidebar"
|
|
1131
1133
|
suggestions={[
|
|
1132
1134
|
t("recordingPage.summarizeClip"),
|
|
1133
1135
|
t("recordingPage.findKeyMoments"),
|
|
@@ -3634,9 +3634,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
3634
3634
|
backdrop-filter: blur(20px);
|
|
3635
3635
|
-webkit-backdrop-filter: blur(20px);
|
|
3636
3636
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
3637
|
-
box-shadow:
|
|
3638
|
-
0 14px 40px rgba(0, 0, 0, 0.45),
|
|
3639
|
-
0 2px 6px rgba(0, 0, 0, 0.3);
|
|
3640
3637
|
color: white;
|
|
3641
3638
|
font-size: 13px;
|
|
3642
3639
|
font-weight: 600;
|