@agent-native/toolkit 0.225.2-nightly-20261106152255 → 0.226.0
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 +1 -0
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +124 -9
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +1 -1
- package/dist/app/agentkit/react/core-file-storage.d.ts +21 -0
- package/dist/app/agentkit/react/core-file-storage.d.ts.map +1 -0
- package/dist/app/agentkit/react/core-file-storage.js +3 -0
- package/dist/app/agentkit/react/core-file-storage.js.map +1 -0
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +89 -10
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +39 -58
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.js +3 -1
- package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
- package/dist/app/chat/agentkit-chat/root.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/root.js +66 -2
- package/dist/app/chat/agentkit-chat/root.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +1 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/chat/chat/surface-types.js.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.js +2 -0
- package/dist/app/chat/composer/runtime-adapters.js.map +1 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.d.ts +3 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.d.ts.map +1 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.js +11 -11
- package/dist/app/chat/composer/use-agentkit-capabilities.js.map +1 -1
- package/dist/app/chat/composer/use-workspace-composer-menu.d.ts +3 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.d.ts.map +1 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.js +80 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.js.map +1 -0
- package/dist/app/chat/index.d.ts +1 -0
- package/dist/app/chat/index.d.ts.map +1 -1
- package/dist/app/chat/index.js +1 -0
- package/dist/app/chat/index.js.map +1 -1
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts +5 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts.map +1 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.js +108 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.js.map +1 -0
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +6 -1
- 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 +6 -1
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +6 -1
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- 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 +6 -1
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/resources/McpIntegrationDialog.d.ts +3 -1
- package/dist/app/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/app/resources/McpIntegrationDialog.js +2 -2
- package/dist/app/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/app/settings/useBuilderStatus.js +119 -40
- package/dist/app/settings/useBuilderStatus.js.map +1 -1
- package/dist/composer/ComposerContextMenu.d.ts +36 -2
- package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
- package/dist/composer/ComposerContextMenu.js +108 -40
- package/dist/composer/ComposerContextMenu.js.map +1 -1
- package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -1
- package/dist/composer/ComposerContextPickerDialog.js +63 -26
- package/dist/composer/ComposerContextPickerDialog.js.map +1 -1
- package/dist/composer/ComposerPlusMenu.d.ts +5 -3
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/composer/ComposerPlusMenu.js +166 -121
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/MentionItemMedia.d.ts.map +1 -1
- package/dist/composer/MentionItemMedia.js +3 -1
- package/dist/composer/MentionItemMedia.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +1 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +1 -0
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +117 -50
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/index.d.ts +1 -0
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +15 -0
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/composer/use-composer-panel-placement.d.ts.map +1 -1
- package/dist/composer/use-composer-panel-placement.js +2 -4
- package/dist/composer/use-composer-panel-placement.js.map +1 -1
- package/dist/composer/use-mention-search.d.ts +7 -0
- package/dist/composer/use-mention-search.d.ts.map +1 -1
- package/dist/composer/use-mention-search.js +86 -19
- package/dist/composer/use-mention-search.js.map +1 -1
- package/dist/composer/useComposerContextPicker.d.ts +5 -1
- package/dist/composer/useComposerContextPicker.d.ts.map +1 -1
- package/dist/composer/useComposerContextPicker.js +18 -4
- package/dist/composer/useComposerContextPicker.js.map +1 -1
- package/package.json +4 -4
- package/src/app/agentkit/react/components.tsx +158 -11
- package/src/app/agentkit/react/composer-context.spec.tsx +180 -0
- package/src/app/agentkit/react/core-file-storage.tsx +14 -0
- package/src/app/auth/AuthPage.interaction.spec.tsx +143 -0
- package/src/app/auth/AuthPage.tsx +159 -13
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +65 -3
- package/src/app/chat/AgentKitAssistantChat.tsx +57 -65
- package/src/app/chat/MultiTabAssistantChat.spec.tsx +21 -0
- package/src/app/chat/MultiTabAssistantChat.tsx +4 -0
- package/src/app/chat/agentkit-chat/root.tsx +91 -8
- package/src/app/chat/chat/surface-types.ts +1 -0
- package/src/app/chat/composer/runtime-adapters.tsx +2 -0
- package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +26 -1
- package/src/app/chat/composer/use-agentkit-capabilities.ts +11 -11
- package/src/app/chat/composer/use-agentkit-integration-menu.spec.tsx +2 -4
- package/src/app/chat/composer/use-workspace-composer-menu.spec.tsx +174 -0
- package/src/app/chat/composer/use-workspace-composer-menu.tsx +122 -0
- package/src/app/chat/index.ts +1 -0
- package/src/app/chat/prepare-prompt-composer-agent-handoff.spec.ts +162 -0
- package/src/app/chat/prepare-prompt-composer-agent-handoff.ts +134 -0
- package/src/app/i18n/catalogs/ar-SA.ts +6 -1
- package/src/app/i18n/catalogs/de-DE.ts +6 -1
- package/src/app/i18n/catalogs/en-US.ts +6 -1
- package/src/app/i18n/catalogs/es-ES.ts +6 -1
- package/src/app/i18n/catalogs/fr-FR.ts +6 -1
- package/src/app/i18n/catalogs/hi-IN.ts +6 -1
- package/src/app/i18n/catalogs/ja-JP.ts +6 -1
- package/src/app/i18n/catalogs/ko-KR.ts +6 -1
- package/src/app/i18n/catalogs/pt-BR.ts +6 -1
- package/src/app/i18n/catalogs/zh-CN.ts +6 -1
- package/src/app/i18n/catalogs/zh-TW.ts +6 -1
- package/src/app/resources/McpIntegrationDialog.tsx +5 -0
- package/src/app/settings/useBuilderStatus.spec.tsx +229 -13
- package/src/app/settings/useBuilderStatus.ts +145 -47
- package/src/composer/ComposerContextMenu.spec.tsx +95 -32
- package/src/composer/ComposerContextMenu.tsx +284 -73
- package/src/composer/ComposerContextPicker.spec.tsx +13 -14
- package/src/composer/ComposerContextPickerDialog.spec.tsx +40 -3
- package/src/composer/ComposerContextPickerDialog.tsx +158 -24
- package/src/composer/ComposerPlusMenu.parity.spec.tsx +442 -62
- package/src/composer/ComposerPlusMenu.tsx +322 -237
- package/src/composer/MentionItemMedia.tsx +3 -0
- package/src/composer/PromptComposer.context.spec.tsx +253 -82
- package/src/composer/PromptComposer.tsx +2 -0
- package/src/composer/TiptapComposer.spec.ts +6 -8
- package/src/composer/TiptapComposer.tsx +148 -55
- package/src/composer/index.ts +5 -0
- package/src/composer/runtime-adapters.tsx +18 -0
- package/src/composer/use-composer-panel-placement.ts +2 -4
- package/src/composer/use-mention-search.spec.tsx +99 -1
- package/src/composer/use-mention-search.ts +101 -24
- package/src/composer/useComposerContextPicker.ts +25 -5
|
@@ -856,6 +856,10 @@ export function useBuilderConnectFlow(
|
|
|
856
856
|
const [builderEnabled, setBuilderEnabled] = useState(false);
|
|
857
857
|
const [orgName, setOrgName] = useState<string | null>(null);
|
|
858
858
|
const [connecting, setConnecting] = useState(false);
|
|
859
|
+
const [reconciling, setReconciling] = useState<string | null>(null);
|
|
860
|
+
const pollEpochRef = useRef(0);
|
|
861
|
+
const [pollEpoch, setPollEpoch] = useState(0);
|
|
862
|
+
const pendingPollRef = useRef<number | null>(null);
|
|
859
863
|
const errorRevisionRef = useRef(0);
|
|
860
864
|
const [errorState, setErrorState] = useState<{
|
|
861
865
|
kind: BuilderConnectErrorKind;
|
|
@@ -888,6 +892,11 @@ export function useBuilderConnectFlow(
|
|
|
888
892
|
const provisionAccountAttemptRef = useRef(false);
|
|
889
893
|
const connectAttemptIdRef = useRef<string | null>(null);
|
|
890
894
|
const cancelledConnectAttemptIdRef = useRef<string | null>(null);
|
|
895
|
+
const blockedLaunchRef = useRef<{
|
|
896
|
+
from: string | null;
|
|
897
|
+
to: string;
|
|
898
|
+
revision: number;
|
|
899
|
+
} | null>(null);
|
|
891
900
|
const activePopupRef = useRef<Window | null>(null);
|
|
892
901
|
const confirmConnectSuccessRef = useRef<(attemptId: string) => Promise<void>>(
|
|
893
902
|
async () => {},
|
|
@@ -927,13 +936,14 @@ export function useBuilderConnectFlow(
|
|
|
927
936
|
source: trackingSource,
|
|
928
937
|
flow: trackingFlow,
|
|
929
938
|
});
|
|
939
|
+
// A new generation must not wait for a transport that ignores abort.
|
|
930
940
|
const getConnectPollInterval = useCallback(
|
|
931
941
|
() =>
|
|
932
942
|
Math.min(
|
|
933
943
|
MAX_POLL_INTERVAL_MS,
|
|
934
944
|
POLL_INTERVAL_MS * 2 ** statusPollFailuresRef.current,
|
|
935
945
|
),
|
|
936
|
-
[],
|
|
946
|
+
[pollEpoch],
|
|
937
947
|
);
|
|
938
948
|
|
|
939
949
|
useEffect(() => {
|
|
@@ -1062,6 +1072,7 @@ export function useBuilderConnectFlow(
|
|
|
1062
1072
|
setBuilderEnabled(false);
|
|
1063
1073
|
setOrgName(null);
|
|
1064
1074
|
setConnecting(false);
|
|
1075
|
+
setReconciling(null);
|
|
1065
1076
|
setError(null);
|
|
1066
1077
|
markStatusAvailable();
|
|
1067
1078
|
setAccountExists(false);
|
|
@@ -1079,15 +1090,31 @@ export function useBuilderConnectFlow(
|
|
|
1079
1090
|
let refreshGeneration = 0;
|
|
1080
1091
|
const refresh = async () => {
|
|
1081
1092
|
const errorRevisionAtStart = errorRevisionRef.current;
|
|
1093
|
+
const attemptIdAtStart = connectAttemptIdRef.current;
|
|
1082
1094
|
const generation = ++refreshGeneration;
|
|
1083
1095
|
const isCurrentRefresh = () => generation === refreshGeneration;
|
|
1084
1096
|
const s = await fetchStatus();
|
|
1085
|
-
|
|
1086
|
-
|
|
1097
|
+
const blocked = blockedLaunchRef.current;
|
|
1098
|
+
const sameBlockedLaunch =
|
|
1099
|
+
blocked?.from === attemptIdAtStart &&
|
|
1100
|
+
blocked.to === connectAttemptIdRef.current &&
|
|
1101
|
+
blocked.revision === errorRevisionRef.current;
|
|
1102
|
+
if (
|
|
1103
|
+
cancelled ||
|
|
1104
|
+
!mountedRef.current ||
|
|
1105
|
+
!isCurrentRefresh() ||
|
|
1106
|
+
(!sameBlockedLaunch &&
|
|
1107
|
+
(connectAttemptIdRef.current !== attemptIdAtStart ||
|
|
1108
|
+
errorRevisionRef.current !== errorRevisionAtStart))
|
|
1109
|
+
)
|
|
1110
|
+
return;
|
|
1087
1111
|
setHasFetchedStatus(true);
|
|
1088
1112
|
setStatusReadSettledCount((count) => count + 1);
|
|
1089
1113
|
if (!s) {
|
|
1090
|
-
|
|
1114
|
+
if (sameBlockedLaunch) {
|
|
1115
|
+
statusUnavailableRef.current = true;
|
|
1116
|
+
setStatusUnavailableRevision(errorRevisionAtStart);
|
|
1117
|
+
} else markStatusUnavailable();
|
|
1091
1118
|
return;
|
|
1092
1119
|
}
|
|
1093
1120
|
if (statusUnavailableRef.current) {
|
|
@@ -1102,7 +1129,11 @@ export function useBuilderConnectFlow(
|
|
|
1102
1129
|
setCanDisconnect(!!s.canDisconnect);
|
|
1103
1130
|
setAgentNativeProvisioningEnabled(!!s.agentNativeProvisioningEnabled);
|
|
1104
1131
|
setAgentNativeProvisioningToken(s.agentNativeProvisioningToken ?? null);
|
|
1105
|
-
|
|
1132
|
+
if (
|
|
1133
|
+
!connectAttemptIdRef.current ||
|
|
1134
|
+
cancelledConnectAttemptIdRef.current !== connectAttemptIdRef.current
|
|
1135
|
+
)
|
|
1136
|
+
setAccountExists(s.connectError?.code === "account_exists");
|
|
1106
1137
|
setBuilderEnabled(!!s.builderEnabled);
|
|
1107
1138
|
const nextConnectUrl = s.connectUrl ?? null;
|
|
1108
1139
|
setStatusConnectUrl(nextConnectUrl);
|
|
@@ -1116,6 +1147,7 @@ export function useBuilderConnectFlow(
|
|
|
1116
1147
|
if (connectComplete) {
|
|
1117
1148
|
connectStartedAtRef.current = null;
|
|
1118
1149
|
setConnecting(false);
|
|
1150
|
+
setReconciling(null);
|
|
1119
1151
|
}
|
|
1120
1152
|
if (connectComplete && !notifiedConnectedRef.current) {
|
|
1121
1153
|
notifiedConnectedRef.current = true;
|
|
@@ -1131,7 +1163,12 @@ export function useBuilderConnectFlow(
|
|
|
1131
1163
|
}
|
|
1132
1164
|
if (errorRevisionRef.current === errorRevisionAtStart) {
|
|
1133
1165
|
const activeConnectStartedAt = connectStartedAtRef.current;
|
|
1134
|
-
if (
|
|
1166
|
+
if (
|
|
1167
|
+
(!connectAttemptIdRef.current ||
|
|
1168
|
+
cancelledConnectAttemptIdRef.current !==
|
|
1169
|
+
connectAttemptIdRef.current) &&
|
|
1170
|
+
isCurrentConnectError(s.connectError, activeConnectStartedAt)
|
|
1171
|
+
) {
|
|
1135
1172
|
setError(s.connectError.message);
|
|
1136
1173
|
} else if (!activeConnectStartedAt && s.authError?.message) {
|
|
1137
1174
|
setError(s.authError.message);
|
|
@@ -1180,11 +1217,33 @@ export function useBuilderConnectFlow(
|
|
|
1180
1217
|
setError,
|
|
1181
1218
|
]);
|
|
1182
1219
|
|
|
1220
|
+
useEffect(() => {
|
|
1221
|
+
if (!reconciling) return;
|
|
1222
|
+
const timer = setTimeout(
|
|
1223
|
+
() => {
|
|
1224
|
+
if (connectAttemptIdRef.current !== reconciling) return;
|
|
1225
|
+
callbackSuccessCancelRef.current?.cancel();
|
|
1226
|
+
callbackSuccessRequestControllerRef.current?.controller.abort();
|
|
1227
|
+
connectStartedAtRef.current = null;
|
|
1228
|
+
setReconciling(null);
|
|
1229
|
+
if (pendingPollRef.current === pollEpochRef.current)
|
|
1230
|
+
markStatusUnavailable();
|
|
1231
|
+
},
|
|
1232
|
+
POPUP_CLOSED_CONFIRMATION_GRACE_MS +
|
|
1233
|
+
CALLBACK_SUCCESS_STATUS_RETRIES *
|
|
1234
|
+
(STATUS_FETCH_ABORT_MS + CALLBACK_SUCCESS_STATUS_RETRY_MS),
|
|
1235
|
+
);
|
|
1236
|
+
return () => clearTimeout(timer);
|
|
1237
|
+
}, [reconciling, markStatusUnavailable]);
|
|
1238
|
+
|
|
1183
1239
|
const retry = useCallback(() => retryStatusRef.current(), []);
|
|
1184
1240
|
const cancel = useCallback(() => {
|
|
1185
1241
|
const started = connectStartedAtRef.current;
|
|
1186
1242
|
if (started === null) return;
|
|
1187
1243
|
const attemptId = connectAttemptIdRef.current;
|
|
1244
|
+
setConnecting(false);
|
|
1245
|
+
setReconciling(attemptId);
|
|
1246
|
+
setPollEpoch(++pollEpochRef.current);
|
|
1188
1247
|
provisionAccountAttemptRef.current = false;
|
|
1189
1248
|
cancelledConnectAttemptIdRef.current = attemptId;
|
|
1190
1249
|
popupClosedAtRef.current ??= Date.now();
|
|
@@ -1192,6 +1251,7 @@ export function useBuilderConnectFlow(
|
|
|
1192
1251
|
callbackSuccessCancelRef.current.cancel();
|
|
1193
1252
|
if (callbackSuccessRequestControllerRef.current?.started === started)
|
|
1194
1253
|
callbackSuccessRequestControllerRef.current.controller.abort();
|
|
1254
|
+
callbackSuccessStartedAtRef.current = null;
|
|
1195
1255
|
try {
|
|
1196
1256
|
activePopupRef.current?.close();
|
|
1197
1257
|
} catch {
|
|
@@ -1218,7 +1278,9 @@ export function useBuilderConnectFlow(
|
|
|
1218
1278
|
(startOptions?: BuilderConnectStartOptions) => {
|
|
1219
1279
|
if (!enabled) return;
|
|
1220
1280
|
const started = Date.now();
|
|
1281
|
+
const previousAttemptId = connectAttemptIdRef.current;
|
|
1221
1282
|
const connectAttemptId = createBuilderConnectAttemptId();
|
|
1283
|
+
blockedLaunchRef.current = null;
|
|
1222
1284
|
const clickTrackingSource =
|
|
1223
1285
|
startOptions?.trackingSource ?? trackingSource;
|
|
1224
1286
|
const clickTrackingFlow = startOptions?.trackingFlow ?? trackingFlow;
|
|
@@ -1244,6 +1306,8 @@ export function useBuilderConnectFlow(
|
|
|
1244
1306
|
callbackSuccessRequestControllerRef.current?.controller.abort();
|
|
1245
1307
|
connectStartedAtRef.current = started;
|
|
1246
1308
|
connectAttemptIdRef.current = connectAttemptId;
|
|
1309
|
+
setReconciling(null);
|
|
1310
|
+
setPollEpoch(++pollEpochRef.current);
|
|
1247
1311
|
provisionAccountAttemptRef.current = provisionAccountForStart;
|
|
1248
1312
|
cancelledConnectAttemptIdRef.current = null;
|
|
1249
1313
|
statusPollFailuresRef.current = 0;
|
|
@@ -1551,18 +1615,21 @@ export function useBuilderConnectFlow(
|
|
|
1551
1615
|
"Couldn't open Builder. Allow popups and try again.",
|
|
1552
1616
|
"launch",
|
|
1553
1617
|
);
|
|
1618
|
+
blockedLaunchRef.current = {
|
|
1619
|
+
from: previousAttemptId,
|
|
1620
|
+
to: connectAttemptId,
|
|
1621
|
+
revision: errorRevisionRef.current,
|
|
1622
|
+
};
|
|
1554
1623
|
return;
|
|
1555
1624
|
}
|
|
1556
1625
|
|
|
1626
|
+
const isCurrentHostAttempt = () =>
|
|
1627
|
+
mountedRef.current &&
|
|
1628
|
+
connectAttemptIdRef.current === connectAttemptId &&
|
|
1629
|
+
cancelledConnectAttemptIdRef.current !== connectAttemptId;
|
|
1557
1630
|
void (async () => {
|
|
1558
1631
|
const s = await fetchStatus(undefined, connectAttemptId);
|
|
1559
|
-
if (
|
|
1560
|
-
!mountedRef.current ||
|
|
1561
|
-
connectAttemptIdRef.current !== connectAttemptId ||
|
|
1562
|
-
cancelledConnectAttemptIdRef.current === connectAttemptId
|
|
1563
|
-
) {
|
|
1564
|
-
return;
|
|
1565
|
-
}
|
|
1632
|
+
if (!isCurrentHostAttempt()) return;
|
|
1566
1633
|
if (s) markStatusAvailable();
|
|
1567
1634
|
else markStatusUnavailable();
|
|
1568
1635
|
if (s) {
|
|
@@ -1599,7 +1666,7 @@ export function useBuilderConnectFlow(
|
|
|
1599
1666
|
});
|
|
1600
1667
|
const openedByHost =
|
|
1601
1668
|
await openBuilderConnectViaMcpHost(trackedHostUrl);
|
|
1602
|
-
if (!
|
|
1669
|
+
if (!isCurrentHostAttempt() || openedByHost) return;
|
|
1603
1670
|
connectStartedAtRef.current = null;
|
|
1604
1671
|
setConnecting(false);
|
|
1605
1672
|
if (!s) restoreExistingAccount();
|
|
@@ -1738,12 +1805,18 @@ export function useBuilderConnectFlow(
|
|
|
1738
1805
|
usePollLoop(
|
|
1739
1806
|
async (signal) => {
|
|
1740
1807
|
const started = connectStartedAtRef.current;
|
|
1741
|
-
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
);
|
|
1746
|
-
if (
|
|
1808
|
+
const attemptId = connectAttemptIdRef.current;
|
|
1809
|
+
const epoch = pollEpochRef.current;
|
|
1810
|
+
if (started == null || !attemptId) return;
|
|
1811
|
+
pendingPollRef.current = epoch;
|
|
1812
|
+
const s = await fetchStatus(signal, attemptId);
|
|
1813
|
+
if (pendingPollRef.current === epoch) pendingPollRef.current = null;
|
|
1814
|
+
if (
|
|
1815
|
+
!mountedRef.current ||
|
|
1816
|
+
connectStartedAtRef.current !== started ||
|
|
1817
|
+
connectAttemptIdRef.current !== attemptId ||
|
|
1818
|
+
pollEpochRef.current !== epoch
|
|
1819
|
+
) {
|
|
1747
1820
|
return;
|
|
1748
1821
|
}
|
|
1749
1822
|
if (s) {
|
|
@@ -1767,7 +1840,8 @@ export function useBuilderConnectFlow(
|
|
|
1767
1840
|
setCanDisconnect(!!s.canDisconnect);
|
|
1768
1841
|
setAgentNativeProvisioningEnabled(!!s.agentNativeProvisioningEnabled);
|
|
1769
1842
|
setAgentNativeProvisioningToken(s.agentNativeProvisioningToken ?? null);
|
|
1770
|
-
|
|
1843
|
+
if (cancelledConnectAttemptIdRef.current !== attemptId)
|
|
1844
|
+
setAccountExists(s.connectError?.code === "account_exists");
|
|
1771
1845
|
setBuilderEnabled(!!s.builderEnabled);
|
|
1772
1846
|
const nextConnectUrl = s.connectUrl ?? null;
|
|
1773
1847
|
setStatusConnectUrl(nextConnectUrl);
|
|
@@ -1777,6 +1851,7 @@ export function useBuilderConnectFlow(
|
|
|
1777
1851
|
if (s && isBuilderConnectComplete(s, connectTargetRef.current)) {
|
|
1778
1852
|
setAccountExists(false);
|
|
1779
1853
|
setConnecting(false);
|
|
1854
|
+
setReconciling(null);
|
|
1780
1855
|
connectStartedAtRef.current = null;
|
|
1781
1856
|
notifiedConnectedRef.current = true;
|
|
1782
1857
|
notifyProvisionedAccount();
|
|
@@ -1788,7 +1863,10 @@ export function useBuilderConnectFlow(
|
|
|
1788
1863
|
// already flipped; re-arming the flow on a consumer callback failure
|
|
1789
1864
|
// would reconnect an account that is connected.
|
|
1790
1865
|
}
|
|
1791
|
-
} else if (
|
|
1866
|
+
} else if (
|
|
1867
|
+
cancelledConnectAttemptIdRef.current !== attemptId &&
|
|
1868
|
+
isCurrentConnectError(s?.connectError, started)
|
|
1869
|
+
) {
|
|
1792
1870
|
connectStartedAtRef.current = null;
|
|
1793
1871
|
setConnecting(false);
|
|
1794
1872
|
setAccountExists(s.connectError.code === "account_exists");
|
|
@@ -1798,6 +1876,7 @@ export function useBuilderConnectFlow(
|
|
|
1798
1876
|
: `Couldn't save Builder credentials: ${s.connectError.message}. Try again or contact support.`,
|
|
1799
1877
|
);
|
|
1800
1878
|
} else if (
|
|
1879
|
+
cancelledConnectAttemptIdRef.current !== attemptId &&
|
|
1801
1880
|
(isPopupClosed(activePopupRef.current) ||
|
|
1802
1881
|
popupClosedAtRef.current !== null) &&
|
|
1803
1882
|
callbackSuccessInFlightAtRef.current !== started
|
|
@@ -1821,7 +1900,10 @@ export function useBuilderConnectFlow(
|
|
|
1821
1900
|
});
|
|
1822
1901
|
if (s) setError(t("agentChat.settingsShell.builder.setupFailed"));
|
|
1823
1902
|
}
|
|
1824
|
-
} else if (
|
|
1903
|
+
} else if (
|
|
1904
|
+
cancelledConnectAttemptIdRef.current !== attemptId &&
|
|
1905
|
+
Date.now() - started > POLL_TIMEOUT_MS
|
|
1906
|
+
) {
|
|
1825
1907
|
connectStartedAtRef.current = null;
|
|
1826
1908
|
setConnecting(false);
|
|
1827
1909
|
const { source, flow } = activeTrackingRef.current;
|
|
@@ -1844,7 +1926,7 @@ export function useBuilderConnectFlow(
|
|
|
1844
1926
|
{
|
|
1845
1927
|
intervalMs: getConnectPollInterval,
|
|
1846
1928
|
leading: false,
|
|
1847
|
-
enabled: enabled && connecting,
|
|
1929
|
+
enabled: enabled && (connecting || !!reconciling),
|
|
1848
1930
|
},
|
|
1849
1931
|
);
|
|
1850
1932
|
|
|
@@ -1859,7 +1941,11 @@ export function useBuilderConnectFlow(
|
|
|
1859
1941
|
code: string | undefined,
|
|
1860
1942
|
attemptId: string | undefined,
|
|
1861
1943
|
) => {
|
|
1862
|
-
if (
|
|
1944
|
+
if (
|
|
1945
|
+
!isCurrentConnectAttempt(attemptId) ||
|
|
1946
|
+
cancelledConnectAttemptIdRef.current === attemptId
|
|
1947
|
+
)
|
|
1948
|
+
return;
|
|
1863
1949
|
connectStartedAtRef.current = null;
|
|
1864
1950
|
setConnecting(false);
|
|
1865
1951
|
setAccountExists(code === "account_exists");
|
|
@@ -1893,6 +1979,9 @@ export function useBuilderConnectFlow(
|
|
|
1893
1979
|
started,
|
|
1894
1980
|
cancel: cancelConfirmation,
|
|
1895
1981
|
};
|
|
1982
|
+
const isActiveConfirmation = () =>
|
|
1983
|
+
callbackSuccessCancelRef.current?.cancel === cancelConfirmation;
|
|
1984
|
+
const wasPopupClosed = () => popupClosedAtRef.current !== null;
|
|
1896
1985
|
try {
|
|
1897
1986
|
for (let i = 0; i < CALLBACK_SUCCESS_STATUS_RETRIES; i += 1) {
|
|
1898
1987
|
const controller =
|
|
@@ -1910,10 +1999,7 @@ export function useBuilderConnectFlow(
|
|
|
1910
1999
|
: null;
|
|
1911
2000
|
try {
|
|
1912
2001
|
s = await Promise.race([
|
|
1913
|
-
fetchStatus(
|
|
1914
|
-
controller?.signal,
|
|
1915
|
-
connectAttemptIdRef.current ?? undefined,
|
|
1916
|
-
),
|
|
2002
|
+
fetchStatus(controller?.signal, attemptId),
|
|
1917
2003
|
cancelledPromise,
|
|
1918
2004
|
]);
|
|
1919
2005
|
} finally {
|
|
@@ -1928,14 +2014,16 @@ export function useBuilderConnectFlow(
|
|
|
1928
2014
|
if (
|
|
1929
2015
|
cancelled ||
|
|
1930
2016
|
!mountedRef.current ||
|
|
1931
|
-
connectStartedAtRef.current !== started
|
|
2017
|
+
connectStartedAtRef.current !== started ||
|
|
2018
|
+
connectAttemptIdRef.current !== attemptId
|
|
1932
2019
|
) {
|
|
1933
2020
|
return;
|
|
1934
2021
|
}
|
|
1935
2022
|
if (s) markStatusAvailable();
|
|
1936
2023
|
if (
|
|
1937
2024
|
(s && isBuilderConnectComplete(s, connectTargetRef.current)) ||
|
|
1938
|
-
|
|
2025
|
+
(cancelledConnectAttemptIdRef.current !== attemptId &&
|
|
2026
|
+
isCurrentConnectError(s?.connectError, started))
|
|
1939
2027
|
) {
|
|
1940
2028
|
break;
|
|
1941
2029
|
}
|
|
@@ -1948,31 +2036,39 @@ export function useBuilderConnectFlow(
|
|
|
1948
2036
|
}
|
|
1949
2037
|
}
|
|
1950
2038
|
} finally {
|
|
1951
|
-
if (
|
|
2039
|
+
if (isActiveConfirmation()) {
|
|
1952
2040
|
callbackSuccessCancelRef.current = null;
|
|
1953
|
-
}
|
|
1954
|
-
if (callbackSuccessRequestControllerRef.current?.started === started) {
|
|
1955
|
-
callbackSuccessRequestControllerRef.current.controller.abort();
|
|
1956
|
-
callbackSuccessRequestControllerRef.current = null;
|
|
1957
|
-
}
|
|
1958
|
-
if (callbackSuccessInFlightAtRef.current === started) {
|
|
1959
|
-
callbackSuccessInFlightAtRef.current = null;
|
|
1960
2041
|
if (
|
|
1961
|
-
|
|
1962
|
-
!(s && isBuilderConnectComplete(s, connectTargetRef.current))
|
|
2042
|
+
callbackSuccessRequestControllerRef.current?.started === started
|
|
1963
2043
|
) {
|
|
1964
|
-
|
|
2044
|
+
callbackSuccessRequestControllerRef.current.controller.abort();
|
|
2045
|
+
callbackSuccessRequestControllerRef.current = null;
|
|
2046
|
+
}
|
|
2047
|
+
if (callbackSuccessInFlightAtRef.current === started) {
|
|
2048
|
+
callbackSuccessInFlightAtRef.current = null;
|
|
2049
|
+
if (
|
|
2050
|
+
wasPopupClosed() &&
|
|
2051
|
+
!(s && isBuilderConnectComplete(s, connectTargetRef.current))
|
|
2052
|
+
) {
|
|
2053
|
+
popupClosedAtRef.current = Date.now();
|
|
2054
|
+
}
|
|
1965
2055
|
}
|
|
1966
2056
|
}
|
|
1967
2057
|
}
|
|
1968
|
-
if (
|
|
2058
|
+
if (
|
|
2059
|
+
!mountedRef.current ||
|
|
2060
|
+
connectStartedAtRef.current !== started ||
|
|
2061
|
+
connectAttemptIdRef.current !== attemptId
|
|
2062
|
+
) {
|
|
1969
2063
|
return;
|
|
1970
2064
|
}
|
|
1971
2065
|
if (!s) return;
|
|
1972
2066
|
if (!isBuilderConnectComplete(s, connectTargetRef.current)) {
|
|
1973
|
-
const connectError =
|
|
1974
|
-
|
|
1975
|
-
|
|
2067
|
+
const connectError =
|
|
2068
|
+
cancelledConnectAttemptIdRef.current !== attemptId &&
|
|
2069
|
+
isCurrentConnectError(s.connectError, started)
|
|
2070
|
+
? s.connectError
|
|
2071
|
+
: null;
|
|
1976
2072
|
setHasFetchedStatus(true);
|
|
1977
2073
|
if (s) {
|
|
1978
2074
|
setStatusResolved(true);
|
|
@@ -1990,7 +2086,8 @@ export function useBuilderConnectFlow(
|
|
|
1990
2086
|
setAgentNativeProvisioningToken(
|
|
1991
2087
|
s.agentNativeProvisioningToken ?? null,
|
|
1992
2088
|
);
|
|
1993
|
-
|
|
2089
|
+
if (cancelledConnectAttemptIdRef.current !== attemptId)
|
|
2090
|
+
setAccountExists(s.connectError?.code === "account_exists");
|
|
1994
2091
|
setBuilderEnabled(!!s.builderEnabled);
|
|
1995
2092
|
const nextConnectUrl = s.connectUrl ?? null;
|
|
1996
2093
|
setStatusConnectUrl(nextConnectUrl);
|
|
@@ -2027,6 +2124,7 @@ export function useBuilderConnectFlow(
|
|
|
2027
2124
|
const org = s.orgName ?? null;
|
|
2028
2125
|
setOrgName(org);
|
|
2029
2126
|
setConnecting(false);
|
|
2127
|
+
setReconciling(null);
|
|
2030
2128
|
connectStartedAtRef.current = null;
|
|
2031
2129
|
notifyProvisionedAccount();
|
|
2032
2130
|
notifiedConnectedRef.current = true;
|
|
@@ -136,32 +136,91 @@ describe("connected composer menus", () => {
|
|
|
136
136
|
);
|
|
137
137
|
const open = async () => {
|
|
138
138
|
await openRoot();
|
|
139
|
-
if (document.querySelector('[role="menuitem"][aria-haspopup="menu"]'))
|
|
140
|
-
await key(row("Add context"), "ArrowRight");
|
|
141
139
|
};
|
|
142
140
|
async function click(label: string) {
|
|
143
141
|
await act(async () => row(label).click());
|
|
144
142
|
}
|
|
143
|
+
it("keeps integration failures visible with a working retry outside source choices", async () => {
|
|
144
|
+
const retry = vi.fn().mockResolvedValue(undefined);
|
|
145
|
+
await render(
|
|
146
|
+
[
|
|
147
|
+
{
|
|
148
|
+
id: "source",
|
|
149
|
+
label: "Reference",
|
|
150
|
+
section: "context",
|
|
151
|
+
onSelect: vi.fn(),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
{ integrationStatus: { loading: true } },
|
|
155
|
+
);
|
|
156
|
+
await openRoot();
|
|
157
|
+
const menu = menus()[0];
|
|
158
|
+
expect(menu.querySelector('[aria-busy="true"]')).not.toBeNull();
|
|
159
|
+
expect(menu.textContent).toContain("Loading...");
|
|
160
|
+
expect(menu.querySelector('[role="status"]')).toBeNull();
|
|
161
|
+
await render(
|
|
162
|
+
[
|
|
163
|
+
{
|
|
164
|
+
id: "source",
|
|
165
|
+
label: "Reference",
|
|
166
|
+
section: "context",
|
|
167
|
+
onSelect: vi.fn(),
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
{
|
|
171
|
+
integrationStatus: { error: "Connection unavailable", onRetry: retry },
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
expect(menus()[0].textContent).toContain("Connection unavailable");
|
|
175
|
+
expect(row("Reference")).toBeDefined();
|
|
176
|
+
await click("Retry");
|
|
177
|
+
expect(retry).toHaveBeenCalledOnce();
|
|
178
|
+
expect(menus()).toHaveLength(1);
|
|
179
|
+
});
|
|
145
180
|
it("keeps the root menu compact and puts context choices in nested menus", async () => {
|
|
146
|
-
await render(
|
|
181
|
+
await render(
|
|
182
|
+
[
|
|
183
|
+
{
|
|
184
|
+
id: "references",
|
|
185
|
+
label: "Design references",
|
|
186
|
+
order: 20,
|
|
187
|
+
children: items,
|
|
188
|
+
},
|
|
189
|
+
{ id: "figma", label: "Figma design", order: 10, onSelect: vi.fn() },
|
|
190
|
+
{ id: "zeta", label: "Zeta", order: 30, onSelect: vi.fn() },
|
|
191
|
+
{ id: "alpha", label: "Alpha", order: 30, onSelect: vi.fn() },
|
|
192
|
+
],
|
|
193
|
+
{ addAttachment: vi.fn() },
|
|
194
|
+
);
|
|
147
195
|
await openRoot();
|
|
148
196
|
expect(document.querySelectorAll('[role="searchbox"]')).toHaveLength(0);
|
|
149
|
-
|
|
150
|
-
expect(
|
|
151
|
-
|
|
197
|
+
expect(row("Files and images").querySelector("svg")).not.toBeNull();
|
|
198
|
+
expect(
|
|
199
|
+
Array.from(menus()[0].querySelectorAll('[role="menuitem"]')).map(
|
|
200
|
+
(entry) => entry.textContent,
|
|
201
|
+
),
|
|
202
|
+
).toEqual([
|
|
203
|
+
"Files and images",
|
|
204
|
+
"Figma design",
|
|
205
|
+
"Design references",
|
|
206
|
+
"Alpha",
|
|
207
|
+
"Zeta",
|
|
208
|
+
]);
|
|
209
|
+
expect(row("Design references").getAttribute("aria-haspopup")).toBe("menu");
|
|
210
|
+
expect(menus()[0].textContent).toContain("Add context");
|
|
152
211
|
expect(menus()).toHaveLength(1);
|
|
153
212
|
expect(menus()[0].classList.contains("w-64")).toBe(true);
|
|
154
213
|
expect(menus()[0].style.boxShadow).toBe("none");
|
|
155
214
|
expect(menus()[0].className).toContain("data-[state=open]:fade-in-100");
|
|
156
215
|
expect(menus()[0].className).toContain("data-[state=closed]:fade-out-100");
|
|
157
|
-
await key(row("
|
|
216
|
+
await key(row("Design references"), "ArrowRight");
|
|
158
217
|
expect(row("Documents")).toBeDefined();
|
|
159
218
|
expect(row("Library")).toBeDefined();
|
|
160
219
|
expect(document.querySelectorAll('[role="searchbox"]')).toHaveLength(0);
|
|
161
220
|
expect(menus()).toHaveLength(2);
|
|
162
|
-
expect(menus()[
|
|
163
|
-
expect(menus()[
|
|
164
|
-
expect(menus()[
|
|
221
|
+
expect(menus()[0].style.boxShadow).toBe("none");
|
|
222
|
+
expect(menus()[0].className).toContain("data-[state=open]:fade-in-100");
|
|
223
|
+
expect(menus()[0].className).toContain("data-[state=closed]:fade-out-100");
|
|
165
224
|
});
|
|
166
225
|
it("hides the Add context tooltip while the host storage popover is open", async () => {
|
|
167
226
|
const warning = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
@@ -198,7 +257,7 @@ describe("connected composer menus", () => {
|
|
|
198
257
|
expect(row("Workspace apps")).toBeDefined();
|
|
199
258
|
await key(row("Workspace apps"), "ArrowRight");
|
|
200
259
|
expect(row("DesignInteractive prototypes")).toBeDefined();
|
|
201
|
-
expect(menus()).toHaveLength(
|
|
260
|
+
expect(menus()).toHaveLength(2);
|
|
202
261
|
await key(row("DesignInteractive prototypes"), "Enter");
|
|
203
262
|
expect(menus()).toHaveLength(0);
|
|
204
263
|
});
|
|
@@ -235,13 +294,13 @@ describe("connected composer menus", () => {
|
|
|
235
294
|
await openRoot();
|
|
236
295
|
expect(menus()[0].classList.contains("w-64")).toBe(true);
|
|
237
296
|
expect(menus()[0].style.width).toBe("");
|
|
238
|
-
expect(menus()[0].style.maxHeight).toBe("
|
|
297
|
+
expect(menus()[0].style.maxHeight).toBe("376px");
|
|
239
298
|
},
|
|
240
299
|
);
|
|
241
300
|
it.each([
|
|
242
301
|
[8, "bottom", 240],
|
|
243
302
|
[40, "bottom", 208],
|
|
244
|
-
[100, "
|
|
303
|
+
[100, "bottom", 148],
|
|
245
304
|
[180, "top", 216],
|
|
246
305
|
] as const)(
|
|
247
306
|
"keeps actions reachable at plus-button top %ipx in a short viewport",
|
|
@@ -270,7 +329,7 @@ describe("connected composer menus", () => {
|
|
|
270
329
|
expect(menus()[0].style.maxHeight).toBe(`${maxHeight}px`);
|
|
271
330
|
expect(menus()[0].getAttribute("data-side")).toBe(side);
|
|
272
331
|
expect(menus()[0].querySelector('[role="menuitem"]')).not.toBeNull();
|
|
273
|
-
expect(menus()[0].
|
|
332
|
+
expect(menus()[0].classList.contains("overflow-y-auto")).toBe(true);
|
|
274
333
|
} finally {
|
|
275
334
|
window.innerHeight = previousHeight;
|
|
276
335
|
}
|
|
@@ -318,7 +377,7 @@ describe("connected composer menus", () => {
|
|
|
318
377
|
viewport.dispatchEvent(new Event("scroll"));
|
|
319
378
|
});
|
|
320
379
|
expect(menus()[0].style.maxHeight).toBe("118px");
|
|
321
|
-
await key(row("
|
|
380
|
+
await key(row("Documents"), "Escape");
|
|
322
381
|
expect(removeListener).toHaveBeenCalledWith(
|
|
323
382
|
"resize",
|
|
324
383
|
expect.any(Function),
|
|
@@ -351,7 +410,6 @@ describe("connected composer menus", () => {
|
|
|
351
410
|
},
|
|
352
411
|
]);
|
|
353
412
|
await openRoot();
|
|
354
|
-
await key(row("Add context"), "ArrowLeft");
|
|
355
413
|
expect(menus()[0].getAttribute("dir")).toBe("rtl");
|
|
356
414
|
await key(row("Source"), "ArrowLeft");
|
|
357
415
|
expect(row("One")).toBeDefined();
|
|
@@ -398,7 +456,6 @@ describe("connected composer menus", () => {
|
|
|
398
456
|
}
|
|
399
457
|
await key(row("Project brief"), "Escape");
|
|
400
458
|
await key(row("Documents"), "Escape");
|
|
401
|
-
await key(row("Add context"), "Escape");
|
|
402
459
|
expect(observation.disconnect).toHaveBeenCalled();
|
|
403
460
|
});
|
|
404
461
|
it("closes a context picker when its composer becomes disabled", async () => {
|
|
@@ -469,7 +526,7 @@ describe("connected composer menus", () => {
|
|
|
469
526
|
await key(row("Documents"), "ArrowRight");
|
|
470
527
|
expect(row("Project brief")).toBeDefined();
|
|
471
528
|
expect(row("Notes")).toBeDefined();
|
|
472
|
-
expect(menus()).toHaveLength(
|
|
529
|
+
expect(menus()).toHaveLength(2);
|
|
473
530
|
await click("Project brief");
|
|
474
531
|
expect(select).toHaveBeenCalledOnce();
|
|
475
532
|
expect(menus()).toHaveLength(0);
|
|
@@ -477,21 +534,20 @@ describe("connected composer menus", () => {
|
|
|
477
534
|
it("supports native keyboard navigation across nested context menus", async () => {
|
|
478
535
|
await render();
|
|
479
536
|
await openRoot();
|
|
480
|
-
await key(row("Add context"), "ArrowRight");
|
|
481
537
|
await key(row("Documents"), "ArrowRight");
|
|
482
|
-
expect(menus()).toHaveLength(
|
|
483
|
-
await key(row("
|
|
538
|
+
expect(menus()).toHaveLength(2);
|
|
539
|
+
await key(row("Archive"), "ArrowDown");
|
|
540
|
+
expect(document.activeElement).toBe(row("Project brief"));
|
|
541
|
+
await key(row("Project brief"), "ArrowUp");
|
|
484
542
|
expect(document.activeElement).toBe(row("Archive"));
|
|
485
543
|
await key(row("Archive"), "ArrowRight");
|
|
486
544
|
expect(row("Meeting notes")).toBeDefined();
|
|
487
|
-
expect(menus()).toHaveLength(4);
|
|
488
|
-
await key(row("Meeting notes"), "Escape");
|
|
489
545
|
expect(menus()).toHaveLength(3);
|
|
490
|
-
await key(row("
|
|
546
|
+
await key(row("Meeting notes"), "Escape");
|
|
491
547
|
expect(menus()).toHaveLength(2);
|
|
492
|
-
await key(row("
|
|
548
|
+
await key(row("Archive"), "Escape");
|
|
493
549
|
expect(menus()).toHaveLength(1);
|
|
494
|
-
await key(row("
|
|
550
|
+
await key(row("Documents"), "Escape");
|
|
495
551
|
expect(menus()).toHaveLength(0);
|
|
496
552
|
});
|
|
497
553
|
it("preserves native upload accepts, cancellation, multiple files and errors", async () => {
|
|
@@ -511,7 +567,7 @@ describe("connected composer menus", () => {
|
|
|
511
567
|
expect(input.multiple).toBe(true);
|
|
512
568
|
expect(input.accept).toBe("text/plain");
|
|
513
569
|
await openRoot();
|
|
514
|
-
await click("
|
|
570
|
+
await click("Files and images");
|
|
515
571
|
expect(picker).toHaveBeenCalledOnce();
|
|
516
572
|
await act(async () =>
|
|
517
573
|
input.dispatchEvent(new Event("change", { bubbles: true })),
|
|
@@ -527,11 +583,18 @@ describe("connected composer menus", () => {
|
|
|
527
583
|
});
|
|
528
584
|
it("requests gated upload after the menu closes", async () => {
|
|
529
585
|
const onAttachmentRequest = vi.fn();
|
|
530
|
-
|
|
586
|
+
const addAttachment = vi.fn();
|
|
587
|
+
await render([], { onAttachmentRequest, addAttachment });
|
|
588
|
+
const input =
|
|
589
|
+
document.querySelector<HTMLInputElement>('input[type="file"]');
|
|
590
|
+
if (!input) throw new Error("Missing file input");
|
|
591
|
+
const choose = vi.spyOn(input, "click");
|
|
531
592
|
await openRoot();
|
|
532
|
-
await click("
|
|
593
|
+
await click("Files and images");
|
|
533
594
|
await act(async () => new Promise((resolve) => setTimeout(resolve, 0)));
|
|
534
595
|
expect(onAttachmentRequest).toHaveBeenCalledOnce();
|
|
596
|
+
expect(choose).not.toHaveBeenCalled();
|
|
597
|
+
expect(addAttachment).not.toHaveBeenCalled();
|
|
535
598
|
expect(menus()).toHaveLength(0);
|
|
536
599
|
expect(document.activeElement?.getAttribute("aria-label")).toBe(
|
|
537
600
|
"Add context",
|
|
@@ -570,7 +633,7 @@ describe("connected composer menus", () => {
|
|
|
570
633
|
expect(select).toHaveBeenCalledOnce();
|
|
571
634
|
expect(container.querySelector('[aria-label="Prompt draft"]')).toBe(draft);
|
|
572
635
|
await act(async () => controls.onBack());
|
|
573
|
-
expect(menus()).toHaveLength(
|
|
636
|
+
expect(menus()).toHaveLength(1);
|
|
574
637
|
expect(dismiss).toHaveBeenCalledTimes(2);
|
|
575
638
|
});
|
|
576
639
|
it("ignores stale legacy close and reports resume of a removed source", async () => {
|
|
@@ -593,7 +656,7 @@ describe("connected composer menus", () => {
|
|
|
593
656
|
const old = controls;
|
|
594
657
|
await act(async () => controls.onBack());
|
|
595
658
|
await act(async () => old.onClose());
|
|
596
|
-
expect(menus()).toHaveLength(
|
|
659
|
+
expect(menus()).toHaveLength(1);
|
|
597
660
|
await render([]);
|
|
598
661
|
await act(async () => old.onResume());
|
|
599
662
|
expect(document.querySelector('[role="alert"]')?.textContent).toBe(
|
|
@@ -617,7 +680,7 @@ describe("connected composer menus", () => {
|
|
|
617
680
|
]);
|
|
618
681
|
await open();
|
|
619
682
|
await click("Reference");
|
|
620
|
-
expect(menus()).toHaveLength(
|
|
683
|
+
expect(menus()).toHaveLength(2);
|
|
621
684
|
expect(document.querySelector('[role="alert"]')?.textContent).toBe(
|
|
622
685
|
"Reference unavailable",
|
|
623
686
|
);
|