@agent-native/core 0.164.4 → 0.164.12

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.
Files changed (268) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
  3. package/corpus/templates/calendar/AGENTS.md +5 -0
  4. package/corpus/templates/calendar/actions/delete-event.ts +1 -1
  5. package/corpus/templates/calendar/actions/delete-events.ts +508 -0
  6. package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
  7. package/corpus/templates/calendar/actions/list-events.ts +53 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
  11. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
  12. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
  13. package/corpus/templates/calendar/app/i18n-data.ts +120 -0
  14. package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
  15. package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
  16. package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
  17. package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
  18. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  19. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  20. package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  21. package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  22. package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
  23. package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
  24. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
  25. package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
  26. package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
  27. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
  28. package/corpus/templates/clips/desktop/README.md +19 -4
  29. package/corpus/templates/clips/desktop/src/app.tsx +78 -16
  30. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
  31. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
  32. package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  33. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
  34. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  35. package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
  36. package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
  37. package/corpus/templates/factory/AGENTS.md +3 -2
  38. package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
  39. package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
  40. package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
  41. package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
  42. package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
  43. package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
  44. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
  45. package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
  46. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
  47. package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
  48. package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
  49. package/corpus/templates/factory/app/routes/factory.tsx +324 -61
  50. package/corpus/templates/factory/server/connectors/slack.ts +44 -2
  51. package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
  52. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
  53. package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
  54. package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
  55. package/corpus/templates/mail/actions/view-screen.ts +1 -1
  56. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
  57. package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
  58. package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
  59. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  60. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  61. package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  62. package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  63. package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
  64. package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
  65. package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
  66. package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
  67. package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
  68. package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
  69. package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
  70. package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
  71. package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
  72. package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
  73. package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
  74. package/corpus/templates/slides/server/db/schema.ts +8 -0
  75. package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
  76. package/corpus/templates/slides/server/lib/emails.ts +26 -0
  77. package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
  78. package/corpus/templates/slides/server/plugins/db.ts +50 -0
  79. package/corpus/templates/slides/shared/deck-access.ts +31 -0
  80. package/dist/a2a/task-store.d.ts +1 -0
  81. package/dist/a2a/task-store.js +1 -1
  82. package/dist/agent/observational-memory/store.d.ts +1 -0
  83. package/dist/agent/observational-memory/store.js +1 -1
  84. package/dist/agent/run-store.d.ts +1 -0
  85. package/dist/agent/run-store.js +1 -1
  86. package/dist/agent/tool-approval-store.d.ts +1 -0
  87. package/dist/agent/tool-approval-store.js +1 -1
  88. package/dist/application-state/store.d.ts +1 -0
  89. package/dist/application-state/store.js +1 -1
  90. package/dist/browser-sessions/store.d.ts +1 -0
  91. package/dist/browser-sessions/store.js +1 -1
  92. package/dist/checkpoints/store.d.ts +1 -0
  93. package/dist/checkpoints/store.js +1 -1
  94. package/dist/cli/code-agent-schedules.d.ts +11 -0
  95. package/dist/cli/code-agent-schedules.js +44 -5
  96. package/dist/client/AgentPanel.d.ts +5 -1
  97. package/dist/client/AgentPanel.js +2 -2
  98. package/dist/client/AssistantChat.d.ts +5 -1
  99. package/dist/client/AssistantChat.js +40 -6
  100. package/dist/client/EnvironmentBadge.d.ts +7 -13
  101. package/dist/client/EnvironmentBadge.js +43 -92
  102. package/dist/client/agent-chat/index.d.ts +1 -0
  103. package/dist/client/agent-chat/index.js +1 -0
  104. package/dist/client/agent-chat-adapter.js +76 -50
  105. package/dist/client/app-providers.js +4 -4
  106. package/dist/client/chat/message-components.d.ts +4 -0
  107. package/dist/client/chat/message-components.js +28 -1
  108. package/dist/client/chat/run-recovery.js +26 -0
  109. package/dist/client/chat-first/apps-rail.d.ts +1 -0
  110. package/dist/client/chat-first/apps-rail.js +28 -2
  111. package/dist/client/sse-event-processor.js +13 -8
  112. package/dist/client/use-active-agent-chat-run.d.ts +2 -0
  113. package/dist/client/use-active-agent-chat-run.js +22 -0
  114. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
  115. package/dist/coding-tools/sandbox/executions-store.js +1 -1
  116. package/dist/collab/awareness-store.d.ts +1 -0
  117. package/dist/collab/awareness-store.js +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts +1 -0
  120. package/dist/collab/storage.js +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/db/client.js +3 -3
  123. package/dist/db/ddl-guard.js +8 -0
  124. package/dist/db/migration-runtime.d.ts +23 -0
  125. package/dist/db/migration-runtime.js +41 -0
  126. package/dist/db/migrations.d.ts +1 -2
  127. package/dist/db/migrations.js +2 -24
  128. package/dist/email-catalog/log.d.ts +1 -0
  129. package/dist/email-catalog/log.js +1 -1
  130. package/dist/guards/index.d.ts +1 -0
  131. package/dist/guards/index.js +1 -0
  132. package/dist/guards/release-schema-complete.d.ts +22 -0
  133. package/dist/guards/release-schema-complete.js +144 -0
  134. package/dist/integrations/a2a-continuations-store.d.ts +1 -0
  135. package/dist/integrations/a2a-continuations-store.js +1 -1
  136. package/dist/integrations/awaiting-input-store.d.ts +1 -0
  137. package/dist/integrations/awaiting-input-store.js +1 -1
  138. package/dist/integrations/config-store.d.ts +1 -0
  139. package/dist/integrations/config-store.js +1 -1
  140. package/dist/integrations/controls-store.d.ts +1 -0
  141. package/dist/integrations/controls-store.js +1 -1
  142. package/dist/integrations/identity-links-store.d.ts +1 -0
  143. package/dist/integrations/identity-links-store.js +1 -1
  144. package/dist/integrations/installations-store.d.ts +1 -0
  145. package/dist/integrations/installations-store.js +1 -1
  146. package/dist/integrations/remote-commands-store.d.ts +1 -0
  147. package/dist/integrations/remote-commands-store.js +1 -1
  148. package/dist/integrations/remote-devices-store.d.ts +1 -0
  149. package/dist/integrations/remote-devices-store.js +1 -1
  150. package/dist/integrations/remote-push-store.d.ts +1 -0
  151. package/dist/integrations/remote-push-store.js +1 -1
  152. package/dist/integrations/remote-run-events-store.d.ts +1 -0
  153. package/dist/integrations/remote-run-events-store.js +1 -1
  154. package/dist/integrations/scope-store.d.ts +1 -0
  155. package/dist/integrations/scope-store.js +1 -1
  156. package/dist/integrations/thread-mapping-store.d.ts +1 -0
  157. package/dist/integrations/thread-mapping-store.js +1 -1
  158. package/dist/integrations/usage-budget-store.d.ts +1 -0
  159. package/dist/integrations/usage-budget-store.js +1 -1
  160. package/dist/jobs/background-automation-runner.js +111 -9
  161. package/dist/jobs/run-history.d.ts +1 -0
  162. package/dist/jobs/run-history.js +1 -1
  163. package/dist/jobs/scheduler-health.d.ts +1 -0
  164. package/dist/jobs/scheduler-health.js +1 -1
  165. package/dist/mcp/approval-store.d.ts +1 -0
  166. package/dist/mcp/approval-store.js +1 -1
  167. package/dist/mcp/connect-store.d.ts +1 -0
  168. package/dist/mcp/connect-store.js +1 -1
  169. package/dist/mcp/oauth-store.d.ts +1 -0
  170. package/dist/mcp/oauth-store.js +1 -1
  171. package/dist/notifications/store.d.ts +1 -0
  172. package/dist/notifications/store.js +1 -1
  173. package/dist/oauth-tokens/store.d.ts +1 -0
  174. package/dist/oauth-tokens/store.js +1 -1
  175. package/dist/observability/routes.d.ts +5 -5
  176. package/dist/progress/routes.d.ts +1 -1
  177. package/dist/progress/store.d.ts +1 -0
  178. package/dist/progress/store.js +1 -1
  179. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  180. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  181. package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
  182. package/dist/provider-api/corpus-jobs-store.js +1 -1
  183. package/dist/provider-api/custom-registry.d.ts +1 -0
  184. package/dist/provider-api/custom-registry.js +1 -1
  185. package/dist/provider-api/quota-governor.d.ts +1 -0
  186. package/dist/provider-api/quota-governor.js +1 -1
  187. package/dist/provider-api/staged-datasets-store.d.ts +1 -0
  188. package/dist/provider-api/staged-datasets-store.js +1 -1
  189. package/dist/resources/handlers.d.ts +3 -3
  190. package/dist/resources/store.d.ts +1 -0
  191. package/dist/resources/store.js +1 -1
  192. package/dist/scripts/dev/index.js +15 -1
  193. package/dist/search/index.js +3 -0
  194. package/dist/secrets/routes.d.ts +9 -9
  195. package/dist/secrets/storage.d.ts +1 -0
  196. package/dist/secrets/storage.js +1 -1
  197. package/dist/server/agent-chat-plugin.js +38 -9
  198. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  199. package/dist/server/agent-teams-run-queue.d.ts +1 -0
  200. package/dist/server/agent-teams-run-queue.js +1 -1
  201. package/dist/server/auth-marketing.js +3 -1
  202. package/dist/server/auth.d.ts +18 -1
  203. package/dist/server/auth.js +370 -154
  204. package/dist/server/embed-session.d.ts +1 -0
  205. package/dist/server/embed-session.js +1 -1
  206. package/dist/server/google-auth-plugin.js +3 -1
  207. package/dist/server/google-oauth.d.ts +10 -0
  208. package/dist/server/google-oauth.js +25 -1
  209. package/dist/server/identity-sso-store.d.ts +1 -0
  210. package/dist/server/identity-sso-store.js +1 -1
  211. package/dist/server/index.d.ts +1 -1
  212. package/dist/server/index.js +1 -1
  213. package/dist/server/onboarding-html.js +200 -31
  214. package/dist/server/poll.d.ts +2 -0
  215. package/dist/server/poll.js +19 -1
  216. package/dist/server/realtime-token.d.ts +1 -1
  217. package/dist/server/release-migrations.js +5 -0
  218. package/dist/server/release-schema.d.ts +46 -0
  219. package/dist/server/release-schema.js +307 -0
  220. package/dist/settings/store.d.ts +1 -0
  221. package/dist/settings/store.js +1 -1
  222. package/dist/shared/environment-lanes.d.ts +28 -0
  223. package/dist/shared/environment-lanes.js +61 -0
  224. package/dist/sharing/actions/share-resource.js +22 -2
  225. package/dist/styles/agent-native.css +17 -15
  226. package/dist/triggers/routes.d.ts +11 -0
  227. package/dist/triggers/routes.js +10 -0
  228. package/dist/usage/alerts-store.d.ts +1 -0
  229. package/dist/usage/alerts-store.js +1 -1
  230. package/dist/usage/store.d.ts +1 -0
  231. package/dist/usage/store.js +1 -1
  232. package/docs/content/actions-access-control.mdx +136 -2
  233. package/docs/content/actions-agent-tools.mdx +101 -0
  234. package/docs/content/actions-defining.mdx +2 -2
  235. package/docs/content/audit-log.mdx +1 -1
  236. package/docs/content/authentication.mdx +1 -1
  237. package/docs/content/cloudflare.mdx +43 -0
  238. package/docs/content/creating-templates.mdx +1 -1
  239. package/docs/content/deploy-an-app.mdx +109 -0
  240. package/docs/content/deployment-environment-variables.mdx +101 -0
  241. package/docs/content/deployment.mdx +125 -792
  242. package/docs/content/docs-components.mdx +2 -2
  243. package/docs/content/environment-variables.mdx +41 -41
  244. package/docs/content/file-uploads.mdx +74 -40
  245. package/docs/content/harness-agents.mdx +2 -2
  246. package/docs/content/internationalization.mdx +61 -65
  247. package/docs/content/key-concepts.mdx +3 -3
  248. package/docs/content/multi-app-workspace.mdx +2 -2
  249. package/docs/content/netlify.mdx +47 -0
  250. package/docs/content/node-docker.mdx +138 -0
  251. package/docs/content/organizations-teams-permissions.mdx +5 -5
  252. package/docs/content/other-platforms.mdx +27 -0
  253. package/docs/content/ssr-caching.mdx +45 -0
  254. package/docs/content/template-assets-developers.mdx +2 -2
  255. package/docs/content/template-chat-developers.mdx +2 -2
  256. package/docs/content/template-content-developers.mdx +2 -2
  257. package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
  258. package/docs/content/template-content-sync.mdx +2 -2
  259. package/docs/content/toolkit-agent-ux.mdx +1 -1
  260. package/docs/content/toolkit-resources.mdx +2 -2
  261. package/docs/content/updating-ui-in-production.mdx +35 -0
  262. package/docs/content/vercel.mdx +38 -0
  263. package/docs/content/what-is-agent-native.mdx +1 -1
  264. package/docs/content/workspace-deployment.mdx +350 -0
  265. package/package.json +2 -2
  266. package/docs/content/agents.mdx +0 -9
  267. package/docs/content/database.mdx +0 -317
  268. package/docs/content/human-approval.mdx +0 -156
