@agent-native/core 0.162.0 → 0.163.1
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/sharing/SKILL.md +27 -2
- package/corpus/templates/analytics/server/plugins/collab.ts +1 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/content/actions/create-content-database.ts +1 -1
- package/corpus/templates/content/actions/create-document.ts +1 -1
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
- 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/editor/GoogleDriveConnectionCta.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
- 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 +173 -33
- 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/handlers/request-auth-context.ts +1 -0
- package/corpus/templates/slides/server/plugins/db.ts +17 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/app-config/workspace.d.ts +1 -0
- package/dist/app-config/workspace.js +8 -0
- package/dist/chat-threads/schema.d.ts +2 -2
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/analytics.js +19 -5
- package/dist/client/org/TeamPage.js +15 -2
- package/dist/client/org/hooks.d.ts +2 -0
- package/dist/client/org/hooks.js +12 -0
- package/dist/client/org/index.d.ts +2 -2
- package/dist/client/org/index.js +1 -1
- package/dist/client/sharing/ShareButton.js +43 -18
- package/dist/client/sharing/useShareButtonController.d.ts +12 -2
- package/dist/client/sharing/useShareButtonController.js +38 -8
- package/dist/collab/awareness.d.ts +2 -2
- 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/config.js +11 -1
- package/dist/dashboard-storage/schema.d.ts +2 -2
- package/dist/data-programs/schema.d.ts +2 -2
- package/dist/deploy/build.d.ts +1 -1
- package/dist/deploy/build.js +89 -12
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/extensions/store.js +3 -0
- package/dist/localization/core-messages/ar-SA.js +1 -0
- package/dist/localization/core-messages/de-DE.js +1 -0
- package/dist/localization/core-messages/en-US.d.ts +1 -0
- package/dist/localization/core-messages/en-US.js +1 -0
- package/dist/localization/core-messages/es-ES.js +1 -0
- package/dist/localization/core-messages/fr-FR.js +1 -0
- package/dist/localization/core-messages/hi-IN.js +1 -0
- package/dist/localization/core-messages/ja-JP.js +1 -0
- package/dist/localization/core-messages/ko-KR.js +1 -0
- package/dist/localization/core-messages/pt-BR.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +1 -0
- package/dist/localization/core-messages/zh-TW.js +1 -0
- package/dist/localization/core-messages.d.ts +1 -0
- package/dist/localization/default-messages.d.ts +4 -0
- package/dist/localization/default-messages.js +4 -0
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/tracking-identity.js +2 -0
- package/dist/org/handlers.d.ts +6 -1
- package/dist/org/handlers.js +43 -0
- package/dist/org/index.d.ts +3 -3
- package/dist/org/index.js +2 -2
- package/dist/org/migrations.js +33 -0
- package/dist/org/plugin.js +10 -1
- package/dist/org/schema.d.ts +315 -0
- package/dist/org/schema.js +20 -1
- package/dist/org/types.d.ts +3 -0
- package/dist/org/workspace-app-access.d.ts +10 -0
- package/dist/org/workspace-app-access.js +179 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +14 -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/poll.js +6 -0
- 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/sharing/access.d.ts +2 -2
- package/dist/sharing/access.js +56 -3
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +26 -1
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/actions/share-resource.js +23 -4
- package/dist/sharing/actions/unshare-resource.d.ts +1 -1
- package/dist/sharing/actions/unshare-resource.js +1 -1
- package/dist/sharing/registry.d.ts +12 -0
- package/dist/sharing/schema.d.ts +4 -4
- package/dist/sharing/schema.js +2 -2
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/tracking/registry.js +8 -6
- package/dist/vite/client.js +5 -0
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
- package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
- package/dist/workspace-connections/groups.d.ts +2 -0
- package/dist/workspace-connections/groups.js +16 -5
- package/docs/content/deployment.mdx +19 -5
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentToggleButton } from "@agent-native/core/client/agent-chat";
|
|
2
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { NotificationsBell } from "@agent-native/core/client/notifications";
|
|
3
4
|
import { RunsTray } from "@agent-native/core/client/progress";
|
|
4
5
|
import {
|
|
5
6
|
useHeaderTitle,
|
|
@@ -68,6 +69,7 @@ export function Header() {
|
|
|
68
69
|
</div>
|
|
69
70
|
<div className="flex items-center gap-2 shrink-0">
|
|
70
71
|
{actions}
|
|
72
|
+
<NotificationsBell pollMs={30_000} />
|
|
71
73
|
<RunsTray pollMs={0} />
|
|
72
74
|
<AgentToggleButton />
|
|
73
75
|
</div>
|
|
@@ -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
|
/>
|
|
@@ -1629,7 +1769,7 @@ export default function DeckEditor() {
|
|
|
1629
1769
|
slideAgentActive ||
|
|
1630
1770
|
(deckAgentActive && agentSlideId === currentSlide.id) ||
|
|
1631
1771
|
(isNewDeckGenerating &&
|
|
1632
|
-
currentSlide.id === deck.slides.
|
|
1772
|
+
currentSlide.id === deck.slides[deck.slides.length - 1]?.id)
|
|
1633
1773
|
}
|
|
1634
1774
|
recentEdits={deckRecentEdits}
|
|
1635
1775
|
onComment={(quotedText) => {
|
|
@@ -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
|
+
});
|
|
@@ -21,6 +21,7 @@ export interface SlidesRequestAuthContext {
|
|
|
21
21
|
*/
|
|
22
22
|
export class SlidesSessionLookupError extends Error {
|
|
23
23
|
readonly statusCode = 503;
|
|
24
|
+
readonly cause: unknown;
|
|
24
25
|
constructor(cause: unknown) {
|
|
25
26
|
super("Could not verify your session. Please try again.");
|
|
26
27
|
this.name = "SlidesSessionLookupError";
|
|
@@ -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
|
);
|
|
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
|
|
|
18
18
|
|
|
19
19
|
This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
|
|
20
20
|
|
|
21
|
+
Workspace apps are the intentional organization-scoped exception: their default
|
|
22
|
+
visibility is `org`, configurable by an organization owner/admin. The creator is
|
|
23
|
+
stored as the app owner, and the creator or an organization owner/admin can
|
|
24
|
+
change the app's visibility or manage its share grants.
|
|
25
|
+
|
|
21
26
|
## Concepts
|
|
22
27
|
|
|
23
28
|
### Three visibility levels
|
|
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
|
|
|
26
31
|
- **`org`** — owner + explicit grants + anyone in the same org (read-only).
|
|
27
32
|
- **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
|
|
28
33
|
|
|
29
|
-
Visibility is coarse. Explicit share grants are fine-grained (per user
|
|
34
|
+
Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
|
|
35
|
+
organization group, or per org).
|
|
30
36
|
|
|
31
37
|
### Roles on a share grant
|
|
32
38
|
|
|
@@ -101,6 +107,7 @@ registerShareableResource({
|
|
|
101
107
|
// ...
|
|
102
108
|
allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
|
|
103
109
|
requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
|
|
110
|
+
supportsGroupShares: true, // enable reusable organization groups for this resource
|
|
104
111
|
});
|
|
105
112
|
```
|
|
106
113
|
|
|
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
|
|
|
111
118
|
|
|
112
119
|
Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
|
|
113
120
|
|
|
121
|
+
### Reusable organization groups
|
|
122
|
+
|
|
123
|
+
The workspace organization model stores reusable groups in
|
|
124
|
+
`workspace_user_groups`. Organization owners/admins manage the groups and their
|
|
125
|
+
members through the existing group actions. Set `supportsGroupShares: true` on a
|
|
126
|
+
resource registration to enable `principalType: "group"` in the generic share
|
|
127
|
+
actions and the shared popover's autocomplete. Group ids are always checked
|
|
128
|
+
against the resource's organization, and membership is evaluated at access time
|
|
129
|
+
so group changes apply to existing shares.
|
|
130
|
+
|
|
131
|
+
Workspace apps opt into this flag and use the organization default visibility
|
|
132
|
+
setting. The app creator is the durable `owner_email`, while organization
|
|
133
|
+
owners/admins receive admin access through the registration's
|
|
134
|
+
`canManageAccess` callback.
|
|
135
|
+
|
|
114
136
|
## Filter list/read queries
|
|
115
137
|
|
|
116
138
|
```ts
|
|
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
|
|
|
197
219
|
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
220
|
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
221
|
content through the shared section shell.
|
|
222
|
+
- When `supportsGroupShares` is enabled, keep the existing people input as the
|
|
223
|
+
only entry point and include organization groups in its autocomplete. Do not
|
|
224
|
+
add a separate group-management surface to each resource.
|
|
200
225
|
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
226
|
action (for example, an embed-code editor), but they should retain the same
|
|
202
227
|
trigger, copy-row, access, and spacing language.
|
|
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
|
|
|
211
236
|
|
|
212
237
|
| Action | Args | Purpose |
|
|
213
238
|
| -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
|
|
214
|
-
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
239
|
+
| `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
|
|
215
240
|
| `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
|
|
216
241
|
| `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
|
|
217
242
|
| `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
|
|
@@ -43,6 +43,7 @@ export declare const appConfigSchema: z.ZodObject<{
|
|
|
43
43
|
isWorkspace: z.ZodOptional<z.ZodBoolean>;
|
|
44
44
|
appsJson: z.ZodOptional<z.ZodString>;
|
|
45
45
|
gatewayUrl: z.ZodOptional<z.ZodString>;
|
|
46
|
+
orgDirectoryUrl: z.ZodOptional<z.ZodString>;
|
|
46
47
|
oauthOrigin: z.ZodOptional<z.ZodString>;
|
|
47
48
|
}, z.core.$strip>>;
|
|
48
49
|
}, z.core.$strip>;
|
|
@@ -19,5 +19,6 @@ export declare const workspaceConfig: z.ZodObject<{
|
|
|
19
19
|
isWorkspace: z.ZodOptional<z.ZodBoolean>;
|
|
20
20
|
appsJson: z.ZodOptional<z.ZodString>;
|
|
21
21
|
gatewayUrl: z.ZodOptional<z.ZodString>;
|
|
22
|
+
orgDirectoryUrl: z.ZodOptional<z.ZodString>;
|
|
22
23
|
oauthOrigin: z.ZodOptional<z.ZodString>;
|
|
23
24
|
}, z.core.$strip>;
|
|
@@ -42,6 +42,14 @@ export const workspaceConfig = z.object({
|
|
|
42
42
|
env: ["WORKSPACE_GATEWAY_URL", "VITE_WORKSPACE_GATEWAY_URL"],
|
|
43
43
|
doc: "URL of the workspace gateway fronting this app.",
|
|
44
44
|
}),
|
|
45
|
+
orgDirectoryUrl: z
|
|
46
|
+
.string()
|
|
47
|
+
.min(1)
|
|
48
|
+
.optional()
|
|
49
|
+
.meta({
|
|
50
|
+
env: ["AGENT_NATIVE_ORG_DIRECTORY_URL"],
|
|
51
|
+
doc: "URL of the authoritative organization Dispatch directory.",
|
|
52
|
+
}),
|
|
45
53
|
oauthOrigin: z
|
|
46
54
|
.string()
|
|
47
55
|
.min(1)
|
|
@@ -403,14 +403,14 @@ export declare const chatThreadShares: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
403
403
|
tableName: string;
|
|
404
404
|
dataType: "string";
|
|
405
405
|
columnType: "SQLiteText";
|
|
406
|
-
data: "org" | "user";
|
|
406
|
+
data: "group" | "org" | "user";
|
|
407
407
|
driverParam: string;
|
|
408
408
|
notNull: true;
|
|
409
409
|
hasDefault: false;
|
|
410
410
|
isPrimaryKey: false;
|
|
411
411
|
isAutoincrement: false;
|
|
412
412
|
hasRuntimeDefault: false;
|
|
413
|
-
enumValues: ["user", "org"];
|
|
413
|
+
enumValues: ["user", "group", "org"];
|
|
414
414
|
baseColumn: never;
|
|
415
415
|
identity: undefined;
|
|
416
416
|
generated: undefined;
|
|
@@ -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
|