@agent-native/toolkit 0.205.0 → 0.206.0-nightly-20261009011246
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/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +17 -9
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +11 -0
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts +2 -0
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +24 -14
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentPanel.d.ts.map +1 -1
- package/dist/app/chat/AgentPanel.js +3 -2
- package/dist/app/chat/AgentPanel.js.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.d.ts +4 -1
- package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.js +61 -3
- package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
- package/dist/app/chat/agentkit-chat/approval-card.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/approval-card.js +2 -1
- package/dist/app/chat/agentkit-chat/approval-card.js.map +1 -1
- package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
- package/dist/app/chat/chat/run-recovery.js +74 -8
- package/dist/app/chat/chat/run-recovery.js.map +1 -1
- package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
- package/dist/app/chat/chat/tool-call-display.js +6 -4
- package/dist/app/chat/chat/tool-call-display.js.map +1 -1
- package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
- package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +2 -1
- package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
- package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/app/chat/conversation/AgentConversation.js +10 -7
- package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
- package/dist/app/chat/fallback-chat-title.d.ts.map +1 -1
- package/dist/app/chat/fallback-chat-title.js +2 -4
- package/dist/app/chat/fallback-chat-title.js.map +1 -1
- package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/app/chat/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/app/chat/session-replay-privacy.d.ts +7 -0
- package/dist/app/chat/session-replay-privacy.d.ts.map +1 -0
- package/dist/app/chat/session-replay-privacy.js +6 -0
- package/dist/app/chat/session-replay-privacy.js.map +1 -0
- package/dist/app/extensions/ExtensionViewer.d.ts +4 -1
- package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/app/extensions/ExtensionViewer.js +3 -3
- package/dist/app/extensions/ExtensionViewer.js.map +1 -1
- package/dist/app/extensions/ExtensionViewerPage.d.ts +4 -1
- package/dist/app/extensions/ExtensionViewerPage.d.ts.map +1 -1
- package/dist/app/extensions/ExtensionViewerPage.js +2 -2
- package/dist/app/extensions/ExtensionViewerPage.js.map +1 -1
- package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
- package/dist/app/onboarding/FirstRunOnboarding.js +53 -12
- package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
- package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
- package/dist/app/onboarding/OnboardingPanel.js +14 -4
- package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/app/resources/ResourceEditor.d.ts +4 -1
- package/dist/app/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/app/resources/ResourceEditor.js +18 -7
- package/dist/app/resources/ResourceEditor.js.map +1 -1
- package/dist/app/resources/ResourcesPanel.d.ts +4 -1
- package/dist/app/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/app/resources/ResourcesPanel.js +18 -18
- package/dist/app/resources/ResourcesPanel.js.map +1 -1
- package/dist/app/resources/custom-agent-model-options.d.ts +16 -0
- package/dist/app/resources/custom-agent-model-options.d.ts.map +1 -0
- package/dist/app/resources/custom-agent-model-options.js +53 -0
- package/dist/app/resources/custom-agent-model-options.js.map +1 -0
- package/dist/app/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/app/settings/SettingsPanel.js +34 -3
- package/dist/app/settings/SettingsPanel.js.map +1 -1
- package/dist/app/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/app/settings/SettingsTabsPage.js +2 -0
- package/dist/app/settings/SettingsTabsPage.js.map +1 -1
- package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
- package/dist/app/settings/api-keys/ApiKeyDialogs.js +49 -9
- package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
- package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +2 -1
- package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
- package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
- package/dist/app/settings/model/ModelSettingsPage.js +43 -9
- package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
- package/dist/app/settings/model/ProviderDialog.d.ts +4 -0
- package/dist/app/settings/model/ProviderDialog.d.ts.map +1 -1
- package/dist/app/settings/model/ProviderDialog.js +107 -11
- package/dist/app/settings/model/ProviderDialog.js.map +1 -1
- package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/app/settings/useBuilderStatus.js +13 -10
- package/dist/app/settings/useBuilderStatus.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +51 -13
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/package.json +4 -4
- package/src/app/agentkit/react/components.interaction.spec.tsx +229 -1
- package/src/app/agentkit/react/components.spec.tsx +42 -1
- package/src/app/agentkit/react/components.tsx +46 -12
- package/src/app/auth/AuthPage.tsx +13 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +116 -5
- package/src/app/chat/AgentKitAssistantChat.tsx +41 -20
- package/src/app/chat/AgentPanel.tsx +5 -1
- package/src/app/chat/MultiTabAssistantChat.spec.tsx +152 -7
- package/src/app/chat/MultiTabAssistantChat.tsx +71 -4
- package/src/app/chat/agentkit-chat/approval-card.spec.tsx +17 -0
- package/src/app/chat/agentkit-chat/approval-card.tsx +2 -0
- package/src/app/chat/chat/run-recovery.spec.tsx +354 -9
- package/src/app/chat/chat/run-recovery.tsx +178 -10
- package/src/app/chat/chat/tool-call-display.spec.tsx +77 -0
- package/src/app/chat/chat/tool-call-display.tsx +23 -6
- package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +2 -1
- package/src/app/chat/conversation/AgentConversation.spec.tsx +190 -1
- package/src/app/chat/conversation/AgentConversation.tsx +32 -8
- package/src/app/chat/fallback-chat-title.spec.ts +44 -0
- package/src/app/chat/fallback-chat-title.ts +3 -4
- package/src/app/chat/mcp-apps/McpAppRenderer.spec.ts +24 -0
- package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -2
- package/src/app/chat/session-replay-privacy.spec.ts +14 -0
- package/src/app/chat/session-replay-privacy.ts +5 -0
- package/src/app/extensions/ExtensionViewer.tsx +15 -3
- package/src/app/extensions/ExtensionViewerPage.tsx +7 -3
- package/src/app/onboarding/FirstRunOnboarding.spec.tsx +252 -2
- package/src/app/onboarding/FirstRunOnboarding.tsx +56 -9
- package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +1 -0
- package/src/app/onboarding/OnboardingPanel.tsx +15 -3
- package/src/app/resources/ResourceEditor.spec.tsx +240 -5
- package/src/app/resources/ResourceEditor.tsx +57 -10
- package/src/app/resources/ResourcesPanel.spec.ts +86 -4
- package/src/app/resources/ResourcesPanel.tsx +49 -16
- package/src/app/resources/custom-agent-model-options.ts +92 -0
- package/src/app/settings/SettingsPanel.model-defaults.spec.tsx +26 -13
- package/src/app/settings/SettingsPanel.tsx +35 -3
- package/src/app/settings/SettingsTabsPage.spec.tsx +97 -0
- package/src/app/settings/SettingsTabsPage.tsx +2 -0
- package/src/app/settings/api-keys/ApiKeyDialogs.tsx +84 -22
- package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +229 -1
- package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +3 -2
- package/src/app/settings/model/ModelSettingsPage.spec.tsx +99 -9
- package/src/app/settings/model/ModelSettingsPage.tsx +79 -11
- package/src/app/settings/model/ProviderDialog.spec.tsx +192 -1
- package/src/app/settings/model/ProviderDialog.tsx +225 -29
- package/src/app/settings/useBuilderStatus.spec.tsx +49 -0
- package/src/app/settings/useBuilderStatus.ts +16 -10
- package/src/composer/TiptapComposer.spec.ts +97 -2
- package/src/composer/TiptapComposer.tsx +67 -13
|
@@ -5,6 +5,7 @@ import { registerFirstRunOnboardingExtension } from "@agent-native/core/client/o
|
|
|
5
5
|
import { TooltipProvider } from "@agent-native/toolkit/ui/tooltip";
|
|
6
6
|
import React, { act } from "react";
|
|
7
7
|
import { createRoot, type Root } from "react-dom/client";
|
|
8
|
+
import { MemoryRouter, Route, Routes } from "react-router";
|
|
8
9
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
10
|
|
|
10
11
|
import { createToolkitI18nCatalog } from "../i18n.js";
|
|
@@ -31,7 +32,10 @@ const mocks = vi.hoisted(() => ({
|
|
|
31
32
|
completeFirstRun: vi.fn(),
|
|
32
33
|
useBuilderConnectFlow: vi.fn(),
|
|
33
34
|
routePathname: "/",
|
|
35
|
+
useActualRouter: false,
|
|
36
|
+
navigate: vi.fn(),
|
|
34
37
|
trackOnboardingEvent: vi.fn(),
|
|
38
|
+
setCustomKeyOnboardingAttempt: vi.fn(),
|
|
35
39
|
useOnboarding: vi.fn(),
|
|
36
40
|
useOnboardingPreviewMode: vi.fn(),
|
|
37
41
|
useOnboardingPreviewStep: vi.fn(),
|
|
@@ -41,7 +45,12 @@ vi.mock("react-router", async (importOriginal) => {
|
|
|
41
45
|
const actual = await importOriginal<typeof import("react-router")>();
|
|
42
46
|
return {
|
|
43
47
|
...actual,
|
|
44
|
-
useLocation: () =>
|
|
48
|
+
useLocation: () =>
|
|
49
|
+
mocks.useActualRouter
|
|
50
|
+
? actual.useLocation()
|
|
51
|
+
: { pathname: mocks.routePathname },
|
|
52
|
+
useNavigate: () =>
|
|
53
|
+
mocks.useActualRouter ? actual.useNavigate() : mocks.navigate,
|
|
45
54
|
};
|
|
46
55
|
});
|
|
47
56
|
|
|
@@ -50,6 +59,8 @@ vi.mock("@agent-native/core/client/feature-flags/use-feature-flag", () => ({
|
|
|
50
59
|
}));
|
|
51
60
|
|
|
52
61
|
vi.mock("@agent-native/core/client/onboarding/use-onboarding", () => ({
|
|
62
|
+
createOnboardingCorrelationId: () => "test-onboarding-correlation-id",
|
|
63
|
+
setCustomKeyOnboardingAttempt: mocks.setCustomKeyOnboardingAttempt,
|
|
53
64
|
trackOnboardingEvent: mocks.trackOnboardingEvent,
|
|
54
65
|
useOnboarding: mocks.useOnboarding,
|
|
55
66
|
}));
|
|
@@ -77,9 +88,13 @@ describe("FirstRunOnboarding", () => {
|
|
|
77
88
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
78
89
|
mocks.completeFirstRun.mockReset();
|
|
79
90
|
mocks.routePathname = "/";
|
|
91
|
+
mocks.useActualRouter = false;
|
|
92
|
+
mocks.navigate.mockReset();
|
|
80
93
|
mocks.completeFirstRun.mockResolvedValue(undefined);
|
|
81
94
|
mocks.useBuilderConnectFlow.mockReset();
|
|
82
95
|
mocks.trackOnboardingEvent.mockReset();
|
|
96
|
+
mocks.setCustomKeyOnboardingAttempt.mockReset();
|
|
97
|
+
mocks.setCustomKeyOnboardingAttempt.mockResolvedValue("stored");
|
|
83
98
|
mocks.useOnboarding.mockReset();
|
|
84
99
|
mocks.useOnboardingPreviewMode.mockReset();
|
|
85
100
|
mocks.useOnboardingPreviewStep.mockReset();
|
|
@@ -768,6 +783,11 @@ describe("FirstRunOnboarding", () => {
|
|
|
768
783
|
.querySelector("[data-testid='first-run-role-skip']")
|
|
769
784
|
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
770
785
|
});
|
|
786
|
+
expect(
|
|
787
|
+
document.body.querySelector(
|
|
788
|
+
'[data-testid="first-run-builder-create-account"]',
|
|
789
|
+
)?.textContent,
|
|
790
|
+
).toBe("Use Builder.io");
|
|
771
791
|
act(() => {
|
|
772
792
|
document.body
|
|
773
793
|
.querySelector('[data-testid="first-run-builder-create-account"]')
|
|
@@ -1325,6 +1345,7 @@ describe("FirstRunOnboarding", () => {
|
|
|
1325
1345
|
);
|
|
1326
1346
|
const attemptId = (methodClick?.[1] as Record<string, unknown>)
|
|
1327
1347
|
?.onboarding_attempt_id;
|
|
1348
|
+
expect(mocks.setCustomKeyOnboardingAttempt).toHaveBeenCalledWith(attemptId);
|
|
1328
1349
|
expect(mocks.trackOnboardingEvent).toHaveBeenCalledWith(
|
|
1329
1350
|
"onboarding_method_outcome",
|
|
1330
1351
|
expect.objectContaining({
|
|
@@ -1336,6 +1357,61 @@ describe("FirstRunOnboarding", () => {
|
|
|
1336
1357
|
window.history.replaceState(null, "", "/");
|
|
1337
1358
|
});
|
|
1338
1359
|
|
|
1360
|
+
it.each(["no_session", "unavailable"] as const)(
|
|
1361
|
+
"navigates while custom-key attempt storage resolves and reports %s",
|
|
1362
|
+
async (status) => {
|
|
1363
|
+
let resolveAttemptStorage:
|
|
1364
|
+
| ((status: "stored" | "no_session" | "unavailable") => void)
|
|
1365
|
+
| undefined;
|
|
1366
|
+
mocks.setCustomKeyOnboardingAttempt.mockReturnValue(
|
|
1367
|
+
new Promise((resolve) => {
|
|
1368
|
+
resolveAttemptStorage = resolve;
|
|
1369
|
+
}),
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1372
|
+
act(() => {
|
|
1373
|
+
root.render(
|
|
1374
|
+
<TooltipProvider>
|
|
1375
|
+
<FirstRunOnboarding />
|
|
1376
|
+
</TooltipProvider>,
|
|
1377
|
+
);
|
|
1378
|
+
});
|
|
1379
|
+
act(() => {
|
|
1380
|
+
document.body
|
|
1381
|
+
.querySelector("[data-testid='first-run-role-skip']")
|
|
1382
|
+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
await act(async () => {
|
|
1386
|
+
document.body
|
|
1387
|
+
.querySelector("[data-testid='first-run-open-key-settings']")
|
|
1388
|
+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
1389
|
+
await Promise.resolve();
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
expect(window.location.pathname).toBe(
|
|
1393
|
+
manualSetupSettingsRoute({ redesign: true }),
|
|
1394
|
+
);
|
|
1395
|
+
expect(mocks.trackOnboardingEvent).toHaveBeenCalledWith(
|
|
1396
|
+
"onboarding_method_outcome",
|
|
1397
|
+
expect.objectContaining({
|
|
1398
|
+
method_id: "custom_keys",
|
|
1399
|
+
outcome: "settings_opened",
|
|
1400
|
+
}),
|
|
1401
|
+
);
|
|
1402
|
+
|
|
1403
|
+
await act(async () => {
|
|
1404
|
+
resolveAttemptStorage?.(status);
|
|
1405
|
+
await Promise.resolve();
|
|
1406
|
+
});
|
|
1407
|
+
expect(mocks.trackOnboardingEvent).toHaveBeenCalledWith(
|
|
1408
|
+
"onboarding_correlation_unavailable",
|
|
1409
|
+
expect.objectContaining({ correlation_status: status }),
|
|
1410
|
+
);
|
|
1411
|
+
window.history.replaceState(null, "", "/");
|
|
1412
|
+
},
|
|
1413
|
+
);
|
|
1414
|
+
|
|
1339
1415
|
it("lets Clips skip provider setup and finish first-run onboarding", async () => {
|
|
1340
1416
|
let resolveCompletion: (() => void) | undefined;
|
|
1341
1417
|
mocks.completeFirstRun.mockImplementation(
|
|
@@ -1356,6 +1432,7 @@ describe("FirstRunOnboarding", () => {
|
|
|
1356
1432
|
completeFirstRun: mocks.completeFirstRun,
|
|
1357
1433
|
completeFirstRunError: null,
|
|
1358
1434
|
});
|
|
1435
|
+
mocks.routePathname = "/library";
|
|
1359
1436
|
|
|
1360
1437
|
await act(async () => {
|
|
1361
1438
|
root.render(
|
|
@@ -1399,7 +1476,8 @@ describe("FirstRunOnboarding", () => {
|
|
|
1399
1476
|
});
|
|
1400
1477
|
|
|
1401
1478
|
expect(mocks.completeFirstRun).toHaveBeenCalledOnce();
|
|
1402
|
-
expect(
|
|
1479
|
+
expect(mocks.navigate).toHaveBeenCalledOnce();
|
|
1480
|
+
expect(mocks.navigate).toHaveBeenCalledWith("/record", { replace: true });
|
|
1403
1481
|
expect(mocks.trackOnboardingEvent).toHaveBeenCalledWith(
|
|
1404
1482
|
"onboarding_step_skipped",
|
|
1405
1483
|
expect.objectContaining({
|
|
@@ -1414,6 +1492,63 @@ describe("FirstRunOnboarding", () => {
|
|
|
1414
1492
|
);
|
|
1415
1493
|
});
|
|
1416
1494
|
|
|
1495
|
+
it("navigates to Clips recording inside the router basename", async () => {
|
|
1496
|
+
mocks.useActualRouter = true;
|
|
1497
|
+
mocks.useOnboardingPreviewMode.mockReturnValue(true);
|
|
1498
|
+
mocks.useOnboardingPreviewStep.mockReturnValue("choice");
|
|
1499
|
+
mocks.useOnboarding.mockReturnValue({
|
|
1500
|
+
firstRun: true,
|
|
1501
|
+
loading: false,
|
|
1502
|
+
error: null,
|
|
1503
|
+
profile: {
|
|
1504
|
+
appId: "clips",
|
|
1505
|
+
appName: "Clips",
|
|
1506
|
+
capabilities: [],
|
|
1507
|
+
},
|
|
1508
|
+
completeFirstRun: mocks.completeFirstRun,
|
|
1509
|
+
completeFirstRunError: null,
|
|
1510
|
+
});
|
|
1511
|
+
vi.stubEnv("VITE_APP_BASE_PATH", "/clips");
|
|
1512
|
+
window.history.replaceState(
|
|
1513
|
+
null,
|
|
1514
|
+
"",
|
|
1515
|
+
"/clips/library?onboarding=preview&step=choice",
|
|
1516
|
+
);
|
|
1517
|
+
|
|
1518
|
+
await act(async () => {
|
|
1519
|
+
root.render(
|
|
1520
|
+
<MemoryRouter
|
|
1521
|
+
basename="/clips"
|
|
1522
|
+
initialEntries={["/clips/library?onboarding=preview&step=choice"]}
|
|
1523
|
+
>
|
|
1524
|
+
<Routes>
|
|
1525
|
+
<Route path="/library" element={<FirstRunOnboarding />} />
|
|
1526
|
+
<Route
|
|
1527
|
+
path="/record"
|
|
1528
|
+
element={<div data-testid="clips-record-route" />}
|
|
1529
|
+
/>
|
|
1530
|
+
<Route path="*" element={<div data-testid="not-found-route" />} />
|
|
1531
|
+
</Routes>
|
|
1532
|
+
</MemoryRouter>,
|
|
1533
|
+
);
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
await act(async () => {
|
|
1537
|
+
document.body
|
|
1538
|
+
.querySelector("[data-testid='first-run-setup-skip']")
|
|
1539
|
+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
1540
|
+
await Promise.resolve();
|
|
1541
|
+
await Promise.resolve();
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
expect(
|
|
1545
|
+
document.body.querySelector('[data-testid="clips-record-route"]'),
|
|
1546
|
+
).not.toBeNull();
|
|
1547
|
+
expect(
|
|
1548
|
+
document.body.querySelector('[data-testid="not-found-route"]'),
|
|
1549
|
+
).toBeNull();
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1417
1552
|
it("does not start duplicate manual setup attempts while completion is pending", async () => {
|
|
1418
1553
|
let resolveCompletion: (() => void) | undefined;
|
|
1419
1554
|
mocks.completeFirstRun.mockImplementation(
|
|
@@ -1619,6 +1754,56 @@ describe("FirstRunOnboarding", () => {
|
|
|
1619
1754
|
);
|
|
1620
1755
|
});
|
|
1621
1756
|
|
|
1757
|
+
it("does not reuse a failed skip redirect for manual setup completion", async () => {
|
|
1758
|
+
mocks.completeFirstRun
|
|
1759
|
+
.mockRejectedValueOnce(new Error("first-run completion failed: 500"))
|
|
1760
|
+
.mockResolvedValueOnce(undefined);
|
|
1761
|
+
mocks.useOnboarding.mockReturnValue({
|
|
1762
|
+
firstRun: true,
|
|
1763
|
+
loading: false,
|
|
1764
|
+
error: null,
|
|
1765
|
+
profile: {
|
|
1766
|
+
appId: "clips",
|
|
1767
|
+
appName: "Clips",
|
|
1768
|
+
capabilities: [],
|
|
1769
|
+
},
|
|
1770
|
+
completeFirstRun: mocks.completeFirstRun,
|
|
1771
|
+
completeFirstRunError: "first-run completion failed: 500",
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
await act(async () => {
|
|
1775
|
+
root.render(
|
|
1776
|
+
<TooltipProvider>
|
|
1777
|
+
<FirstRunOnboarding />
|
|
1778
|
+
</TooltipProvider>,
|
|
1779
|
+
);
|
|
1780
|
+
});
|
|
1781
|
+
act(() => {
|
|
1782
|
+
document.body
|
|
1783
|
+
.querySelector("[data-testid='first-run-role-skip']")
|
|
1784
|
+
?.click();
|
|
1785
|
+
});
|
|
1786
|
+
await act(async () => {
|
|
1787
|
+
document.body
|
|
1788
|
+
.querySelector("[data-testid='first-run-setup-skip']")
|
|
1789
|
+
?.click();
|
|
1790
|
+
await Promise.resolve();
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
expect(mocks.completeFirstRun).toHaveBeenCalledOnce();
|
|
1794
|
+
await act(async () => {
|
|
1795
|
+
document.body
|
|
1796
|
+
.querySelector("[data-testid='first-run-open-key-settings']")
|
|
1797
|
+
?.click();
|
|
1798
|
+
await Promise.resolve();
|
|
1799
|
+
await Promise.resolve();
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
expect(mocks.completeFirstRun).toHaveBeenCalledTimes(2);
|
|
1803
|
+
expect(mocks.navigate).not.toHaveBeenCalled();
|
|
1804
|
+
expect(window.location.pathname).toBe("/settings/model");
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1622
1807
|
it("preserves the completed step when first-run completion succeeds on retry", async () => {
|
|
1623
1808
|
let completionResult: boolean | void;
|
|
1624
1809
|
mocks.completeFirstRun
|
|
@@ -1983,4 +2168,69 @@ describe("FirstRunOnboarding", () => {
|
|
|
1983
2168
|
),
|
|
1984
2169
|
).toBeNull();
|
|
1985
2170
|
});
|
|
2171
|
+
|
|
2172
|
+
it("preserves a skip redirect when the diverted completion is retried", async () => {
|
|
2173
|
+
mocks.completeFirstRun
|
|
2174
|
+
.mockRejectedValueOnce(new Error("first-run completion failed: 500"))
|
|
2175
|
+
.mockResolvedValueOnce(undefined);
|
|
2176
|
+
mocks.useOnboarding.mockReturnValue({
|
|
2177
|
+
firstRun: true,
|
|
2178
|
+
loading: false,
|
|
2179
|
+
error: null,
|
|
2180
|
+
profile: {
|
|
2181
|
+
appId: "clips",
|
|
2182
|
+
appName: "Clips",
|
|
2183
|
+
capabilities: [],
|
|
2184
|
+
},
|
|
2185
|
+
completeFirstRun: mocks.completeFirstRun,
|
|
2186
|
+
completeFirstRunError: "first-run completion failed: 500",
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2189
|
+
await act(async () => {
|
|
2190
|
+
root.render(
|
|
2191
|
+
<TooltipProvider>
|
|
2192
|
+
<FirstRunOnboarding />
|
|
2193
|
+
</TooltipProvider>,
|
|
2194
|
+
);
|
|
2195
|
+
});
|
|
2196
|
+
act(() => {
|
|
2197
|
+
document.body
|
|
2198
|
+
.querySelector("[data-testid='first-run-role-skip']")
|
|
2199
|
+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
2200
|
+
});
|
|
2201
|
+
await act(async () => {
|
|
2202
|
+
document.body
|
|
2203
|
+
.querySelector("[data-testid='first-run-setup-skip']")
|
|
2204
|
+
?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
2205
|
+
await Promise.resolve();
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
const extensionSkip = [...document.body.querySelectorAll("button")].find(
|
|
2209
|
+
(button) => button.textContent === "Extension Skip",
|
|
2210
|
+
);
|
|
2211
|
+
if (extensionSkip) {
|
|
2212
|
+
await act(async () => {
|
|
2213
|
+
extensionSkip.click();
|
|
2214
|
+
await Promise.resolve();
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
expect(mocks.completeFirstRun).toHaveBeenCalledTimes(1);
|
|
2219
|
+
expect(document.body.textContent).toContain(
|
|
2220
|
+
"first-run completion failed: 500",
|
|
2221
|
+
);
|
|
2222
|
+
await act(async () => {
|
|
2223
|
+
[...document.body.querySelectorAll("button")]
|
|
2224
|
+
.find((button) => button.textContent === "Try again")
|
|
2225
|
+
?.click();
|
|
2226
|
+
await Promise.resolve();
|
|
2227
|
+
await Promise.resolve();
|
|
2228
|
+
});
|
|
2229
|
+
|
|
2230
|
+
expect(mocks.completeFirstRun).toHaveBeenCalledTimes(2);
|
|
2231
|
+
expect(mocks.navigate).toHaveBeenCalledOnce();
|
|
2232
|
+
expect(mocks.navigate).toHaveBeenCalledWith("/record", {
|
|
2233
|
+
replace: true,
|
|
2234
|
+
});
|
|
2235
|
+
});
|
|
1986
2236
|
});
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from "@agent-native/core/client/onboarding/first-run-registry";
|
|
8
8
|
import { saveFirstRunOnboardingRole } from "@agent-native/core/client/onboarding/first-run-status";
|
|
9
9
|
import {
|
|
10
|
+
createOnboardingCorrelationId,
|
|
11
|
+
setCustomKeyOnboardingAttempt,
|
|
10
12
|
trackOnboardingEvent,
|
|
11
13
|
useOnboarding,
|
|
12
14
|
} from "@agent-native/core/client/onboarding/use-onboarding";
|
|
@@ -37,7 +39,7 @@ import React, {
|
|
|
37
39
|
useRef,
|
|
38
40
|
useState,
|
|
39
41
|
} from "react";
|
|
40
|
-
import { useLocation } from "react-router";
|
|
42
|
+
import { useLocation, useNavigate } from "react-router";
|
|
41
43
|
|
|
42
44
|
import { useBuilderConnectFlow } from "../settings/useBuilderStatus.js";
|
|
43
45
|
import {
|
|
@@ -166,6 +168,7 @@ export function FirstRunOnboarding({
|
|
|
166
168
|
}: FirstRunOnboardingProps = {}) {
|
|
167
169
|
const t = useT();
|
|
168
170
|
const { pathname } = useLocation();
|
|
171
|
+
const navigate = useNavigate();
|
|
169
172
|
const previewMode = useOnboardingPreviewMode();
|
|
170
173
|
const previewStep = useOnboardingPreviewStep();
|
|
171
174
|
const {
|
|
@@ -301,18 +304,21 @@ export function FirstRunOnboarding({
|
|
|
301
304
|
const completionAttemptRef = useRef<{
|
|
302
305
|
screen: FirstRunScreen | null;
|
|
303
306
|
extensionIndex: number;
|
|
307
|
+
redirect: string | null;
|
|
304
308
|
} | null>(null);
|
|
305
309
|
const completionInFlightRef = useRef(false);
|
|
310
|
+
const completionRedirectRef = useRef<string | null>(null);
|
|
306
311
|
const setupSkipStartedRef = useRef(false);
|
|
307
312
|
const onboardingTerminalRef = useRef(false);
|
|
308
313
|
const abandonmentTrackedRef = useRef(false);
|
|
309
314
|
const setupAttemptRef = useRef<FirstRunSetupAttempt | null>(null);
|
|
310
315
|
const builderSetupAttemptRef = useRef<FirstRunSetupAttempt | null>(null);
|
|
316
|
+
const stepViewRef = useRef<{ key: string; id: string } | null>(null);
|
|
311
317
|
const startSetupMethod = useCallback(
|
|
312
318
|
(methodId: FirstRunSetupMethodId, methodKind: "builder" | "manual") => {
|
|
313
319
|
if (previewMode || typeof window === "undefined") return null;
|
|
314
320
|
const attempt = {
|
|
315
|
-
id:
|
|
321
|
+
id: createOnboardingCorrelationId(),
|
|
316
322
|
methodId,
|
|
317
323
|
outcomeTracked: false,
|
|
318
324
|
};
|
|
@@ -334,10 +340,14 @@ export function FirstRunOnboarding({
|
|
|
334
340
|
async (
|
|
335
341
|
completedScreen: FirstRunScreen | null,
|
|
336
342
|
completedExtensionIndex = extensionIndex,
|
|
343
|
+
retryRedirect: string | null = completionRedirectRef.current,
|
|
337
344
|
) => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
345
|
+
completionRedirectRef.current = null;
|
|
346
|
+
completionAttemptRef.current = {
|
|
347
|
+
screen: completedScreen,
|
|
348
|
+
extensionIndex: completedExtensionIndex,
|
|
349
|
+
redirect: retryRedirect,
|
|
350
|
+
};
|
|
341
351
|
completionInFlightRef.current = true;
|
|
342
352
|
try {
|
|
343
353
|
await completeFirstRun();
|
|
@@ -346,6 +356,9 @@ export function FirstRunOnboarding({
|
|
|
346
356
|
}
|
|
347
357
|
onboardingTerminalRef.current = true;
|
|
348
358
|
completionAttemptRef.current = null;
|
|
359
|
+
if (retryRedirect) {
|
|
360
|
+
navigate(retryRedirect, { replace: true });
|
|
361
|
+
}
|
|
349
362
|
return true;
|
|
350
363
|
} catch {
|
|
351
364
|
// coercion-ok: completeFirstRun exposes this failure as the inline retry state.
|
|
@@ -354,7 +367,13 @@ export function FirstRunOnboarding({
|
|
|
354
367
|
completionInFlightRef.current = false;
|
|
355
368
|
}
|
|
356
369
|
},
|
|
357
|
-
[
|
|
370
|
+
[
|
|
371
|
+
completeFirstRun,
|
|
372
|
+
extensionIndex,
|
|
373
|
+
navigate,
|
|
374
|
+
pathname,
|
|
375
|
+
trackFirstRunStepCompleted,
|
|
376
|
+
],
|
|
358
377
|
);
|
|
359
378
|
useEffect(() => {
|
|
360
379
|
if (!previewMode && firstRun && !loading && profile) {
|
|
@@ -362,7 +381,10 @@ export function FirstRunOnboarding({
|
|
|
362
381
|
}
|
|
363
382
|
}, [firstRun, loading, previewMode, profile]);
|
|
364
383
|
useEffect(() => {
|
|
365
|
-
if (previewMode || !firstRun || loading || !profile)
|
|
384
|
+
if (previewMode || !firstRun || loading || !profile) {
|
|
385
|
+
stepViewRef.current = null;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
366
388
|
const step = firstRunStepProperties(
|
|
367
389
|
screen,
|
|
368
390
|
beforeSetupExtensions,
|
|
@@ -371,7 +393,14 @@ export function FirstRunOnboarding({
|
|
|
371
393
|
extensionIndex,
|
|
372
394
|
extensionStepIndex,
|
|
373
395
|
);
|
|
374
|
-
|
|
396
|
+
const key = [step.step_id, step.extension_id, step.step_index].join(":");
|
|
397
|
+
if (stepViewRef.current?.key !== key) {
|
|
398
|
+
stepViewRef.current = { key, id: createOnboardingCorrelationId() };
|
|
399
|
+
}
|
|
400
|
+
trackOnboardingEvent("onboarding_step_viewed", {
|
|
401
|
+
...step,
|
|
402
|
+
step_view_id: stepViewRef.current.id,
|
|
403
|
+
});
|
|
375
404
|
}, [
|
|
376
405
|
afterSetupExtensions,
|
|
377
406
|
beforeSetupExtensions,
|
|
@@ -498,6 +527,7 @@ export function FirstRunOnboarding({
|
|
|
498
527
|
void finishOnboarding(
|
|
499
528
|
attempt?.screen ?? null,
|
|
500
529
|
attempt?.extensionIndex ?? extensionIndex,
|
|
530
|
+
attempt?.redirect ?? null,
|
|
501
531
|
);
|
|
502
532
|
}, [extensionIndex, finishOnboarding]);
|
|
503
533
|
const completionErrorProps = {
|
|
@@ -581,7 +611,23 @@ export function FirstRunOnboarding({
|
|
|
581
611
|
);
|
|
582
612
|
return;
|
|
583
613
|
}
|
|
614
|
+
const attemptStorage = attempt
|
|
615
|
+
? setCustomKeyOnboardingAttempt(attempt.id)
|
|
616
|
+
: null;
|
|
584
617
|
trackFirstRunSetupOutcome(attempt, "settings_opened");
|
|
618
|
+
if (attempt && attemptStorage) {
|
|
619
|
+
void attemptStorage.then((status) => {
|
|
620
|
+
if (status !== "stored") {
|
|
621
|
+
trackOnboardingEvent("onboarding_correlation_unavailable", {
|
|
622
|
+
flow: "first_run",
|
|
623
|
+
step_id: "choice",
|
|
624
|
+
method_id: "custom_keys",
|
|
625
|
+
onboarding_attempt_id: attempt.id,
|
|
626
|
+
correlation_status: status,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
585
631
|
if (typeof window === "undefined") return;
|
|
586
632
|
const search = new URLSearchParams(window.location.search);
|
|
587
633
|
search.delete(ONBOARDING_PREVIEW_QUERY_PARAM);
|
|
@@ -756,7 +802,7 @@ export function FirstRunOnboarding({
|
|
|
756
802
|
onClick={() => handleBuilder(true)}
|
|
757
803
|
disabled={connectFlow.connecting}
|
|
758
804
|
>
|
|
759
|
-
{t("agentChat.onboarding.
|
|
805
|
+
{t("agentChat.onboarding.builderCreateAccount")}
|
|
760
806
|
</button>
|
|
761
807
|
)}
|
|
762
808
|
<button
|
|
@@ -839,6 +885,7 @@ export function FirstRunOnboarding({
|
|
|
839
885
|
return;
|
|
840
886
|
setupSkipStartedRef.current = true;
|
|
841
887
|
trackFirstRunStepSkipped("choice");
|
|
888
|
+
completionRedirectRef.current = "/record";
|
|
842
889
|
handleFinish(null);
|
|
843
890
|
}}
|
|
844
891
|
>
|
|
@@ -9,6 +9,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
9
9
|
|
|
10
10
|
let steps: OnboardingStepStatus[] = [];
|
|
11
11
|
vi.mock("@agent-native/core/client/onboarding/use-onboarding", () => ({
|
|
12
|
+
createOnboardingCorrelationId: () => "test-onboarding-correlation-id",
|
|
12
13
|
trackOnboardingEvent: vi.fn(),
|
|
13
14
|
useOnboarding: () => ({
|
|
14
15
|
steps,
|
|
@@ -2,6 +2,7 @@ import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
|
|
|
2
2
|
import { agentNativePath } from "@agent-native/core/client/api-path";
|
|
3
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
4
|
import {
|
|
5
|
+
createOnboardingCorrelationId,
|
|
5
6
|
trackOnboardingEvent,
|
|
6
7
|
useOnboarding,
|
|
7
8
|
} from "@agent-native/core/client/onboarding/use-onboarding";
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
IconKey,
|
|
27
28
|
IconLoader2,
|
|
28
29
|
} from "@tabler/icons-react";
|
|
29
|
-
import React, { useState, useEffect, useId } from "react";
|
|
30
|
+
import React, { useState, useEffect, useId, useRef } from "react";
|
|
30
31
|
|
|
31
32
|
import {
|
|
32
33
|
BuilderConnectPopover,
|
|
@@ -74,21 +75,32 @@ export function OnboardingPanel({
|
|
|
74
75
|
const checklistVisible =
|
|
75
76
|
!loading && totalCount > 0 && (previewMode || (!dismissed && !allComplete));
|
|
76
77
|
const [expanded, setExpanded] = useState(true);
|
|
78
|
+
const stepViewRef = useRef<{ key: string; id: string } | null>(null);
|
|
77
79
|
|
|
78
80
|
useEffect(() => {
|
|
79
|
-
if (!checklistVisible)
|
|
81
|
+
if (!checklistVisible || !expanded) {
|
|
82
|
+
stepViewRef.current = null;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
80
85
|
const activeStepIndex = steps.findIndex(
|
|
81
86
|
(step) => step.id === currentStepId,
|
|
82
87
|
);
|
|
83
88
|
const activeStep = steps[activeStepIndex];
|
|
84
89
|
trackOnboardingEvent("onboarding_started", { flow: "checklist" });
|
|
85
90
|
if (!activeStep) return;
|
|
91
|
+
if (stepViewRef.current?.key !== activeStep.id) {
|
|
92
|
+
stepViewRef.current = {
|
|
93
|
+
key: activeStep.id,
|
|
94
|
+
id: createOnboardingCorrelationId(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
86
97
|
trackOnboardingEvent("onboarding_step_viewed", {
|
|
87
98
|
flow: "checklist",
|
|
88
99
|
step_id: activeStep.id,
|
|
89
100
|
step_index: activeStepIndex,
|
|
101
|
+
step_view_id: stepViewRef.current.id,
|
|
90
102
|
});
|
|
91
|
-
}, [checklistVisible, currentStepId, previewMode, steps]);
|
|
103
|
+
}, [checklistVisible, currentStepId, expanded, previewMode, steps]);
|
|
92
104
|
|
|
93
105
|
if (loading || totalCount === 0) return null;
|
|
94
106
|
if (!previewMode) {
|