@@ -0,0 +1,101 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { IconLogin2 } from "@tabler/icons-react";
3
+ import type { FormEvent } from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ } from "@/components/ui/dialog";
13
+ import { Input } from "@/components/ui/input";
14
+ import { Label } from "@/components/ui/label";
15
+ import { Separator } from "@/components/ui/separator";
16
+
17
+ export interface RequestDeckAccessDialogProps {
18
+ open: boolean;
19
+ onOpenChange: (open: boolean) => void;
20
+ signInHref: string;
21
+ email: string;
22
+ onEmailChange: (email: string) => void;
23
+ onSubmit: (event: FormEvent<HTMLFormElement>) => void;
24
+ isSubmitting: boolean;
25
+ error?: string | null;
26
+ }
27
+
28
+ export function RequestDeckAccessDialog({
29
+ open,
30
+ onOpenChange,
31
+ signInHref,
32
+ email,
33
+ onEmailChange,
34
+ onSubmit,
35
+ isSubmitting,
36
+ error,
37
+ }: RequestDeckAccessDialogProps) {
38
+ const t = useT();
39
+
40
+ return (
41
+ <Dialog open={open} onOpenChange={onOpenChange}>
42
+ <DialogContent className="sm:max-w-md">
43
+ <DialogHeader>
44
+ <DialogTitle>{t("deckEditor.requestAccessDialogTitle")}</DialogTitle>
45
+ <DialogDescription>
46
+ {t("deckEditor.requestAccessDialogDescription")}
47
+ </DialogDescription>
48
+ </DialogHeader>
49
+
50
+ <div className="grid gap-4">
51
+ <Button asChild className="w-full gap-2">
52
+ <a href={signInHref}>
53
+ <IconLogin2 className="size-4" aria-hidden="true" />
54
+ {t("deckEditor.requestAccessSignIn")}
55
+ </a>
56
+ </Button>
57
+
58
+ <div className="flex items-center gap-3 text-xs text-muted-foreground">
59
+ <Separator className="flex-1" />
60
+ <span>{t("deckEditor.requestAccessOr")}</span>
61
+ <Separator className="flex-1" />
62
+ </div>
63
+
64
+ <form className="grid gap-3" onSubmit={onSubmit}>
65
+ <div className="grid gap-2">
66
+ <Label htmlFor="private-deck-request-email">
67
+ {t("deckEditor.requestAccessEmailLabel")}
68
+ </Label>
69
+ <Input
70
+ id="private-deck-request-email"
71
+ type="email"
72
+ inputMode="email"
73
+ autoComplete="email"
74
+ required
75
+ value={email}
76
+ onChange={(event) => onEmailChange(event.target.value)}
77
+ placeholder={t("deckEditor.requestAccessEmailPlaceholder")}
78
+ aria-invalid={Boolean(error)}
79
+ />
80
+ <p className="text-xs text-muted-foreground">
81
+ {t("deckEditor.requestAccessEmailHint")}
82
+ </p>
83
+ </div>
84
+
85
+ {error ? (
86
+ <p className="text-sm text-destructive" role="alert">
87
+ {error}
88
+ </p>
89
+ ) : null}
90
+
91
+ <Button type="submit" className="w-full" disabled={isSubmitting}>
92
+ {isSubmitting
93
+ ? t("deckEditor.requestingAccess")
94
+ : t("deckEditor.requestAccessWithEmail")}
95
+ </Button>
96
+ </form>
97
+ </div>
98
+ </DialogContent>
99
+ </Dialog>
100
+ );
101
+ }
@@ -12,6 +12,7 @@ export type DeckAccessStatusResponse = {
12
12
  viewerName: string | null;
13
13
  role: "owner" | "viewer" | "commenter" | "editor" | "admin" | null;
14
14
  visibility: "private" | "org" | "public" | null;
15
+ accessRequestToken?: string;
15
16
  };
