@agent-native/core 0.163.0 → 0.163.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/README.md +1 -1
- package/corpus/templates/analytics/server/plugins/collab.ts +1 -0
- package/corpus/templates/slides/DEVELOPING.md +17 -0
- package/corpus/templates/slides/actions/get-deck-access-status.ts +67 -0
- package/corpus/templates/slides/actions/request-deck-access.ts +569 -0
- package/corpus/templates/slides/agent-native.json +3 -0
- package/corpus/templates/slides/app/components/layout/Header.tsx +2 -0
- package/corpus/templates/slides/app/hooks/use-deck-access.ts +51 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +12 -0
- package/corpus/templates/slides/app/lib/deck-editor-loading.ts +7 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +172 -32
- package/corpus/templates/slides/changelog/2026-08-18-private-deck-links-now-explain-access-and-let-viewers-notify.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +10 -0
- package/corpus/templates/slides/server/plugins/db.ts +17 -0
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/analytics.js +19 -5
- package/dist/client/session-replay.js +349 -100
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts +2 -0
- package/dist/collab/storage.js +7 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.js +11 -1
- package/dist/deploy/build.d.ts +1 -1
- package/dist/deploy/build.js +89 -12
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/tracking-identity.js +2 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/collab-plugin.d.ts +6 -0
- package/dist/server/collab-plugin.js +27 -6
- package/dist/server/deploy-environment.d.ts +1 -1
- package/dist/server/deploy-environment.js +40 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/sentry-config.js +8 -4
- package/dist/server/sentry.js +4 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/tracking/registry.js +8 -6
- package/dist/vite/client.js +5 -0
- package/docs/content/deployment.mdx +19 -5
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useActionMutation,
|
|
3
|
+
useActionQuery,
|
|
4
|
+
} from "@agent-native/core/client/hooks";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
|
|
7
|
+
export type DeckAccessStatusResponse = {
|
|
8
|
+
exists: boolean;
|
|
9
|
+
hasAccess: boolean;
|
|
10
|
+
signedIn: boolean;
|
|
11
|
+
viewerEmail: string | null;
|
|
12
|
+
viewerName: string | null;
|
|
13
|
+
role: "owner" | "viewer" | "commenter" | "editor" | "admin" | null;
|
|
14
|
+
visibility: "private" | "org" | "public" | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function useDeckAccessStatus(deckId?: string) {
|
|
18
|
+
return useActionQuery<DeckAccessStatusResponse>(
|
|
19
|
+
"get-deck-access-status",
|
|
20
|
+
{ deckId: deckId ?? "" },
|
|
21
|
+
{
|
|
22
|
+
enabled: Boolean(deckId),
|
|
23
|
+
retry: false,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type RequestDeckAccessResult = {
|
|
29
|
+
ok: true;
|
|
30
|
+
alreadyHasAccess: boolean;
|
|
31
|
+
notifiedOwner: boolean;
|
|
32
|
+
requestId?: string;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function showActionError(message: string) {
|
|
37
|
+
return (error: Error) => {
|
|
38
|
+
toast.error(
|
|
39
|
+
error.message
|
|
40
|
+
? error.message.replace(/^Action [\w-]+ failed:\s*/, "")
|
|
41
|
+
: message,
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useRequestDeckAccess() {
|
|
47
|
+
return useActionMutation<RequestDeckAccessResult, { deckId: string }>(
|
|
48
|
+
"request-deck-access",
|
|
49
|
+
{ onError: showActionError("Failed to request access") },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -565,6 +565,18 @@ const messages = {
|
|
|
565
565
|
"This link points to a team presentation. Join the team shown above and the deck will open here automatically.",
|
|
566
566
|
deckUnavailableDescription:
|
|
567
567
|
"This deck may have been removed, or your account does not have access to it.",
|
|
568
|
+
privateDeckTitle: "This deck is private",
|
|
569
|
+
privateDeckDescription:
|
|
570
|
+
"Ask the owner for access to open this deck. They’ll be notified when you send a request.",
|
|
571
|
+
signedInAs: "You're signed in as",
|
|
572
|
+
accessRequestSentDescription:
|
|
573
|
+
"The owner has been notified. You can open this deck once access is granted.",
|
|
574
|
+
accessRequestRecordedDescription:
|
|
575
|
+
"Your request has been recorded. You can open this deck once access is granted.",
|
|
576
|
+
requestAccessPending: "Sending request...",
|
|
577
|
+
accessRequestSent: "Request sent",
|
|
578
|
+
requestAccess: "Request access",
|
|
579
|
+
signInToRequestAccess: "Sign in to request access",
|
|
568
580
|
backToDecks: "Back to Decks",
|
|
569
581
|
tryAgain: "Try again",
|
|
570
582
|
imageUploadFailed: "Image upload failed",
|
|
@@ -12,6 +12,7 @@ export function shouldShowDeckEditorSkeleton({
|
|
|
12
12
|
accessCheckKey,
|
|
13
13
|
checkedAccessKey,
|
|
14
14
|
retrying,
|
|
15
|
+
privateDeckAccessConfirmed,
|
|
15
16
|
}: {
|
|
16
17
|
deckFound: boolean;
|
|
17
18
|
decksLoading: boolean;
|
|
@@ -19,7 +20,13 @@ export function shouldShowDeckEditorSkeleton({
|
|
|
19
20
|
accessCheckKey: string | null;
|
|
20
21
|
checkedAccessKey: string | null;
|
|
21
22
|
retrying: boolean;
|
|
23
|
+
privateDeckAccessConfirmed: boolean;
|
|
22
24
|
}): boolean {
|
|
25
|
+
// A private deck intentionally returns 404 from the content action. The
|
|
26
|
+
// metadata-only access check is the source of truth for this state, so do
|
|
27
|
+
// not leave the user on the editor skeleton while that protected fetch
|
|
28
|
+
// settles.
|
|
29
|
+
if (privateDeckAccessConfirmed) return false;
|
|
23
30
|
if (decksLoading) return true;
|
|
24
31
|
if (deckFound || !accessCheckKey) return false;
|
|
25
32
|
return orgLoading || checkedAccessKey !== accessCheckKey || retrying;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { useGuidedQuestionFlow } from "@agent-native/core/client/agent-chat";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
agentNativePath,
|
|
4
|
+
appBasePath,
|
|
5
|
+
} from "@agent-native/core/client/api-path";
|
|
3
6
|
import {
|
|
4
7
|
useCollaborativeDoc,
|
|
5
8
|
emailToColor,
|
|
@@ -8,6 +11,7 @@ import {
|
|
|
8
11
|
import { useSession } from "@agent-native/core/client/hooks";
|
|
9
12
|
import { useT } from "@agent-native/core/client/i18n";
|
|
10
13
|
import { useOrg } from "@agent-native/core/client/org";
|
|
14
|
+
import { buildSignInReturnHref } from "@agent-native/core/client/ui";
|
|
11
15
|
import {
|
|
12
16
|
DndContext,
|
|
13
17
|
closestCenter,
|
|
@@ -17,9 +21,13 @@ import {
|
|
|
17
21
|
DragEndEvent,
|
|
18
22
|
} from "@dnd-kit/core";
|
|
19
23
|
import {
|
|
24
|
+
IconAt,
|
|
20
25
|
IconArrowLeft,
|
|
21
26
|
IconLock,
|
|
27
|
+
IconLoader2,
|
|
28
|
+
IconLogin2,
|
|
22
29
|
IconRefresh,
|
|
30
|
+
IconUserPlus,
|
|
23
31
|
IconUsersGroup,
|
|
24
32
|
} from "@tabler/icons-react";
|
|
25
33
|
import { nanoid } from "nanoid";
|
|
@@ -67,6 +75,11 @@ import {
|
|
|
67
75
|
useSaveState,
|
|
68
76
|
} from "@/context/DeckContext";
|
|
69
77
|
import { useAgentGenerating } from "@/hooks/use-agent-generating";
|
|
78
|
+
import {
|
|
79
|
+
useDeckAccessStatus,
|
|
80
|
+
useRequestDeckAccess,
|
|
81
|
+
type DeckAccessStatusResponse,
|
|
82
|
+
} from "@/hooks/use-deck-access";
|
|
70
83
|
import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
|
|
71
84
|
import { useDeckPresence } from "@/hooks/use-deck-presence";
|
|
72
85
|
import { useDeckRole } from "@/hooks/use-deck-role";
|
|
@@ -109,37 +122,70 @@ import { shortcutLabel } from "@/lib/utils";
|
|
|
109
122
|
type EditorSidePanel = "comments" | null;
|
|
110
123
|
|
|
111
124
|
function MissingDeckAccessPane({
|
|
125
|
+
accessStatus,
|
|
126
|
+
accessStatusError,
|
|
127
|
+
accessStatusLoading,
|
|
112
128
|
hasTeamJoinOption,
|
|
113
129
|
orgLoading,
|
|
114
130
|
orgError,
|
|
131
|
+
requestAccessPending,
|
|
132
|
+
accessRequestSent,
|
|
133
|
+
accessRequestNotified,
|
|
134
|
+
signedIn,
|
|
135
|
+
viewerEmail,
|
|
115
136
|
refreshing,
|
|
137
|
+
onRequestAccess,
|
|
138
|
+
onSignIn,
|
|
116
139
|
onRetry,
|
|
117
140
|
onBack,
|
|
118
141
|
}: {
|
|
142
|
+
accessStatus: DeckAccessStatusResponse | null;
|
|
143
|
+
accessStatusError: boolean;
|
|
144
|
+
accessStatusLoading: boolean;
|
|
119
145
|
hasTeamJoinOption: boolean;
|
|
120
146
|
orgLoading: boolean;
|
|
121
147
|
orgError: boolean;
|
|
148
|
+
requestAccessPending: boolean;
|
|
149
|
+
accessRequestSent: boolean;
|
|
150
|
+
accessRequestNotified: boolean;
|
|
151
|
+
signedIn: boolean;
|
|
152
|
+
viewerEmail: string | null;
|
|
122
153
|
refreshing: boolean;
|
|
154
|
+
onRequestAccess: () => void;
|
|
155
|
+
onSignIn: () => void;
|
|
123
156
|
onRetry: () => void;
|
|
124
157
|
onBack: () => void;
|
|
125
158
|
}) {
|
|
126
159
|
const t = useT();
|
|
160
|
+
const privateDeck = Boolean(
|
|
161
|
+
accessStatus?.exists &&
|
|
162
|
+
!accessStatus.hasAccess &&
|
|
163
|
+
accessStatus.visibility === "private",
|
|
164
|
+
);
|
|
165
|
+
const checkingAccess = !privateDeck && (accessStatusLoading || orgLoading);
|
|
166
|
+
const accessCheckFailed = accessStatusError || orgError;
|
|
127
167
|
const Icon =
|
|
128
|
-
|
|
129
|
-
|
|
168
|
+
privateDeck || (!hasTeamJoinOption && !checkingAccess && !accessCheckFailed)
|
|
169
|
+
? IconLock
|
|
170
|
+
: IconUsersGroup;
|
|
171
|
+
const title = checkingAccess
|
|
130
172
|
? t("deckEditor.lookingForDeck")
|
|
131
|
-
:
|
|
132
|
-
? t("deckEditor.
|
|
133
|
-
:
|
|
134
|
-
? t("deckEditor.
|
|
135
|
-
:
|
|
136
|
-
|
|
173
|
+
: privateDeck
|
|
174
|
+
? t("deckEditor.privateDeckTitle")
|
|
175
|
+
: accessCheckFailed
|
|
176
|
+
? t("deckEditor.teamAccessCheckFailed")
|
|
177
|
+
: hasTeamJoinOption
|
|
178
|
+
? t("deckEditor.joinTeamToOpen")
|
|
179
|
+
: t("deckEditor.deckUnavailable");
|
|
180
|
+
const description = checkingAccess
|
|
137
181
|
? t("deckEditor.checkingSharedAccess")
|
|
138
|
-
:
|
|
139
|
-
? t("deckEditor.
|
|
140
|
-
:
|
|
141
|
-
? t("deckEditor.
|
|
142
|
-
:
|
|
182
|
+
: privateDeck
|
|
183
|
+
? t("deckEditor.privateDeckDescription")
|
|
184
|
+
: accessCheckFailed
|
|
185
|
+
? t("deckEditor.verifySharedAccessFailed")
|
|
186
|
+
: hasTeamJoinOption
|
|
187
|
+
? t("deckEditor.joinTeamDescription")
|
|
188
|
+
: t("deckEditor.deckUnavailableDescription");
|
|
143
189
|
|
|
144
190
|
return (
|
|
145
191
|
<div className="flex min-h-screen items-center justify-center bg-background px-4 py-10">
|
|
@@ -153,21 +199,64 @@ function MissingDeckAccessPane({
|
|
|
153
199
|
</div>
|
|
154
200
|
</div>
|
|
155
201
|
<p className="text-sm leading-6 text-muted-foreground">{description}</p>
|
|
156
|
-
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
{privateDeck && viewerEmail ? (
|
|
203
|
+
<div className="mt-4 flex items-center gap-2 rounded-md border border-border bg-muted/35 px-3 py-2 text-sm">
|
|
204
|
+
<IconAt className="size-4 shrink-0 text-muted-foreground" />
|
|
205
|
+
<span className="min-w-0 truncate text-muted-foreground">
|
|
206
|
+
{t("deckEditor.signedInAs")}{" "}
|
|
207
|
+
<span className="font-medium text-foreground">{viewerEmail}</span>
|
|
208
|
+
</span>
|
|
209
|
+
</div>
|
|
210
|
+
) : null}
|
|
211
|
+
{privateDeck && accessRequestSent ? (
|
|
212
|
+
<div className="mt-3 rounded-md border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700 dark:text-emerald-300">
|
|
213
|
+
{t(
|
|
214
|
+
accessRequestNotified
|
|
215
|
+
? "deckEditor.accessRequestSentDescription"
|
|
216
|
+
: "deckEditor.accessRequestRecordedDescription",
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
) : null}
|
|
220
|
+
<div className="mt-5 flex flex-col gap-2">
|
|
221
|
+
{privateDeck ? (
|
|
222
|
+
<Button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={signedIn ? onRequestAccess : onSignIn}
|
|
225
|
+
disabled={requestAccessPending || accessRequestSent}
|
|
226
|
+
>
|
|
227
|
+
{requestAccessPending ? (
|
|
228
|
+
<IconLoader2 className="size-4 animate-spin" />
|
|
229
|
+
) : signedIn ? (
|
|
230
|
+
<IconUserPlus className="size-4" />
|
|
231
|
+
) : (
|
|
232
|
+
<IconLogin2 className="size-4" />
|
|
233
|
+
)}
|
|
234
|
+
{requestAccessPending
|
|
235
|
+
? t("deckEditor.requestAccessPending")
|
|
236
|
+
: accessRequestSent
|
|
237
|
+
? t("deckEditor.accessRequestSent")
|
|
238
|
+
: signedIn
|
|
239
|
+
? t("deckEditor.requestAccess")
|
|
240
|
+
: t("deckEditor.signInToRequestAccess")}
|
|
241
|
+
</Button>
|
|
242
|
+
) : null}
|
|
243
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
244
|
+
<Button type="button" variant="outline" onClick={onBack}>
|
|
245
|
+
<IconArrowLeft className="size-4" />
|
|
246
|
+
{t("deckEditor.backToDecks")}
|
|
247
|
+
</Button>
|
|
248
|
+
<Button
|
|
249
|
+
type="button"
|
|
250
|
+
variant={privateDeck ? "ghost" : "default"}
|
|
251
|
+
onClick={onRetry}
|
|
252
|
+
disabled={refreshing || checkingAccess}
|
|
253
|
+
>
|
|
254
|
+
<IconRefresh
|
|
255
|
+
className={refreshing ? "size-4 animate-spin" : "size-4"}
|
|
256
|
+
/>
|
|
257
|
+
{t("deckEditor.tryAgain")}
|
|
258
|
+
</Button>
|
|
259
|
+
</div>
|
|
171
260
|
</div>
|
|
172
261
|
</div>
|
|
173
262
|
</div>
|
|
@@ -200,6 +289,7 @@ export default function DeckEditor() {
|
|
|
200
289
|
const { id } = useParams<{ id: string }>();
|
|
201
290
|
const navigate = useNavigate();
|
|
202
291
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
292
|
+
const { session, isLoading: sessionLoading } = useSession();
|
|
203
293
|
const {
|
|
204
294
|
getDeck,
|
|
205
295
|
reloadDecks,
|
|
@@ -219,6 +309,8 @@ export default function DeckEditor() {
|
|
|
219
309
|
loading,
|
|
220
310
|
loadError,
|
|
221
311
|
} = useDecks();
|
|
312
|
+
const deckAccessStatusQuery = useDeckAccessStatus(id);
|
|
313
|
+
const requestDeckAccessMutation = useRequestDeckAccess();
|
|
222
314
|
const [activeSlideId, setActiveSlideId] = useState<string | null>(null);
|
|
223
315
|
const [inlineEditActive, setInlineEditActive] = useState(false);
|
|
224
316
|
const [addSlideGenerating, setAddSlideGenerating] = useState(false);
|
|
@@ -294,6 +386,10 @@ export default function DeckEditor() {
|
|
|
294
386
|
const [wideContextToolbarSlot, setWideContextToolbarSlot] =
|
|
295
387
|
useState<HTMLDivElement | null>(null);
|
|
296
388
|
const [retryingMissingDeck, setRetryingMissingDeck] = useState(false);
|
|
389
|
+
const [accessRequestSentDeckId, setAccessRequestSentDeckId] = useState<
|
|
390
|
+
string | null
|
|
391
|
+
>(null);
|
|
392
|
+
const [accessRequestNotified, setAccessRequestNotified] = useState(false);
|
|
297
393
|
const [checkedDeckAccessKey, setCheckedDeckAccessKey] = useState<
|
|
298
394
|
string | null
|
|
299
395
|
>(null);
|
|
@@ -349,6 +445,7 @@ export default function DeckEditor() {
|
|
|
349
445
|
const uploadInputRef = useRef<HTMLInputElement>(null);
|
|
350
446
|
|
|
351
447
|
const deck = getDeck(id || "");
|
|
448
|
+
const deckAccessStatus = deckAccessStatusQuery.data ?? null;
|
|
352
449
|
const fitDims = getAspectRatioDims(deck?.aspectRatio);
|
|
353
450
|
const currentDeckAccessKey = deckAccessCheckKey(id, org?.orgId);
|
|
354
451
|
const hasTeamJoinOption =
|
|
@@ -511,6 +608,32 @@ export default function DeckEditor() {
|
|
|
511
608
|
}
|
|
512
609
|
}, [refetchOrg, reloadDecks]);
|
|
513
610
|
|
|
611
|
+
const openSignIn = useCallback(() => {
|
|
612
|
+
window.location.href = buildSignInReturnHref();
|
|
613
|
+
}, []);
|
|
614
|
+
|
|
615
|
+
const requestDeckAccess = useCallback(() => {
|
|
616
|
+
if (!id) return;
|
|
617
|
+
requestDeckAccessMutation.mutate(
|
|
618
|
+
{ deckId: id },
|
|
619
|
+
{
|
|
620
|
+
onSuccess: (result) => {
|
|
621
|
+
setAccessRequestSentDeckId(id);
|
|
622
|
+
setAccessRequestNotified(result.notifiedOwner);
|
|
623
|
+
toast.success(result.message);
|
|
624
|
+
if (result.alreadyHasAccess) void reloadDecks();
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
);
|
|
628
|
+
}, [id, reloadDecks, requestDeckAccessMutation]);
|
|
629
|
+
|
|
630
|
+
useEffect(() => {
|
|
631
|
+
if (accessRequestSentDeckId && accessRequestSentDeckId !== id) {
|
|
632
|
+
setAccessRequestSentDeckId(null);
|
|
633
|
+
setAccessRequestNotified(false);
|
|
634
|
+
}
|
|
635
|
+
}, [accessRequestSentDeckId, id]);
|
|
636
|
+
|
|
514
637
|
// The final generation write can race the last sync event. Pull the
|
|
515
638
|
// authoritative open deck when the run settles so a stale canvas does not
|
|
516
639
|
// require a browser refresh to reveal completed slides.
|
|
@@ -1204,7 +1327,6 @@ export default function DeckEditor() {
|
|
|
1204
1327
|
useRef<typeof deck extends undefined ? null : any>(null);
|
|
1205
1328
|
|
|
1206
1329
|
// Session for collab user identity
|
|
1207
|
-
const { session } = useSession();
|
|
1208
1330
|
const currentUser = session?.email
|
|
1209
1331
|
? {
|
|
1210
1332
|
email: session.email,
|
|
@@ -1242,7 +1364,7 @@ export default function DeckEditor() {
|
|
|
1242
1364
|
recentEdits: deckRecentEdits,
|
|
1243
1365
|
awareness: deckPresenceAwareness,
|
|
1244
1366
|
} = useDeckPresence({
|
|
1245
|
-
deckId: id ?? null,
|
|
1367
|
+
deckId: deck ? (id ?? null) : null,
|
|
1246
1368
|
activeSlideId: activeSlideId,
|
|
1247
1369
|
user: currentUser,
|
|
1248
1370
|
});
|
|
@@ -1254,7 +1376,10 @@ export default function DeckEditor() {
|
|
|
1254
1376
|
const agentActive = generating || deckAgentActive || slideAgentActive;
|
|
1255
1377
|
|
|
1256
1378
|
// Comments for the current slide (for badge count)
|
|
1257
|
-
const currentSlideCommentsQuery = useSlideComments(
|
|
1379
|
+
const currentSlideCommentsQuery = useSlideComments(
|
|
1380
|
+
deck ? (id ?? null) : null,
|
|
1381
|
+
activeSlideId,
|
|
1382
|
+
);
|
|
1258
1383
|
const currentSlideThreads: CommentThread[] =
|
|
1259
1384
|
currentSlideCommentsQuery.data ?? [];
|
|
1260
1385
|
const unresolvedCommentCount = currentSlideThreads.filter(
|
|
@@ -1269,6 +1394,11 @@ export default function DeckEditor() {
|
|
|
1269
1394
|
accessCheckKey: currentDeckAccessKey,
|
|
1270
1395
|
checkedAccessKey: checkedDeckAccessKey,
|
|
1271
1396
|
retrying: retryingMissingDeck,
|
|
1397
|
+
privateDeckAccessConfirmed: Boolean(
|
|
1398
|
+
deckAccessStatus?.exists &&
|
|
1399
|
+
!deckAccessStatus.hasAccess &&
|
|
1400
|
+
deckAccessStatus.visibility === "private",
|
|
1401
|
+
),
|
|
1272
1402
|
})
|
|
1273
1403
|
) {
|
|
1274
1404
|
return <DeckEditorSkeleton label={t("deckEditor.lookingForDeck")} />;
|
|
@@ -1276,10 +1406,20 @@ export default function DeckEditor() {
|
|
|
1276
1406
|
if (!deck || !id) {
|
|
1277
1407
|
return (
|
|
1278
1408
|
<MissingDeckAccessPane
|
|
1409
|
+
accessStatus={deckAccessStatus}
|
|
1410
|
+
accessStatusError={deckAccessStatusQuery.isError}
|
|
1411
|
+
accessStatusLoading={loading || deckAccessStatusQuery.isLoading}
|
|
1279
1412
|
hasTeamJoinOption={hasTeamJoinOption}
|
|
1280
1413
|
orgLoading={orgLoading}
|
|
1281
1414
|
orgError={orgError || loadError}
|
|
1415
|
+
requestAccessPending={requestDeckAccessMutation.isPending}
|
|
1416
|
+
accessRequestSent={accessRequestSentDeckId === id}
|
|
1417
|
+
accessRequestNotified={accessRequestNotified}
|
|
1418
|
+
signedIn={Boolean(session) && !sessionLoading}
|
|
1419
|
+
viewerEmail={session?.email ?? deckAccessStatus?.viewerEmail ?? null}
|
|
1282
1420
|
refreshing={retryingMissingDeck}
|
|
1421
|
+
onRequestAccess={requestDeckAccess}
|
|
1422
|
+
onSignIn={openSignIn}
|
|
1283
1423
|
onRetry={() => void retryOpenDeck()}
|
|
1284
1424
|
onBack={() => navigate("/")}
|
|
1285
1425
|
/>
|
|
@@ -83,3 +83,13 @@ export const slideComments = table("slide_comments", {
|
|
|
83
83
|
createdAt: text("created_at").notNull().default(now()),
|
|
84
84
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
export const deckEvents = table("deck_events", {
|
|
88
|
+
id: text("id").primaryKey(),
|
|
89
|
+
deckId: text("deck_id").notNull(),
|
|
90
|
+
type: text("type").notNull(),
|
|
91
|
+
message: text("message").notNull(),
|
|
92
|
+
payload: text("payload"),
|
|
93
|
+
createdBy: text("created_by").notNull().default("human"),
|
|
94
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
95
|
+
});
|
|
@@ -235,6 +235,23 @@ export const runSlidesMigrations = runMigrations(
|
|
|
235
235
|
name: "slides-share-design-system-snapshot",
|
|
236
236
|
sql: `ALTER TABLE deck_share_links ADD COLUMN IF NOT EXISTS design_system_data TEXT`,
|
|
237
237
|
},
|
|
238
|
+
// v22: durable access-request events for private deck links. The request
|
|
239
|
+
// action also sends the owner an email when outbound email is configured,
|
|
240
|
+
// but the event remains the source of truth when it is not.
|
|
241
|
+
{
|
|
242
|
+
version: 22,
|
|
243
|
+
name: "slides-deck-access-requests",
|
|
244
|
+
sql: `CREATE TABLE IF NOT EXISTS deck_events (
|
|
245
|
+
id TEXT PRIMARY KEY,
|
|
246
|
+
deck_id TEXT NOT NULL,
|
|
247
|
+
type TEXT NOT NULL,
|
|
248
|
+
message TEXT NOT NULL,
|
|
249
|
+
payload TEXT,
|
|
250
|
+
created_by TEXT NOT NULL DEFAULT 'human',
|
|
251
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
252
|
+
);
|
|
253
|
+
CREATE INDEX IF NOT EXISTS deck_events_deck_created_idx ON deck_events (deck_id, created_at)`,
|
|
254
|
+
},
|
|
238
255
|
],
|
|
239
256
|
{ table: "slides_migrations" },
|
|
240
257
|
);
|
|
@@ -21,6 +21,7 @@ declare global {
|
|
|
21
21
|
workspaceRuntime?: boolean;
|
|
22
22
|
sentryDsn?: string;
|
|
23
23
|
sentryEnvironment?: string;
|
|
24
|
+
deploymentEnvironment?: string;
|
|
24
25
|
/**
|
|
25
26
|
* Public PostHog project key + host. Publishable and identical for every
|
|
26
27
|
* visitor, so it ships inside the CDN-cached SSR shell alongside the
|
package/dist/client/analytics.js
CHANGED
|
@@ -3,6 +3,7 @@ import { llmConnectionTrackingProperties, } from "../shared/llm-connection.js";
|
|
|
3
3
|
import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
|
|
4
4
|
import { getAnalyticsClientPlatform } from "./analytics-platform.js";
|
|
5
5
|
import { getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
|
|
6
|
+
import { injectedAgentNativeConfig } from "./app-config.js";
|
|
6
7
|
export { clearAnalyticsSessionId, setAnalyticsSessionId, } from "./analytics-session.js";
|
|
7
8
|
import { fetchAgentEngineStatus, fetchAuthSessionStatus, } from "./client-status-requests.js";
|
|
8
9
|
import { installErrorCapture, } from "./error-capture.js";
|
|
@@ -681,6 +682,16 @@ function getClientSentryDsn() {
|
|
|
681
682
|
window.__AGENT_NATIVE_CONFIG__?.sentryDsn ||
|
|
682
683
|
resolveClientSentryDsnFromKeyProject(env));
|
|
683
684
|
}
|
|
685
|
+
function resolveClientDeploymentEnvironment() {
|
|
686
|
+
const env = import.meta.env ?? {};
|
|
687
|
+
return (window.__AGENT_NATIVE_CONFIG__?.deploymentEnvironment ||
|
|
688
|
+
injectedAgentNativeConfig().deployment?.environment ||
|
|
689
|
+
env.VITE_AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT ||
|
|
690
|
+
env.VITE_SENTRY_ENVIRONMENT ||
|
|
691
|
+
window.__AGENT_NATIVE_CONFIG__?.sentryEnvironment ||
|
|
692
|
+
env.MODE ||
|
|
693
|
+
"production");
|
|
694
|
+
}
|
|
684
695
|
function captureWithSentry(module, error, context) {
|
|
685
696
|
return module.withScope((scope) => {
|
|
686
697
|
if (context.tags) {
|
|
@@ -721,10 +732,12 @@ function ensureSentry(loadWithoutDsn = false) {
|
|
|
721
732
|
}
|
|
722
733
|
module.init({
|
|
723
734
|
dsn,
|
|
724
|
-
environment:
|
|
725
|
-
import.meta.env?.MODE ||
|
|
726
|
-
"production",
|
|
735
|
+
environment: resolveClientDeploymentEnvironment(),
|
|
727
736
|
beforeSend(event) {
|
|
737
|
+
event.tags = {
|
|
738
|
+
...event.tags,
|
|
739
|
+
deployment_environment: resolveClientDeploymentEnvironment(),
|
|
740
|
+
};
|
|
728
741
|
if (shouldDropBrowserSentryNoise(event)) {
|
|
729
742
|
return null;
|
|
730
743
|
}
|
|
@@ -756,6 +769,7 @@ function ensureSentry(loadWithoutDsn = false) {
|
|
|
756
769
|
},
|
|
757
770
|
});
|
|
758
771
|
module.setTag("runtime", "browser");
|
|
772
|
+
module.setTag("deployment_environment", resolveClientDeploymentEnvironment());
|
|
759
773
|
_sentryInitialized = true;
|
|
760
774
|
// Flush any user/tag that was set before init.
|
|
761
775
|
if (_pendingSentryUser !== undefined) {
|
|
@@ -1169,8 +1183,7 @@ function maybeInstallErrorCapture(config) {
|
|
|
1169
1183
|
send: sendExceptionEvent,
|
|
1170
1184
|
getSessionContext: errorCaptureSessionContext,
|
|
1171
1185
|
emitReplayEvent: emitExceptionToReplay,
|
|
1172
|
-
environment: options.environment ||
|
|
1173
|
-
import.meta.env?.MODE,
|
|
1186
|
+
environment: options.environment || resolveClientDeploymentEnvironment(),
|
|
1174
1187
|
...(options.release ? { release: options.release } : {}),
|
|
1175
1188
|
...(options.captureGlobalErrors !== undefined
|
|
1176
1189
|
? { captureGlobalErrors: options.captureGlobalErrors }
|
|
@@ -1410,6 +1423,7 @@ function resolveProps(name, params) {
|
|
|
1410
1423
|
}
|
|
1411
1424
|
const llmProps = llmConnectionTrackingProperties(_llmConnectionStatus);
|
|
1412
1425
|
const enriched = { ...withTemplate };
|
|
1426
|
+
enriched.deployment_environment = resolveClientDeploymentEnvironment();
|
|
1413
1427
|
for (const [key, value] of Object.entries(llmProps)) {
|
|
1414
1428
|
if (enriched[key] === undefined)
|
|
1415
1429
|
enriched[key] = value;
|