@agent-native/toolkit 0.201.1 → 0.202.0-nightly-20261006033736
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/agent-native.eject.json +39 -12
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +292 -23
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +5 -0
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +5 -0
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +78 -3
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +7 -14
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/auth/server.d.ts +6 -0
- package/dist/app/auth/server.d.ts.map +1 -0
- package/dist/app/auth/server.js +19 -0
- package/dist/app/auth/server.js.map +1 -0
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +164 -83
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentSidebar.d.ts +2 -1
- package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
- package/dist/app/chat/AgentSidebar.js +2 -2
- package/dist/app/chat/AgentSidebar.js.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.js +13 -3
- package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +2 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +41 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +41 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +41 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +41 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +41 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +41 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +41 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +41 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +41 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +41 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +41 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +41 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/shared/StarfieldBackground.d.ts +1 -6
- package/dist/app/shared/StarfieldBackground.d.ts.map +1 -1
- package/dist/app/shared/StarfieldBackground.js +1 -368
- package/dist/app/shared/StarfieldBackground.js.map +1 -1
- package/dist/app/shared/WaveBackground.d.ts +5 -0
- package/dist/app/shared/WaveBackground.d.ts.map +1 -0
- package/dist/app/shared/WaveBackground.js +34 -0
- package/dist/app/shared/WaveBackground.js.map +1 -0
- package/dist/app/shared/WebGlWaveBackground.d.ts +9 -0
- package/dist/app/shared/WebGlWaveBackground.d.ts.map +1 -0
- package/dist/app/shared/WebGlWaveBackground.js +433 -0
- package/dist/app/shared/WebGlWaveBackground.js.map +1 -0
- package/dist/app/shared/index.d.ts +1 -0
- package/dist/app/shared/index.d.ts.map +1 -1
- package/dist/app/shared/index.js +1 -0
- package/dist/app/shared/index.js.map +1 -1
- package/dist/app/shared/ocean/bloom-blur.wgsl +43 -0
- package/dist/app/shared/ocean/bloom-blur.wgsl.js +1 -0
- package/dist/app/shared/ocean/bloom-bright.wgsl +21 -0
- package/dist/app/shared/ocean/bloom-bright.wgsl.js +1 -0
- package/dist/app/shared/ocean/bloom-composite.wgsl +40 -0
- package/dist/app/shared/ocean/bloom-composite.wgsl.js +1 -0
- package/dist/app/shared/ocean/brand-colors.d.ts +9 -0
- package/dist/app/shared/ocean/brand-colors.d.ts.map +1 -0
- package/dist/app/shared/ocean/brand-colors.js +45 -0
- package/dist/app/shared/ocean/brand-colors.js.map +1 -0
- package/dist/app/shared/ocean/camera.d.ts +5 -0
- package/dist/app/shared/ocean/camera.d.ts.map +1 -0
- package/dist/app/shared/ocean/camera.js +24 -0
- package/dist/app/shared/ocean/camera.js.map +1 -0
- package/dist/app/shared/ocean/hero-ocean-background.d.ts +8 -0
- package/dist/app/shared/ocean/hero-ocean-background.d.ts.map +1 -0
- package/dist/app/shared/ocean/hero-ocean-background.js +186 -0
- package/dist/app/shared/ocean/hero-ocean-background.js.map +1 -0
- package/dist/app/shared/ocean/ifft-stage.wgsl +36 -0
- package/dist/app/shared/ocean/ifft-stage.wgsl.js +1 -0
- package/dist/app/shared/ocean/initial-spectrum.wgsl +36 -0
- package/dist/app/shared/ocean/initial-spectrum.wgsl.js +1 -0
- package/dist/app/shared/ocean/noise.wgsl +24 -0
- package/dist/app/shared/ocean/noise.wgsl.js +1 -0
- package/dist/app/shared/ocean/normal-foam.wgsl +32 -0
- package/dist/app/shared/ocean/normal-foam.wgsl.js +1 -0
- package/dist/app/shared/ocean/ocean-colors.d.ts +6 -0
- package/dist/app/shared/ocean/ocean-colors.d.ts.map +1 -0
- package/dist/app/shared/ocean/ocean-colors.js +6 -0
- package/dist/app/shared/ocean/ocean-colors.js.map +1 -0
- package/dist/app/shared/ocean/ocean-common.wgsl +15 -0
- package/dist/app/shared/ocean/ocean-common.wgsl.js +1 -0
- package/dist/app/shared/ocean/ocean-graph.d.ts +12 -0
- package/dist/app/shared/ocean/ocean-graph.d.ts.map +1 -0
- package/dist/app/shared/ocean/ocean-graph.js +16 -0
- package/dist/app/shared/ocean/ocean-graph.js.map +1 -0
- package/dist/app/shared/ocean/particles.wgsl +139 -0
- package/dist/app/shared/ocean/particles.wgsl.js +1 -0
- package/dist/app/shared/ocean/present.wgsl +40 -0
- package/dist/app/shared/ocean/present.wgsl.js +1 -0
- package/dist/app/shared/ocean/renderer.d.ts +66 -0
- package/dist/app/shared/ocean/renderer.d.ts.map +1 -0
- package/dist/app/shared/ocean/renderer.js +535 -0
- package/dist/app/shared/ocean/renderer.js.map +1 -0
- package/dist/app/shared/ocean/spectrum.wgsl +35 -0
- package/dist/app/shared/ocean/spectrum.wgsl.js +1 -0
- package/dist/app/shared/ocean/tuning.d.ts +48 -0
- package/dist/app/shared/ocean/tuning.d.ts.map +1 -0
- package/dist/app/shared/ocean/tuning.js +74 -0
- package/dist/app/shared/ocean/tuning.js.map +1 -0
- package/dist/app/shared/ocean/webgpu-support.d.ts +3 -0
- package/dist/app/shared/ocean/webgpu-support.d.ts.map +1 -0
- package/dist/app/shared/ocean/webgpu-support.js +13 -0
- package/dist/app/shared/ocean/webgpu-support.js.map +1 -0
- package/dist/app/sharing/AccessRequestApprovalPage.d.ts +23 -0
- package/dist/app/sharing/AccessRequestApprovalPage.d.ts.map +1 -0
- package/dist/app/sharing/AccessRequestApprovalPage.js +95 -0
- package/dist/app/sharing/AccessRequestApprovalPage.js.map +1 -0
- package/dist/app/sharing/AccessRequestsSection.d.ts +5 -0
- package/dist/app/sharing/AccessRequestsSection.d.ts.map +1 -0
- package/dist/app/sharing/AccessRequestsSection.js +47 -0
- package/dist/app/sharing/AccessRequestsSection.js.map +1 -0
- package/dist/app/sharing/ResourceAccessScreen.d.ts +53 -0
- package/dist/app/sharing/ResourceAccessScreen.d.ts.map +1 -0
- package/dist/app/sharing/ResourceAccessScreen.js +132 -0
- package/dist/app/sharing/ResourceAccessScreen.js.map +1 -0
- package/dist/app/sharing/ShareButton.d.ts.map +1 -1
- package/dist/app/sharing/ShareButton.js +4 -2
- package/dist/app/sharing/ShareButton.js.map +1 -1
- package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/app/sharing/ShareDialog.js +7 -5
- package/dist/app/sharing/ShareDialog.js.map +1 -1
- package/dist/app/sharing/index.d.ts +2 -0
- package/dist/app/sharing/index.d.ts.map +1 -1
- package/dist/app/sharing/index.js +2 -0
- package/dist/app/sharing/index.js.map +1 -1
- package/dist/app/styles/agent-native.css +21 -1
- package/dist/app/styles/tokens/agent-kit.css +3 -1
- package/dist/composer/AgentSuggestionBar.d.ts +1 -1
- package/dist/composer/AgentSuggestionBar.d.ts.map +1 -1
- package/dist/composer/AgentSuggestionBar.js +75 -6
- package/dist/composer/AgentSuggestionBar.js.map +1 -1
- package/package.json +14 -4
- package/src/app/agentkit/react/components.tsx +492 -60
- package/src/app/agentkit/react/context.tsx +10 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +487 -1
- package/src/app/agentkit/react/styles.css +78 -3
- package/src/app/auth/AuthPage.spec.tsx +11 -7
- package/src/app/auth/AuthPage.tsx +16 -35
- package/src/app/auth/server.spec.ts +68 -0
- package/src/app/auth/server.ts +29 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +287 -290
- package/src/app/chat/AgentKitAssistantChat.tsx +219 -116
- package/src/app/chat/AgentSidebar.tsx +3 -0
- package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +85 -0
- package/src/app/chat/agentkit-chat/guided-questions.tsx +12 -5
- package/src/app/chat/chat/surface-types.ts +2 -0
- package/src/app/i18n/catalogs/ar-SA.ts +44 -0
- package/src/app/i18n/catalogs/de-DE.ts +55 -0
- package/src/app/i18n/catalogs/en-US.ts +46 -0
- package/src/app/i18n/catalogs/es-ES.ts +53 -0
- package/src/app/i18n/catalogs/fr-FR.ts +53 -0
- package/src/app/i18n/catalogs/hi-IN.ts +47 -0
- package/src/app/i18n/catalogs/ja-JP.ts +54 -0
- package/src/app/i18n/catalogs/ko-KR.ts +50 -0
- package/src/app/i18n/catalogs/pt-BR.ts +52 -0
- package/src/app/i18n/catalogs/zh-CN.ts +42 -0
- package/src/app/i18n/catalogs/zh-TW.ts +43 -0
- package/src/app/shared/StarfieldBackground.tsx +4 -423
- package/src/app/shared/WaveBackground.spec.tsx +104 -0
- package/src/app/shared/WaveBackground.tsx +60 -0
- package/src/app/shared/WebGlWaveBackground.spec.tsx +106 -0
- package/src/app/shared/WebGlWaveBackground.tsx +499 -0
- package/src/app/shared/index.ts +1 -0
- package/src/app/shared/ocean/bloom-blur.wgsl +43 -0
- package/src/app/shared/ocean/bloom-bright.wgsl +21 -0
- package/src/app/shared/ocean/bloom-composite.wgsl +40 -0
- package/src/app/shared/ocean/brand-colors.ts +49 -0
- package/src/app/shared/ocean/camera.ts +28 -0
- package/src/app/shared/ocean/hero-ocean-background.spec.tsx +369 -0
- package/src/app/shared/ocean/hero-ocean-background.tsx +238 -0
- package/src/app/shared/ocean/ifft-stage.wgsl +36 -0
- package/src/app/shared/ocean/initial-spectrum.wgsl +36 -0
- package/src/app/shared/ocean/noise.wgsl +24 -0
- package/src/app/shared/ocean/normal-foam.wgsl +32 -0
- package/src/app/shared/ocean/ocean-colors.ts +11 -0
- package/src/app/shared/ocean/ocean-common.wgsl +15 -0
- package/src/app/shared/ocean/ocean-graph.ts +29 -0
- package/src/app/shared/ocean/particles.wgsl +139 -0
- package/src/app/shared/ocean/present.wgsl +40 -0
- package/src/app/shared/ocean/renderer.spec.ts +100 -0
- package/src/app/shared/ocean/renderer.ts +693 -0
- package/src/app/shared/ocean/spectrum.wgsl +35 -0
- package/src/app/shared/ocean/tuning.ts +131 -0
- package/src/app/shared/ocean/webgpu-support.ts +12 -0
- package/src/app/sharing/AccessRequestApprovalPage.spec.tsx +276 -0
- package/src/app/sharing/AccessRequestApprovalPage.tsx +307 -0
- package/src/app/sharing/AccessRequestsSection.tsx +163 -0
- package/src/app/sharing/ResourceAccessScreen.spec.tsx +235 -0
- package/src/app/sharing/ResourceAccessScreen.tsx +331 -0
- package/src/app/sharing/ShareButton.spec.tsx +171 -5
- package/src/app/sharing/ShareButton.tsx +15 -2
- package/src/app/sharing/ShareDialog.spec.tsx +71 -2
- package/src/app/sharing/ShareDialog.tsx +24 -2
- package/src/app/sharing/index.ts +2 -0
- package/src/app/styles/agent-native.css +21 -1
- package/src/app/styles/agent-native.spec.ts +16 -3
- package/src/app/styles/tokens/agent-kit.css +3 -1
- package/src/composer/AgentSuggestionBar.overflow.spec.tsx +277 -0
- package/src/composer/AgentSuggestionBar.tsx +113 -8
- package/src/wgsl-env.d.ts +6 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { buildSignInReturnHref } from "@agent-native/core/client/sign-in-return";
|
|
3
|
+
import {
|
|
4
|
+
ActionButton,
|
|
5
|
+
Dialog,
|
|
6
|
+
TextArea,
|
|
7
|
+
} from "@agent-native/toolkit/design-system";
|
|
8
|
+
import { cn } from "@agent-native/toolkit/utils";
|
|
9
|
+
import {
|
|
10
|
+
IconFileUnknown,
|
|
11
|
+
IconLock,
|
|
12
|
+
IconLogin2,
|
|
13
|
+
IconTrash,
|
|
14
|
+
} from "@tabler/icons-react";
|
|
15
|
+
import {
|
|
16
|
+
useEffect,
|
|
17
|
+
useId,
|
|
18
|
+
useRef,
|
|
19
|
+
useState,
|
|
20
|
+
type ReactNode,
|
|
21
|
+
type RefObject,
|
|
22
|
+
} from "react";
|
|
23
|
+
|
|
24
|
+
export type ResourceAccessScreenState =
|
|
25
|
+
| "denied"
|
|
26
|
+
| "missing"
|
|
27
|
+
| "trashed"
|
|
28
|
+
| "signed-out";
|
|
29
|
+
|
|
30
|
+
/** Asking the owner for access, on the `denied` state. */
|
|
31
|
+
export interface ResourceAccessScreenRequest {
|
|
32
|
+
/** The viewer already asked, and their request is open. */
|
|
33
|
+
sent: boolean;
|
|
34
|
+
/** Sends the request with an optional note; rejects when it fails. */
|
|
35
|
+
onRequest: (note: string) => Promise<void>;
|
|
36
|
+
/** A request is being sent. */
|
|
37
|
+
sending?: boolean;
|
|
38
|
+
/** Why the last request failed. */
|
|
39
|
+
error?: { errorCode: string | null } | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ResourceAccessScreenProps {
|
|
43
|
+
state: ResourceAccessScreenState;
|
|
44
|
+
/** The app's heading for this state, such as "This page doesn't exist". */
|
|
45
|
+
title?: ReactNode;
|
|
46
|
+
description?: ReactNode;
|
|
47
|
+
/** The account the viewer is signed in as, shown as text. */
|
|
48
|
+
signedInEmail?: string | null;
|
|
49
|
+
/** The app's own actions, such as a link back to the viewer's work. */
|
|
50
|
+
actions?: ReactNode;
|
|
51
|
+
/** Shows Switch account after the app's actions. */
|
|
52
|
+
onSwitchAccount?: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Replaces the signed-out state's Sign in, which by default goes to sign-in
|
|
55
|
+
* and comes back to this link.
|
|
56
|
+
*/
|
|
57
|
+
onSignIn?: () => void;
|
|
58
|
+
/** Offers Request access on the `denied` state. */
|
|
59
|
+
request?: ResourceAccessScreenRequest;
|
|
60
|
+
/**
|
|
61
|
+
* The app takes access requests for this kind of resource, so the
|
|
62
|
+
* signed-out state says signing in is how to ask. It says nothing about
|
|
63
|
+
* this particular link.
|
|
64
|
+
*/
|
|
65
|
+
acceptsRequests?: boolean;
|
|
66
|
+
/** Rendered above the screen, such as a sidebar toggle. */
|
|
67
|
+
header?: ReactNode;
|
|
68
|
+
/** Render as the page's `main` landmark when the app shell has none. */
|
|
69
|
+
landmark?: boolean;
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const STATE_ICONS = {
|
|
74
|
+
denied: IconLock,
|
|
75
|
+
missing: IconFileUnknown,
|
|
76
|
+
trashed: IconTrash,
|
|
77
|
+
"signed-out": IconLogin2,
|
|
78
|
+
} satisfies Record<ResourceAccessScreenState, unknown>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* What a link to a resource the viewer can't open says, in place of the
|
|
82
|
+
* resource. It stays on the link's URL, never redirects, and states the
|
|
83
|
+
* situation as static content, so focus starts on the heading rather than a
|
|
84
|
+
* toast or live region.
|
|
85
|
+
*/
|
|
86
|
+
export function ResourceAccessScreen({
|
|
87
|
+
state,
|
|
88
|
+
title,
|
|
89
|
+
description,
|
|
90
|
+
signedInEmail,
|
|
91
|
+
actions,
|
|
92
|
+
onSwitchAccount,
|
|
93
|
+
onSignIn,
|
|
94
|
+
request,
|
|
95
|
+
acceptsRequests = false,
|
|
96
|
+
header,
|
|
97
|
+
landmark = false,
|
|
98
|
+
className,
|
|
99
|
+
}: ResourceAccessScreenProps) {
|
|
100
|
+
const t = useT();
|
|
101
|
+
const headingId = useId();
|
|
102
|
+
const headingRef = useRef<HTMLHeadingElement>(null);
|
|
103
|
+
const [requestOpen, setRequestOpen] = useState(false);
|
|
104
|
+
const Icon = STATE_ICONS[state];
|
|
105
|
+
const Root = landmark ? "main" : "div";
|
|
106
|
+
const signIn =
|
|
107
|
+
state === "signed-out"
|
|
108
|
+
? (onSignIn ?? (() => window.location.assign(buildSignInReturnHref())))
|
|
109
|
+
: undefined;
|
|
110
|
+
const offersRequest = state === "denied" && request !== undefined;
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
headingRef.current?.focus({ preventScroll: true });
|
|
114
|
+
}, [state]);
|
|
115
|
+
|
|
116
|
+
const defaults = {
|
|
117
|
+
denied: {
|
|
118
|
+
title: t("agentChat.accessGate.deniedTitle", {
|
|
119
|
+
defaultValue: "You don't have access",
|
|
120
|
+
}),
|
|
121
|
+
description: request
|
|
122
|
+
? request.sent
|
|
123
|
+
? t("agentChat.accessGate.requestSent", {
|
|
124
|
+
defaultValue: "Request sent. The owner has been notified.",
|
|
125
|
+
})
|
|
126
|
+
: t("agentChat.accessGate.requestDescription", {
|
|
127
|
+
defaultValue: "Request access and the owner will be notified.",
|
|
128
|
+
})
|
|
129
|
+
: t("agentChat.accessGate.deniedDescription", {
|
|
130
|
+
defaultValue: "Ask the owner to share it with you.",
|
|
131
|
+
}),
|
|
132
|
+
},
|
|
133
|
+
missing: {
|
|
134
|
+
title: t("agentChat.accessGate.missingTitle", {
|
|
135
|
+
defaultValue: "This doesn't exist",
|
|
136
|
+
}),
|
|
137
|
+
description: t("agentChat.accessGate.missingDescription", {
|
|
138
|
+
defaultValue: "The link may be wrong, or it may have been deleted.",
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
trashed: {
|
|
142
|
+
title: t("agentChat.accessGate.trashedTitle", {
|
|
143
|
+
defaultValue: "This is in the trash",
|
|
144
|
+
}),
|
|
145
|
+
description: t("agentChat.accessGate.trashedDescription", {
|
|
146
|
+
defaultValue: "Restore it to open it again.",
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
"signed-out": {
|
|
150
|
+
title: t("agentChat.accessGate.signedOutTitle", {
|
|
151
|
+
defaultValue: "Sign in to continue",
|
|
152
|
+
}),
|
|
153
|
+
description: acceptsRequests
|
|
154
|
+
? t("agentChat.accessGate.signedOutRequestDescription", {
|
|
155
|
+
defaultValue: "Sign in to request access.",
|
|
156
|
+
})
|
|
157
|
+
: t("agentChat.accessGate.signedOutDescription", {
|
|
158
|
+
defaultValue: "Sign in with an account that has access.",
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
}[state];
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Root
|
|
165
|
+
className={cn("flex min-h-0 flex-1 flex-col", className)}
|
|
166
|
+
data-access-state={state}
|
|
167
|
+
>
|
|
168
|
+
{header}
|
|
169
|
+
<div className="flex min-h-0 flex-1 items-center justify-center bg-background px-6 py-10">
|
|
170
|
+
<section
|
|
171
|
+
aria-labelledby={headingId}
|
|
172
|
+
className="flex w-full max-w-sm flex-col items-center text-center"
|
|
173
|
+
>
|
|
174
|
+
<div
|
|
175
|
+
aria-hidden="true"
|
|
176
|
+
className="mb-5 flex size-12 items-center justify-center rounded-xl border border-border bg-muted text-muted-foreground"
|
|
177
|
+
>
|
|
178
|
+
<Icon size={22} />
|
|
179
|
+
</div>
|
|
180
|
+
<h1
|
|
181
|
+
ref={headingRef}
|
|
182
|
+
id={headingId}
|
|
183
|
+
tabIndex={-1}
|
|
184
|
+
className="text-2xl font-semibold tracking-normal text-foreground outline-none"
|
|
185
|
+
>
|
|
186
|
+
{title ?? defaults.title}
|
|
187
|
+
</h1>
|
|
188
|
+
{/* A request's outcome is announced politely when it changes; the
|
|
189
|
+
state on arrival is read from the focused heading instead. */}
|
|
190
|
+
<p
|
|
191
|
+
role={offersRequest ? "status" : undefined}
|
|
192
|
+
className="mt-3 text-sm leading-6 text-muted-foreground"
|
|
193
|
+
>
|
|
194
|
+
{description ?? defaults.description}
|
|
195
|
+
</p>
|
|
196
|
+
{signedInEmail ? (
|
|
197
|
+
<p className="mt-4 break-all text-sm text-muted-foreground">
|
|
198
|
+
{t("agentChat.accessGate.signedInAs", {
|
|
199
|
+
email: signedInEmail,
|
|
200
|
+
defaultValue: "You're signed in as {{email}}",
|
|
201
|
+
})}
|
|
202
|
+
</p>
|
|
203
|
+
) : null}
|
|
204
|
+
{signIn || actions || onSwitchAccount || offersRequest ? (
|
|
205
|
+
<div className="mt-6 flex flex-wrap items-center justify-center gap-2">
|
|
206
|
+
{signIn ? (
|
|
207
|
+
<ActionButton onPress={signIn}>
|
|
208
|
+
{t("agentChat.accessGate.signIn", {
|
|
209
|
+
defaultValue: "Sign in",
|
|
210
|
+
})}
|
|
211
|
+
</ActionButton>
|
|
212
|
+
) : null}
|
|
213
|
+
{offersRequest && !request.sent ? (
|
|
214
|
+
<ActionButton onPress={() => setRequestOpen(true)}>
|
|
215
|
+
{t("agentChat.accessGate.requestAccess", {
|
|
216
|
+
defaultValue: "Request access",
|
|
217
|
+
})}
|
|
218
|
+
</ActionButton>
|
|
219
|
+
) : null}
|
|
220
|
+
{actions}
|
|
221
|
+
{onSwitchAccount ? (
|
|
222
|
+
<ActionButton emphasis="outline" onPress={onSwitchAccount}>
|
|
223
|
+
{t("agentChat.accessGate.switchAccount", {
|
|
224
|
+
defaultValue: "Switch account",
|
|
225
|
+
})}
|
|
226
|
+
</ActionButton>
|
|
227
|
+
) : null}
|
|
228
|
+
</div>
|
|
229
|
+
) : null}
|
|
230
|
+
</section>
|
|
231
|
+
</div>
|
|
232
|
+
{offersRequest ? (
|
|
233
|
+
<RequestAccessDialog
|
|
234
|
+
open={requestOpen}
|
|
235
|
+
onOpenChange={setRequestOpen}
|
|
236
|
+
request={request}
|
|
237
|
+
sentFocusRef={headingRef}
|
|
238
|
+
/>
|
|
239
|
+
) : null}
|
|
240
|
+
</Root>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function RequestAccessDialog({
|
|
245
|
+
open,
|
|
246
|
+
onOpenChange,
|
|
247
|
+
request,
|
|
248
|
+
sentFocusRef,
|
|
249
|
+
}: {
|
|
250
|
+
open: boolean;
|
|
251
|
+
onOpenChange: (open: boolean) => void;
|
|
252
|
+
request: ResourceAccessScreenRequest;
|
|
253
|
+
sentFocusRef: RefObject<HTMLElement | null>;
|
|
254
|
+
}) {
|
|
255
|
+
const t = useT();
|
|
256
|
+
const [note, setNote] = useState("");
|
|
257
|
+
const [failed, setFailed] = useState(false);
|
|
258
|
+
// Once sent, Request access is gone, so focus can't return to it.
|
|
259
|
+
const restoreFocusRef = useRef<HTMLElement | null>(null);
|
|
260
|
+
const errorCode = failed ? (request.error?.errorCode ?? null) : null;
|
|
261
|
+
|
|
262
|
+
const send = async () => {
|
|
263
|
+
setFailed(false);
|
|
264
|
+
try {
|
|
265
|
+
await request.onRequest(note);
|
|
266
|
+
setNote("");
|
|
267
|
+
restoreFocusRef.current = sentFocusRef.current;
|
|
268
|
+
onOpenChange(false);
|
|
269
|
+
} catch {
|
|
270
|
+
setFailed(true);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<Dialog
|
|
276
|
+
open={open}
|
|
277
|
+
onOpenChange={(next) => {
|
|
278
|
+
if (!next) setFailed(false);
|
|
279
|
+
onOpenChange(next);
|
|
280
|
+
}}
|
|
281
|
+
title={t("agentChat.accessGate.requestAccess", {
|
|
282
|
+
defaultValue: "Request access",
|
|
283
|
+
})}
|
|
284
|
+
closeLabel={t("agentChat.accessGate.cancel", { defaultValue: "Cancel" })}
|
|
285
|
+
size="small"
|
|
286
|
+
restoreFocusRef={restoreFocusRef}
|
|
287
|
+
footer={
|
|
288
|
+
<div className="flex justify-end gap-2">
|
|
289
|
+
<ActionButton emphasis="outline" onPress={() => onOpenChange(false)}>
|
|
290
|
+
{t("agentChat.accessGate.cancel", { defaultValue: "Cancel" })}
|
|
291
|
+
</ActionButton>
|
|
292
|
+
<ActionButton
|
|
293
|
+
pending={request.sending}
|
|
294
|
+
disabled={request.sending}
|
|
295
|
+
onPress={() => void send()}
|
|
296
|
+
>
|
|
297
|
+
{t("agentChat.accessGate.sendRequest", {
|
|
298
|
+
defaultValue: "Send request",
|
|
299
|
+
})}
|
|
300
|
+
</ActionButton>
|
|
301
|
+
</div>
|
|
302
|
+
}
|
|
303
|
+
>
|
|
304
|
+
<TextArea
|
|
305
|
+
label={t("agentChat.accessGate.requestNoteLabel", {
|
|
306
|
+
defaultValue: "Note (optional)",
|
|
307
|
+
})}
|
|
308
|
+
placeholder={t("agentChat.accessGate.requestNotePlaceholder", {
|
|
309
|
+
defaultValue: "Add a note for the owner",
|
|
310
|
+
})}
|
|
311
|
+
value={note}
|
|
312
|
+
onChange={setNote}
|
|
313
|
+
maxLength={500}
|
|
314
|
+
rows={3}
|
|
315
|
+
autoFocus
|
|
316
|
+
errorMessage={
|
|
317
|
+
failed
|
|
318
|
+
? errorCode === "access_request_rate_limited"
|
|
319
|
+
? t("agentChat.accessGate.requestRateLimited", {
|
|
320
|
+
defaultValue: "Too many requests right now. Try again later.",
|
|
321
|
+
})
|
|
322
|
+
: t("agentChat.accessGate.requestFailed", {
|
|
323
|
+
defaultValue: "Couldn't send your request. Try again.",
|
|
324
|
+
})
|
|
325
|
+
: undefined
|
|
326
|
+
}
|
|
327
|
+
invalid={failed}
|
|
328
|
+
/>
|
|
329
|
+
</Dialog>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
@@ -49,14 +49,37 @@ const sharesData = vi.hoisted(() => ({
|
|
|
49
49
|
},
|
|
50
50
|
}));
|
|
51
51
|
|
|
52
|
+
const accessRequestsData = vi.hoisted(() => ({
|
|
53
|
+
current: [] as unknown[],
|
|
54
|
+
hasMore: false,
|
|
55
|
+
isError: false,
|
|
56
|
+
}));
|
|
57
|
+
const refetchRequests = vi.hoisted(() => vi.fn(async () => undefined));
|
|
58
|
+
const approveRequest = vi.hoisted(() => vi.fn());
|
|
59
|
+
const queriedActions = vi.hoisted(() => [] as string[]);
|
|
60
|
+
|
|
52
61
|
vi.mock("@agent-native/core/client/use-action", () => ({
|
|
53
|
-
useActionQuery: () =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
useActionQuery: (name: string) => {
|
|
63
|
+
queriedActions.push(name);
|
|
64
|
+
return name === "list-resource-access-requests"
|
|
65
|
+
? {
|
|
66
|
+
data: {
|
|
67
|
+
requests: accessRequestsData.current,
|
|
68
|
+
hasMore: accessRequestsData.hasMore,
|
|
69
|
+
},
|
|
70
|
+
isError: accessRequestsData.isError,
|
|
71
|
+
refetch: refetchRequests,
|
|
72
|
+
}
|
|
73
|
+
: {
|
|
74
|
+
data: sharesData.current,
|
|
75
|
+
isError: sharesError.current,
|
|
76
|
+
refetch: refetchShares,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
58
79
|
useActionMutation: (name: string) => ({
|
|
59
80
|
mutate: name === "share-resource" ? shareMutate : otherMutate,
|
|
81
|
+
mutateAsync:
|
|
82
|
+
name === "approve-resource-access-request" ? approveRequest : otherMutate,
|
|
60
83
|
}),
|
|
61
84
|
}));
|
|
62
85
|
|
|
@@ -185,6 +208,12 @@ describe("ShareButton", () => {
|
|
|
185
208
|
);
|
|
186
209
|
shareMutate.mockReset();
|
|
187
210
|
otherMutate.mockReset();
|
|
211
|
+
approveRequest.mockReset().mockResolvedValue(undefined);
|
|
212
|
+
accessRequestsData.current = [];
|
|
213
|
+
accessRequestsData.hasMore = false;
|
|
214
|
+
accessRequestsData.isError = false;
|
|
215
|
+
refetchRequests.mockClear();
|
|
216
|
+
queriedActions.length = 0;
|
|
188
217
|
refetchShares.mockClear();
|
|
189
218
|
popoverInteractOutsideHandlers.length = 0;
|
|
190
219
|
sheetInteractOutsideHandlers.length = 0;
|
|
@@ -1207,6 +1236,143 @@ describe("ShareButton", () => {
|
|
|
1207
1236
|
expect(container.textContent).not.toContain("owner@example.com");
|
|
1208
1237
|
});
|
|
1209
1238
|
|
|
1239
|
+
const patRequest = {
|
|
1240
|
+
id: "req-1",
|
|
1241
|
+
generation: 3,
|
|
1242
|
+
state: "pending",
|
|
1243
|
+
requester: { email: "requester@example.test", name: "Pat Example" },
|
|
1244
|
+
note: "Need this for the launch review.",
|
|
1245
|
+
requestedAt: "2026-10-01T10:00:00.000Z",
|
|
1246
|
+
decidedAt: null,
|
|
1247
|
+
grantedRole: null,
|
|
1248
|
+
resource: {
|
|
1249
|
+
type: "document",
|
|
1250
|
+
id: "doc-1",
|
|
1251
|
+
label: "Document",
|
|
1252
|
+
title: "Launch plan",
|
|
1253
|
+
path: "/page/doc-1",
|
|
1254
|
+
},
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
async function renderWithRequests() {
|
|
1258
|
+
await act(async () => {
|
|
1259
|
+
root.render(
|
|
1260
|
+
<TooltipProvider>
|
|
1261
|
+
<QueryClientProvider client={queryClient}>
|
|
1262
|
+
<ShareButton
|
|
1263
|
+
resourceType="document"
|
|
1264
|
+
resourceId="doc-1"
|
|
1265
|
+
peopleTabLabel="People"
|
|
1266
|
+
agentsTabLabel="Agents"
|
|
1267
|
+
peopleAccessLabel="Who has access"
|
|
1268
|
+
agentTabContent={<button type="button">Copy agent prompt</button>}
|
|
1269
|
+
/>
|
|
1270
|
+
</QueryClientProvider>
|
|
1271
|
+
</TooltipProvider>,
|
|
1272
|
+
);
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
async function allowPat() {
|
|
1277
|
+
const allow = container.querySelector<HTMLButtonElement>(
|
|
1278
|
+
'button[aria-label="Allow Pat Example"]',
|
|
1279
|
+
);
|
|
1280
|
+
await act(async () => allow!.click());
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
it("lists access requests above who has access, and Allow grants Viewer", async () => {
|
|
1284
|
+
accessRequestsData.current = [patRequest];
|
|
1285
|
+
await renderWithRequests();
|
|
1286
|
+
|
|
1287
|
+
const text = container.textContent ?? "";
|
|
1288
|
+
expect(text).toContain("Need this for the launch review.");
|
|
1289
|
+
expect(text.indexOf("Access requests")).toBeGreaterThan(-1);
|
|
1290
|
+
expect(text.indexOf("Access requests")).toBeLessThan(
|
|
1291
|
+
text.indexOf("Who has access"),
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
await allowPat();
|
|
1295
|
+
expect(approveRequest).toHaveBeenCalledWith({
|
|
1296
|
+
requestId: "req-1",
|
|
1297
|
+
generation: 3,
|
|
1298
|
+
role: "viewer",
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
it("reloads the requests when someone else already handled one", async () => {
|
|
1303
|
+
accessRequestsData.current = [patRequest];
|
|
1304
|
+
approveRequest.mockRejectedValueOnce(
|
|
1305
|
+
Object.assign(new Error("stale"), {
|
|
1306
|
+
status: 409,
|
|
1307
|
+
errorCode: "access_request_stale",
|
|
1308
|
+
}),
|
|
1309
|
+
);
|
|
1310
|
+
await renderWithRequests();
|
|
1311
|
+
|
|
1312
|
+
await allowPat();
|
|
1313
|
+
|
|
1314
|
+
expect(refetchRequests).toHaveBeenCalledTimes(1);
|
|
1315
|
+
expect(container.textContent).toContain(
|
|
1316
|
+
"Someone already handled this request, or it changed.",
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
it("says when the requester couldn't be emailed that they're in", async () => {
|
|
1321
|
+
accessRequestsData.current = [patRequest];
|
|
1322
|
+
approveRequest.mockResolvedValueOnce({
|
|
1323
|
+
state: "approved",
|
|
1324
|
+
role: "viewer",
|
|
1325
|
+
email: "failed",
|
|
1326
|
+
});
|
|
1327
|
+
await renderWithRequests();
|
|
1328
|
+
|
|
1329
|
+
await allowPat();
|
|
1330
|
+
|
|
1331
|
+
expect(container.querySelector('[role="status"]')?.textContent).toBe(
|
|
1332
|
+
"Pat Example has access, but we couldn't email them.",
|
|
1333
|
+
);
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
it("keeps saying the email failed when the list can't reload", async () => {
|
|
1337
|
+
accessRequestsData.current = [patRequest];
|
|
1338
|
+
approveRequest.mockImplementationOnce(async () => {
|
|
1339
|
+
accessRequestsData.isError = true;
|
|
1340
|
+
return { state: "approved", role: "viewer", email: "failed" };
|
|
1341
|
+
});
|
|
1342
|
+
await renderWithRequests();
|
|
1343
|
+
|
|
1344
|
+
await allowPat();
|
|
1345
|
+
|
|
1346
|
+
expect(container.textContent).toContain("Couldn't load access requests.");
|
|
1347
|
+
expect(container.querySelector('[role="status"]')?.textContent).toBe(
|
|
1348
|
+
"Pat Example has access, but we couldn't email them.",
|
|
1349
|
+
);
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
it("says when older requests are past the list", async () => {
|
|
1353
|
+
accessRequestsData.current = [patRequest];
|
|
1354
|
+
accessRequestsData.hasMore = true;
|
|
1355
|
+
await renderWithRequests();
|
|
1356
|
+
|
|
1357
|
+
expect(container.textContent).toContain("Showing the 1 newest requests.");
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
it("doesn't read access requests for someone who can't manage access", async () => {
|
|
1361
|
+
sharesData.current = { ...sharesData.current, role: "viewer" };
|
|
1362
|
+
await act(async () => {
|
|
1363
|
+
root.render(
|
|
1364
|
+
<TooltipProvider>
|
|
1365
|
+
<QueryClientProvider client={queryClient}>
|
|
1366
|
+
<ShareButton resourceType="document" resourceId="doc-1" />
|
|
1367
|
+
</QueryClientProvider>
|
|
1368
|
+
</TooltipProvider>,
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
expect(container.textContent).toContain("People with access");
|
|
1373
|
+
expect(queriedActions).not.toContain("list-resource-access-requests");
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1210
1376
|
// Keep the non-source-locale provider test last: react-i18next's global
|
|
1211
1377
|
// fallback instance otherwise leaks the selected language into tests that
|
|
1212
1378
|
// intentionally exercise providerless compatibility.
|
|
@@ -57,6 +57,7 @@ import type {
|
|
|
57
57
|
UIEvent as ReactUIEvent,
|
|
58
58
|
} from "react";
|
|
59
59
|
|
|
60
|
+
import { AccessRequestsSection } from "./AccessRequestsSection.js";
|
|
60
61
|
import { AgentShareSection } from "./AgentShareSection.js";
|
|
61
62
|
|
|
62
63
|
export interface ShareButtonProps {
|
|
@@ -462,6 +463,12 @@ function SharePanel(
|
|
|
462
463
|
const generalAccessLabel =
|
|
463
464
|
props.generalAccessLabel ??
|
|
464
465
|
t("agentChat.share.generalAccess", { defaultValue: "General access" });
|
|
466
|
+
const accessRequests = canManage ? (
|
|
467
|
+
<AccessRequestsSection
|
|
468
|
+
resourceType={props.resourceType}
|
|
469
|
+
resourceId={props.resourceId}
|
|
470
|
+
/>
|
|
471
|
+
) : null;
|
|
465
472
|
const shareLinks = (
|
|
466
473
|
<>
|
|
467
474
|
{props.shareUrl ? (
|
|
@@ -586,7 +593,10 @@ function SharePanel(
|
|
|
586
593
|
|
|
587
594
|
<div className={cn("mb-4 space-y-4", props.agentTabContent && "order-1")}>
|
|
588
595
|
{!props.agentTabContent ? (
|
|
589
|
-
|
|
596
|
+
<>
|
|
597
|
+
{accessRequests}
|
|
598
|
+
<div className="text-sm font-semibold">{peopleAccessLabel}</div>
|
|
599
|
+
</>
|
|
590
600
|
) : null}
|
|
591
601
|
{canManage ? (
|
|
592
602
|
<div className="space-y-2">
|
|
@@ -711,7 +721,10 @@ function SharePanel(
|
|
|
711
721
|
) : null}
|
|
712
722
|
|
|
713
723
|
{props.agentTabContent ? (
|
|
714
|
-
|
|
724
|
+
<>
|
|
725
|
+
{accessRequests}
|
|
726
|
+
<div className="text-sm font-semibold">{peopleAccessLabel}</div>
|
|
727
|
+
</>
|
|
715
728
|
) : null}
|
|
716
729
|
<ul className="flex list-none flex-col gap-1 p-0 m-0">
|
|
717
730
|
{data?.ownerEmail ? (
|
|
@@ -19,9 +19,27 @@ const sharesQuery = {
|
|
|
19
19
|
refetch: vi.fn(),
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const requestsQuery = {
|
|
23
|
+
data: [] as unknown[],
|
|
24
|
+
isError: false,
|
|
25
|
+
refetch: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
const approveRequest = vi.fn(async () => undefined);
|
|
28
|
+
|
|
22
29
|
vi.mock("@agent-native/core/client/use-action", () => ({
|
|
23
|
-
useActionQuery: () =>
|
|
24
|
-
|
|
30
|
+
useActionQuery: (name: string) =>
|
|
31
|
+
name === "list-resource-access-requests"
|
|
32
|
+
? {
|
|
33
|
+
...requestsQuery,
|
|
34
|
+
data: { requests: requestsQuery.data, hasMore: false },
|
|
35
|
+
}
|
|
36
|
+
: sharesQuery,
|
|
37
|
+
useActionMutation: (name: string) => ({
|
|
38
|
+
mutate: vi.fn(),
|
|
39
|
+
mutateAsync:
|
|
40
|
+
name === "approve-resource-access-request" ? approveRequest : vi.fn(),
|
|
41
|
+
isPending: false,
|
|
42
|
+
}),
|
|
25
43
|
}));
|
|
26
44
|
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
27
45
|
useT: () => (key: string, values?: Record<string, string>) =>
|
|
@@ -51,6 +69,7 @@ beforeEach(() => {
|
|
|
51
69
|
});
|
|
52
70
|
|
|
53
71
|
afterEach(() => {
|
|
72
|
+
requestsQuery.data = [];
|
|
54
73
|
act(() => root.unmount());
|
|
55
74
|
queryClient.clear();
|
|
56
75
|
container.remove();
|
|
@@ -195,3 +214,53 @@ describe("ShareDialog primitive normalization", () => {
|
|
|
195
214
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
196
215
|
});
|
|
197
216
|
});
|
|
217
|
+
|
|
218
|
+
describe("ShareDialog access requests", () => {
|
|
219
|
+
it("lists pending requests above people with access and allows as Viewer", async () => {
|
|
220
|
+
requestsQuery.data = [
|
|
221
|
+
{
|
|
222
|
+
id: "req-1",
|
|
223
|
+
generation: 3,
|
|
224
|
+
state: "pending",
|
|
225
|
+
requester: { email: "requester@example.test", name: "Pat Example" },
|
|
226
|
+
note: "Need this for the launch review.",
|
|
227
|
+
requestedAt: "2026-10-01T10:00:00.000Z",
|
|
228
|
+
decidedAt: null,
|
|
229
|
+
grantedRole: null,
|
|
230
|
+
resource: {
|
|
231
|
+
type: "document",
|
|
232
|
+
id: "doc-1",
|
|
233
|
+
label: "Document",
|
|
234
|
+
title: "Quarterly plan",
|
|
235
|
+
path: "/page/doc-1",
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
await renderDialog();
|
|
240
|
+
|
|
241
|
+
const text = document.body.textContent ?? "";
|
|
242
|
+
expect(text).toContain("Pat Example");
|
|
243
|
+
expect(text).toContain("requester@example.test");
|
|
244
|
+
expect(text).toContain("Need this for the launch review.");
|
|
245
|
+
expect(text.indexOf("share.accessRequests")).toBeGreaterThan(-1);
|
|
246
|
+
expect(text.indexOf("share.accessRequests")).toBeLessThan(
|
|
247
|
+
text.indexOf("share.peopleWithAccess"),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const allow = document.body.querySelector<HTMLButtonElement>(
|
|
251
|
+
'button[aria-label="agentChat.share.allowRequestFrom"]',
|
|
252
|
+
);
|
|
253
|
+
await act(async () => allow?.click());
|
|
254
|
+
expect(approveRequest).toHaveBeenCalledWith({
|
|
255
|
+
requestId: "req-1",
|
|
256
|
+
generation: 3,
|
|
257
|
+
role: "viewer",
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("shows no requests section when nothing is pending", async () => {
|
|
262
|
+
await renderDialog();
|
|
263
|
+
|
|
264
|
+
expect(document.body.textContent).not.toContain("share.accessRequests");
|
|
265
|
+
});
|
|
266
|
+
});
|