16
17
 
17
18
  export function useDeckAccessStatus(deckId?: string) {
@@ -44,8 +45,10 @@ function showActionError(message: string) {
44
45
  }
45
46
 
46
47
  export function useRequestDeckAccess() {
47
- return useActionMutation<RequestDeckAccessResult, { deckId: string }>(
48
- "request-deck-access",
49
- { onError: showActionError("Failed to request access") },
50
- );
48
+ return useActionMutation<
49
+ RequestDeckAccessResult,
50
+ { accessRequestToken?: string; deckId: string; requesterEmail?: string }
51
+ >("request-deck-access", {
52
+ onError: showActionError("Failed to request access"),
53
+ });
51
54
  }
@@ -554,20 +554,16 @@ const messages = {
554
554
  },
555
555
  deckEditor: {
556
556
  lookingForDeck: "Looking for this deck",
557
- teamAccessCheckFailed: "Couldn't check team access",
558
557
  joinTeamToOpen: "Join your team to open this deck",
559
558
  deckUnavailable: "Deck unavailable",
560
559
  checkingSharedAccess:
561
560
  "Checking whether this presentation is shared with your account.",
562
- verifySharedAccessFailed:
563
- "We couldn't verify whether this presentation is shared with your account. Try again to reload team access and the deck.",
564
561
  joinTeamDescription:
565
562
  "This link points to a team presentation. Join the team shown above and the deck will open here automatically.",
566
563
  deckUnavailableDescription:
567
564
  "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.",
565
+ privateDeckTitle: "You don't have access to this deck",
566
+ privateDeckDescription: "Request access and the owner will be notified.",
571
567
  signedInAs: "You're signed in as",
572
568
  accessRequestSentDescription:
573
569
  "The owner has been notified. You can open this deck once access is granted.",
@@ -577,6 +573,33 @@ const messages = {
577
573
  accessRequestSent: "Request sent",
578
574
  requestAccess: "Request access",
579
575
  signInToRequestAccess: "Sign in to request access",
576
+ requestAccessDialogTitle: "Request access",
577
+ requestAccessDialogDescription:
578
+ "Sign in, or enter the email address the owner should share this deck with.",
579
+ requestAccessSignIn: "Sign in or sign up",
580
+ requestAccessOr: "or",
581
+ requestAccessEmailLabel: "Email address",
582
+ requestAccessEmailPlaceholder: "you@example.com",
583
+ requestAccessEmailHint:
584
+ "After access is granted, sign in with this email to view the deck.",
585
+ requestAccessWithEmail: "Request with email",
586
+ requestAccessEmailRequired: "Enter a valid email address.",
587
+ requestingAccess: "Requesting access...",
588
+ accessRequestSentWithEmail:
589
+ "The deck owner was asked to share this deck with {{email}}.",
590
+ accessRequestFailed: "Couldn't request access. Try again.",
591
+ accessApprovalTitle: "Access granted",
592
+ accessApprovalAlreadyTitle: "Access already granted",
593
+ accessApprovalMessage: "{{email}} can now open this deck.",
594
+ accessApprovalAlreadyMessage: "{{email}} already has access to this deck.",
595
+ accessApprovalErrorTitle: "Couldn't grant access",
596
+ accessApprovalInvalid: "This access request is invalid or expired.",
597
+ accessApprovalSignInTitle: "Sign in to allow access",
598
+ accessApprovalSignInMessage:
599
+ "Sign in as the deck owner or an admin to approve this request.",
600
+ accessApprovalOpenDeck: "Open deck",
601
+ accessApprovalSignIn: "Sign in",
602
+ accessApprovalLoading: "Granting access...",
580
603
  backToDecks: "Back to Decks",
581
604
  tryAgain: "Try again",
582
605
  imageUploadFailed: "Image upload failed",
@@ -20,18 +20,14 @@ import {
20
20
  useSensors,
21
21
  DragEndEvent,
22
22
  } from "@dnd-kit/core";
23
- import {
24
- IconAt,
25
- IconArrowLeft,
26
- IconLock,
27
- IconLoader2,
28
- IconLogin2,
29
- IconRefresh,
30
- IconUserPlus,
31
- IconUsersGroup,
32
- } from "@tabler/icons-react";
33
23
  import { nanoid } from "nanoid";
34
- import { useState, useCallback, useRef, useEffect } from "react";
24
+ import {
25
+ useState,
26
+ useCallback,
27
+ useRef,
28
+ useEffect,
29
+ type FormEvent,
30
+ } from "react";
35
31
  import {
36
32
  useBlocker,
37
33
  useNavigate,
@@ -51,6 +47,7 @@ import { canExportPptxFromServer } from "@/components/editor/ExportMenu";
51
47
  import GeneratingSlidePreview from "@/components/editor/GeneratingSlidePreview";
52
48
  import HistoryPanel from "@/components/editor/HistoryPanel";
53
49
  import ImageGenPanel from "@/components/editor/ImageGenPanel";
50
+ import { MissingDeckAccessPane } from "@/components/editor/MissingDeckAccessPane";
54
51
  import { QuestionFlow } from "@/components/editor/QuestionFlow";
55
52
  import SlideEditor from "@/components/editor/SlideEditor";
56
53
  import { TweaksPanel } from "@/components/editor/TweaksPanel";
@@ -78,7 +75,6 @@ import { useAgentGenerating } from "@/hooks/use-agent-generating";
78
75
  import {
79
76
  useDeckAccessStatus,
80
77
  useRequestDeckAccess,
81
- type DeckAccessStatusResponse,
82
78
  } from "@/hooks/use-deck-access";
83
79
  import { useDeckDesignSystem } from "@/hooks/use-deck-design-system";
84
80
  import { useDeckPresence } from "@/hooks/use-deck-presence";
@@ -121,147 +117,9 @@ import { shortcutLabel } from "@/lib/utils";
121
117
 
122
118
  type EditorSidePanel = "comments" | null;
123
119
 
124
- function MissingDeckAccessPane({
125
- accessStatus,
126
- accessStatusError,
127
- accessStatusLoading,
128
- hasTeamJoinOption,
129
- orgLoading,
130
- orgError,
131
- requestAccessPending,
132
- accessRequestSent,
133
- accessRequestNotified,
134
- signedIn,
135
- viewerEmail,
136
- refreshing,
137
- onRequestAccess,
138
- onSignIn,
139
- onRetry,
140
- onBack,
141
- }: {
142
- accessStatus: DeckAccessStatusResponse | null;
143
- accessStatusError: boolean;
144
- accessStatusLoading: boolean;
145
- hasTeamJoinOption: boolean;
146
- orgLoading: boolean;
147
- orgError: boolean;
148
- requestAccessPending: boolean;
149
- accessRequestSent: boolean;
150
- accessRequestNotified: boolean;
151
- signedIn: boolean;
152
- viewerEmail: string | null;
153
- refreshing: boolean;
154
- onRequestAccess: () => void;
155
- onSignIn: () => void;
156
- onRetry: () => void;
157
- onBack: () => void;
158
- }) {
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;
167
- const Icon =
168
- privateDeck || (!hasTeamJoinOption && !checkingAccess && !accessCheckFailed)
169
- ? IconLock
170
- : IconUsersGroup;
171
- const title = checkingAccess
172
- ? t("deckEditor.lookingForDeck")
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
181
- ? t("deckEditor.checkingSharedAccess")
182
- : privateDeck
183
- ? t("deckEditor.privateDeckDescription")
184
- : accessCheckFailed
185
- ? t("deckEditor.verifySharedAccessFailed")
186
- : hasTeamJoinOption
187
- ? t("deckEditor.joinTeamDescription")
188
- : t("deckEditor.deckUnavailableDescription");
189
-
190
- return (
191
- <div className="flex min-h-screen items-center justify-center bg-background px-4 py-10">
192
- <div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-sm">
193
- <div className="mb-4 flex items-center gap-3">
194
- <div className="flex size-10 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
195
- <Icon className="size-5" />
196
- </div>
197
- <div className="min-w-0">
198
- <h1 className="text-base font-semibold text-foreground">{title}</h1>
199
- </div>
200
- </div>
201
- <p className="text-sm leading-6 text-muted-foreground">{description}</p>
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>
260
- </div>
261
- </div>
262
- </div>
263
- );
264
- }
120
+ type AccessRequestCapability =
121
+ | { available: true; token: string }
122
+ | { available: false };
265
123
 
266
124
  // The Cmd/Ctrl+C-then-V slide-duplicate shortcut can only tell "this key
267
125
  // event targets the slide rail/canvas" apart from "focus fell back to
@@ -390,6 +248,13 @@ export default function DeckEditor() {
390
248
  string | null
391
249
  >(null);
392
250
  const [accessRequestNotified, setAccessRequestNotified] = useState(false);
251
+ const [requestAccessDialogOpen, setRequestAccessDialogOpen] = useState(false);
252
+ const [requesterEmail, setRequesterEmail] = useState("");
253
+ const [requestAccessDialogError, setRequestAccessDialogError] = useState<
254
+ string | null
255
+ >(null);
256
+ const [accessRequestRefreshPending, setAccessRequestRefreshPending] =
257
+ useState(false);
393
258
  const [checkedDeckAccessKey, setCheckedDeckAccessKey] = useState<
394
259
  string | null
395
260
  >(null);
@@ -609,23 +474,122 @@ export default function DeckEditor() {
609
474
  }, [refetchOrg, reloadDecks]);
610
475
 
611
476
  const openSignIn = useCallback(() => {
612
- window.location.href = buildSignInReturnHref();
613
- }, []);
477
+ window.location.href = buildSignInReturnHref({
478
+ returnTo: id ? `/deck/${encodeURIComponent(id)}` : "/",
479
+ });
480
+ }, [id]);
614
481
 
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();
482
+ const getFreshAccessRequestCapability =
483
+ useCallback(async (): Promise<AccessRequestCapability> => {
484
+ setAccessRequestRefreshPending(true);
485
+ try {
486
+ const result = await deckAccessStatusQuery.refetch();
487
+ const token = result.isSuccess
488
+ ? result.data?.accessRequestToken
489
+ : undefined;
490
+ return token ? { available: true, token } : { available: false };
491
+ } catch (error) {
492
+ console.warn(
493
+ "[slides] deck access request capability refresh failed:",
494
+ error,
495
+ );
496
+ return { available: false };
497
+ } finally {
498
+ setAccessRequestRefreshPending(false);
499
+ }
500
+ }, [deckAccessStatusQuery]);
501
+
502
+ const submitDeckAccessRequest = useCallback(
503
+ async (guestEmail?: string) => {
504
+ if (!id) return;
505
+ const normalizedGuestEmail = guestEmail?.trim() || undefined;
506
+ setRequestAccessDialogError(null);
507
+ const accessRequestCapability = normalizedGuestEmail
508
+ ? await getFreshAccessRequestCapability()
509
+ : { available: false as const };
510
+ if (normalizedGuestEmail && !accessRequestCapability.available) {
511
+ setRequestAccessDialogError(t("deckEditor.accessRequestFailed"));
512
+ return;
513
+ }
514
+ const accessRequestToken =
515
+ normalizedGuestEmail && accessRequestCapability.available
516
+ ? accessRequestCapability.token
517
+ : undefined;
518
+ requestDeckAccessMutation.mutate(
519
+ {
520
+ deckId: id,
521
+ ...(accessRequestToken ? { accessRequestToken } : {}),
522
+ ...(normalizedGuestEmail
523
+ ? { requesterEmail: normalizedGuestEmail }
524
+ : {}),
625
525
  },
626
- },
627
- );
628
- }, [id, reloadDecks, requestDeckAccessMutation]);
526
+ {
527
+ onSuccess: (result) => {
528
+ setAccessRequestSentDeckId(id);
529
+ setAccessRequestNotified(result.notifiedOwner);
530
+ if (normalizedGuestEmail) setRequestAccessDialogOpen(false);
531
+ toast.success(
532
+ normalizedGuestEmail
533
+ ? t("deckEditor.accessRequestSentWithEmail", {
534
+ email: normalizedGuestEmail,
535
+ })
536
+ : result.message,
537
+ );
538
+ if (result.alreadyHasAccess) void reloadDecks();
539
+ },
540
+ onError: (error: unknown) => {
541
+ if (!normalizedGuestEmail) return;
542
+ setRequestAccessDialogError(
543
+ error instanceof Error && error.message
544
+ ? error.message.replace(/^Action [\w-]+ failed:\s*/, "")
545
+ : t("deckEditor.accessRequestFailed"),
546
+ );
547
+ },
548
+ },
549
+ );
550
+ },
551
+ [
552
+ getFreshAccessRequestCapability,
553
+ id,
554
+ reloadDecks,
555
+ requestDeckAccessMutation,
556
+ t,
557
+ ],
558
+ );
559
+
560
+ const requestDeckAccess = useCallback(() => {
561
+ void submitDeckAccessRequest();
562
+ }, [submitDeckAccessRequest]);
563
+
564
+ const submitGuestAccessRequest = useCallback(
565
+ (event: FormEvent<HTMLFormElement>) => {
566
+ event.preventDefault();
567
+ const email = requesterEmail.trim();
568
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
569
+ setRequestAccessDialogError(t("deckEditor.requestAccessEmailRequired"));
570
+ return;
571
+ }
572
+ void submitDeckAccessRequest(email);
573
+ },
574
+ [requesterEmail, submitDeckAccessRequest, t],
575
+ );
576
+
577
+ const requestAccessDialogOpenChange = useCallback(
578
+ (open: boolean) => {
579
+ setRequestAccessDialogOpen(open);
580
+ if (!open) {
581
+ setRequestAccessDialogError(null);
582
+ return;
583
+ }
584
+ setRequestAccessDialogError(null);
585
+ void getFreshAccessRequestCapability().then((capability) => {
586
+ if (!capability.available) {
587
+ setRequestAccessDialogError(t("deckEditor.accessRequestFailed"));
588
+ }
589
+ });
590
+ },
591
+ [getFreshAccessRequestCapability, t],
592
+ );
629
593
 
630
594
  useEffect(() => {
631
595
  if (accessRequestSentDeckId && accessRequestSentDeckId !== id) {
@@ -1412,13 +1376,27 @@ export default function DeckEditor() {
1412
1376
  hasTeamJoinOption={hasTeamJoinOption}
1413
1377
  orgLoading={orgLoading}
1414
1378
  orgError={orgError || loadError}
1415
- requestAccessPending={requestDeckAccessMutation.isPending}
1379
+ requestAccessPending={
1380
+ requestDeckAccessMutation.isPending || accessRequestRefreshPending
1381
+ }
1416
1382
  accessRequestSent={accessRequestSentDeckId === id}
1417
1383
  accessRequestNotified={accessRequestNotified}
1384
+ requestAccessDialogOpen={requestAccessDialogOpen}
1385
+ requesterEmail={requesterEmail}
1386
+ requestAccessDialogError={requestAccessDialogError}
1418
1387
  signedIn={Boolean(session) && !sessionLoading}
1388
+ signInHref={buildSignInReturnHref({
1389
+ returnTo: id ? `/deck/${encodeURIComponent(id)}` : "/",
1390
+ })}
1419
1391
  viewerEmail={session?.email ?? deckAccessStatus?.viewerEmail ?? null}
1420
1392
  refreshing={retryingMissingDeck}
1421
1393
  onRequestAccess={requestDeckAccess}
1394
+ onRequestAccessDialogOpenChange={requestAccessDialogOpenChange}
1395
+ onRequesterEmailChange={(email) => {
1396
+ setRequesterEmail(email);
1397
+ setRequestAccessDialogError(null);
1398
+ }}
1399
+ onSubmitGuestAccessRequest={submitGuestAccessRequest}
1422
1400
  onSignIn={openSignIn}
1423
1401
  onRetry={() => void retryOpenDeck()}
1424
1402
  onBack={() => navigate("/")}