@agent-native/toolkit 0.216.0 → 0.217.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.
Files changed (76) hide show
  1. package/agent-native.eject.json +5 -0
  2. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  3. package/dist/app/auth/AuthPage.js +33 -44
  4. package/dist/app/auth/AuthPage.js.map +1 -1
  5. package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
  6. package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
  7. package/dist/app/onboarding/GettingStartedCard.js +38 -0
  8. package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
  9. package/dist/app/onboarding/index.d.ts +1 -0
  10. package/dist/app/onboarding/index.d.ts.map +1 -1
  11. package/dist/app/onboarding/index.js +1 -0
  12. package/dist/app/onboarding/index.js.map +1 -1
  13. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  14. package/dist/app/providers/AppProviders.js +3 -1
  15. package/dist/app/providers/AppProviders.js.map +1 -1
  16. package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
  17. package/dist/app/settings/AccountSettingsCard.js +2 -1
  18. package/dist/app/settings/AccountSettingsCard.js.map +1 -1
  19. package/dist/app/settings/index.d.ts +1 -0
  20. package/dist/app/settings/index.d.ts.map +1 -1
  21. package/dist/app/settings/index.js +1 -0
  22. package/dist/app/settings/index.js.map +1 -1
  23. package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
  24. package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
  25. package/dist/app/settings/shell/SettingsShell.js +87 -20
  26. package/dist/app/settings/shell/SettingsShell.js.map +1 -1
  27. package/dist/app/settings/shell/bridge.d.ts +5 -3
  28. package/dist/app/settings/shell/bridge.d.ts.map +1 -1
  29. package/dist/app/settings/shell/bridge.js +8 -5
  30. package/dist/app/settings/shell/bridge.js.map +1 -1
  31. package/dist/app/settings/shell/index.d.ts +2 -2
  32. package/dist/app/settings/shell/index.d.ts.map +1 -1
  33. package/dist/app/settings/shell/index.js.map +1 -1
  34. package/dist/app/settings/shell/registry.d.ts +9 -1
  35. package/dist/app/settings/shell/registry.d.ts.map +1 -1
  36. package/dist/app/settings/shell/registry.js +16 -0
  37. package/dist/app/settings/shell/registry.js.map +1 -1
  38. package/dist/app/settings/shell/routing.d.ts +5 -4
  39. package/dist/app/settings/shell/routing.d.ts.map +1 -1
  40. package/dist/app/settings/shell/routing.js +38 -10
  41. package/dist/app/settings/shell/routing.js.map +1 -1
  42. package/dist/app/shared/CommandMenu.d.ts +86 -6
  43. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  44. package/dist/app/shared/CommandMenu.js +303 -41
  45. package/dist/app/shared/CommandMenu.js.map +1 -1
  46. package/dist/app/shared/ConsentBanner.d.ts +9 -0
  47. package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
  48. package/dist/app/shared/ConsentBanner.js +35 -0
  49. package/dist/app/shared/ConsentBanner.js.map +1 -0
  50. package/dist/ui/sonner.d.ts +2 -2
  51. package/dist/ui/sonner.d.ts.map +1 -1
  52. package/dist/ui/sonner.js +2 -2
  53. package/dist/ui/sonner.js.map +1 -1
  54. package/package.json +3 -3
  55. package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
  56. package/src/app/auth/AuthPage.tsx +32 -39
  57. package/src/app/onboarding/GettingStartedCard.tsx +203 -0
  58. package/src/app/onboarding/index.ts +1 -0
  59. package/src/app/providers/AppProviders.tsx +7 -1
  60. package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
  61. package/src/app/settings/AccountSettingsCard.tsx +8 -0
  62. package/src/app/settings/index.ts +1 -0
  63. package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
  64. package/src/app/settings/shell/SettingsShell.tsx +245 -31
  65. package/src/app/settings/shell/bridge.spec.ts +12 -0
  66. package/src/app/settings/shell/bridge.ts +17 -4
  67. package/src/app/settings/shell/index.ts +6 -1
  68. package/src/app/settings/shell/registry.ts +33 -1
  69. package/src/app/settings/shell/routing.spec.ts +92 -1
  70. package/src/app/settings/shell/routing.ts +52 -9
  71. package/src/app/shared/CommandMenu.spec.tsx +1662 -1
  72. package/src/app/shared/CommandMenu.tsx +763 -164
  73. package/src/app/shared/ConsentBanner.spec.tsx +141 -0
  74. package/src/app/shared/ConsentBanner.tsx +141 -0
  75. package/src/ui/sonner.spec.ts +10 -0
  76. package/src/ui/sonner.tsx +2 -2
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getConsentChoice, resolveConsentRegion, setConsentChoice, shouldShowConsentBanner, subscribeConsent, } from "@agent-native/core/client/consent";
3
+ import { useT } from "@agent-native/core/client/i18n";
4
+ import { Button } from "@agent-native/toolkit/ui/button";
5
+ import { Label } from "@agent-native/toolkit/ui/label";
6
+ import { Switch } from "@agent-native/toolkit/ui/switch";
7
+ import { useEffect, useState, useSyncExternalStore } from "react";
8
+ const DEFAULT_PRIVACY_POLICY_HREF = "https://www.agent-native.com/privacy";
9
+ /**
10
+ * The cookie banner for visitors in the consent region. Renders nothing
11
+ * elsewhere, and nothing once the visitor has chosen, until
12
+ * `openConsentSettings()` reopens it.
13
+ */
14
+ export function ConsentBanner({ privacyPolicyHref = DEFAULT_PRIVACY_POLICY_HREF, }) {
15
+ const t = useT();
16
+ const open = useSyncExternalStore(subscribeConsent, shouldShowConsentBanner, () => false);
17
+ const [managing, setManaging] = useState(false);
18
+ const [performance, setPerformance] = useState(false);
19
+ const [targeting, setTargeting] = useState(false);
20
+ useEffect(() => {
21
+ void resolveConsentRegion();
22
+ }, []);
23
+ useEffect(() => {
24
+ if (!open)
25
+ return;
26
+ const stored = getConsentChoice();
27
+ setPerformance(stored?.performance ?? false);
28
+ setTargeting(stored?.targeting ?? false);
29
+ setManaging(false);
30
+ }, [open]);
31
+ if (!open)
32
+ return null;
33
+ return (_jsxs("section", { role: "region", "aria-label": t("consentBanner.label"), "data-agent-native-consent-banner": true, className: "fixed bottom-4 left-4 z-[100] flex w-[min(420px,calc(100vw-2rem))] flex-col gap-3 rounded-lg border bg-popover p-4 text-sm text-popover-foreground shadow-lg", children: [_jsxs("p", { className: "m-0 leading-relaxed", children: [t("consentBanner.message"), " ", _jsx("a", { href: privacyPolicyHref, target: "_blank", rel: "noreferrer", className: "underline underline-offset-2", children: t("consentBanner.privacyPolicy") })] }), managing ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-necessary", children: t("consentBanner.necessary") }), _jsx(Switch, { id: "an-consent-necessary", checked: true, disabled: true })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-performance", children: t("consentBanner.performance") }), _jsx(Switch, { id: "an-consent-performance", checked: performance, onCheckedChange: setPerformance })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-targeting", children: t("consentBanner.targeting") }), _jsx(Switch, { id: "an-consent-targeting", checked: targeting, onCheckedChange: setTargeting })] })] })) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance: false, targeting: false }), children: t("consentBanner.rejectAll") }), _jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance: true, targeting: true }), children: t("consentBanner.acceptAll") }), managing ? (_jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance, targeting }), children: t("consentBanner.save") })) : (_jsx(Button, { variant: "ghost", size: "sm", className: "flex-1", onClick: () => setManaging(true), children: t("consentBanner.manage") }))] })] }));
34
+ }
35
+ //# sourceMappingURL=ConsentBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsentBanner.js","sourceRoot":"","sources":["../../../src/app/shared/ConsentBanner.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Toaster as Sonner } from "sonner";
1
+ import { toast as sonnerToast, Toaster as Sonner } from "sonner";
2
2
  type ToasterProps = React.ComponentProps<typeof Sonner>;
3
3
  declare const Toaster: ({ className, toastOptions, ...props }: ToasterProps) => import("react").JSX.Element;
4
- export { Toaster };
4
+ export { Toaster, sonnerToast };
5
5
  //# sourceMappingURL=sonner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sonner.d.ts","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAI3C,KAAK,YAAY,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AASxD,QAAA,MAAM,OAAO,0CAA2C,YAAY,gCAyCnE,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"sonner.d.ts","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIjE,KAAK,YAAY,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AASxD,QAAA,MAAM,OAAO,0CAA2C,YAAY,gCAyCnE,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC"}
package/dist/ui/sonner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useTheme } from "next-themes";
3
- import { Toaster as Sonner } from "sonner";
3
+ import { toast as sonnerToast, Toaster as Sonner } from "sonner";
4
4
  import { cn } from "../utils.js";
5
5
  const toastWidthClasses = "group-[.toaster]:!w-[var(--width)] group-[.toaster]:!min-w-[min(20rem,calc(100vw_-_2rem))] group-[.toaster]:!max-w-[var(--width)] group-[.toaster]:!gap-3 group-[.toaster]:!break-normal";
6
6
  const toastContentClasses = "group-[.toast]:!min-w-0 group-[.toast]:!flex-1 group-[.toast]:!basis-auto group-[.toast]:break-words";
@@ -21,5 +21,5 @@ const Toaster = ({ className, toastOptions, ...props }) => {
21
21
  },
22
22
  }, ...props }));
23
23
  };
24
- export { Toaster };
24
+ export { Toaster, sonnerToast };
25
25
  //# sourceMappingURL=sonner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sonner.js","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE3C,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAIjC,MAAM,iBAAiB,GACrB,0LAA0L,CAAC;AAC7L,MAAM,mBAAmB,GACvB,sGAAsG,CAAC;AACzG,MAAM,kBAAkB,GACtB,4DAA4D,CAAC;AAE/D,MAAM,OAAO,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,KAAK,EAAgB,EAAE,EAAE;IACtE,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC;IACxC,MAAM,UAAU,GAAG,YAAY,EAAE,UAAU,CAAC;IAE5C,OAAO,CACL,KAAC,MAAM,IACL,KAAK,EAAE,KAA8B,EACrC,SAAS,EAAE,EAAE,CACX,uDAAuD,EACvD,SAAS,CACV,EACD,YAAY,EAAE;YACZ,GAAG,YAAY;YACf,UAAU,EAAE;gBACV,GAAG,UAAU;gBACb,KAAK,EAAE,EAAE,CACP,iBAAiB,EACjB,uIAAuI,EACvI,UAAU,EAAE,KAAK,CAClB;gBACD,KAAK,EAAE,EAAE,CAAC,4BAA4B,EAAE,UAAU,EAAE,KAAK,CAAC;gBAC1D,WAAW,EAAE,EAAE,CACb,iEAAiE,EACjE,UAAU,EAAE,WAAW,CACxB;gBACD,OAAO,EAAE,EAAE,CAAC,mBAAmB,EAAE,UAAU,EAAE,OAAO,CAAC;gBACrD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,kEAAkE,EAClE,UAAU,EAAE,YAAY,CACzB;gBACD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,8DAA8D,EAC9D,UAAU,EAAE,YAAY,CACzB;aACF;SACF,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"sonner.js","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEjE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAIjC,MAAM,iBAAiB,GACrB,0LAA0L,CAAC;AAC7L,MAAM,mBAAmB,GACvB,sGAAsG,CAAC;AACzG,MAAM,kBAAkB,GACtB,4DAA4D,CAAC;AAE/D,MAAM,OAAO,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,KAAK,EAAgB,EAAE,EAAE;IACtE,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC;IACxC,MAAM,UAAU,GAAG,YAAY,EAAE,UAAU,CAAC;IAE5C,OAAO,CACL,KAAC,MAAM,IACL,KAAK,EAAE,KAA8B,EACrC,SAAS,EAAE,EAAE,CACX,uDAAuD,EACvD,SAAS,CACV,EACD,YAAY,EAAE;YACZ,GAAG,YAAY;YACf,UAAU,EAAE;gBACV,GAAG,UAAU;gBACb,KAAK,EAAE,EAAE,CACP,iBAAiB,EACjB,uIAAuI,EACvI,UAAU,EAAE,KAAK,CAClB;gBACD,KAAK,EAAE,EAAE,CAAC,4BAA4B,EAAE,UAAU,EAAE,KAAK,CAAC;gBAC1D,WAAW,EAAE,EAAE,CACb,iEAAiE,EACjE,UAAU,EAAE,WAAW,CACxB;gBACD,OAAO,EAAE,EAAE,CAAC,mBAAmB,EAAE,UAAU,EAAE,OAAO,CAAC;gBACrD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,kEAAkE,EAClE,UAAU,EAAE,YAAY,CACzB;gBACD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,8DAA8D,EAC9D,UAAU,EAAE,YAAY,CACzB;aACF;SACF,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/toolkit",
3
- "version": "0.216.0",
3
+ "version": "0.217.0",
4
4
  "description": "Reusable app-building UI and helpers for Agent-Native apps.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -678,7 +678,7 @@
678
678
  "y-protocols": "^1.0.7",
679
679
  "yjs": "^13.6.33",
680
680
  "zod": "^4.3.6",
681
- "@agent-native/agentkit": "^0.216.0"
681
+ "@agent-native/agentkit": "^0.217.0"
682
682
  },
683
683
  "devDependencies": {
684
684
  "@excalidraw/excalidraw": "0.18.1",
@@ -702,7 +702,7 @@
702
702
  "typescript-7": "npm:typescript@^7.0.2",
703
703
  "vite": "8.1.0",
704
704
  "vitest": "^4.1.5",
705
- "@agent-native/core": "^0.216.0"
705
+ "@agent-native/core": "^0.217.0"
706
706
  },
707
707
  "peerDependencies": {
708
708
  "@agent-native/core": ">=0.205.1",
@@ -247,6 +247,80 @@ describe("AuthPage interactions", () => {
247
247
  }
248
248
  });
249
249
 
250
+ it("stores no visitor id on the sign-in page before analytics consent", async () => {
251
+ const originalHref = window.location.href;
252
+ const sendBeacon = vi.fn(() => true);
253
+ Object.defineProperty(navigator, "sendBeacon", {
254
+ value: sendBeacon,
255
+ configurable: true,
256
+ });
257
+ window.happyDOM.setURL("https://clips.agent-native.com/signup");
258
+ // Earlier renders in this file already settled the region.
259
+ Reflect.deleteProperty(
260
+ globalThis,
261
+ Symbol.for("agent-native.client.consent"),
262
+ );
263
+ window.localStorage.removeItem("agent-native.anonymous_id");
264
+ document.cookie = "an_aid=; path=/; max-age=0";
265
+ window.sessionStorage.setItem("an_consent_region", "consent");
266
+ (
267
+ window as Window & { __AGENT_NATIVE_CONFIG__?: Record<string, string> }
268
+ ).__AGENT_NATIVE_CONFIG__ = {
269
+ agentNativeAnalyticsPublicKey: "anpk_test",
270
+ agentNativeAnalyticsEndpoint: "https://analytics.agent-native.com/track",
271
+ };
272
+
273
+ try {
274
+ await act(async () => {
275
+ root.render(
276
+ <AuthPage
277
+ {...propsFromHtml(
278
+ getOnboardingHtml({
279
+ requestHost: "clips.agent-native.com",
280
+ requestPath: "/signup",
281
+ }),
282
+ )}
283
+ initialView="signup"
284
+ />,
285
+ );
286
+ await Promise.resolve();
287
+ });
288
+
289
+ const email = container.querySelector<HTMLInputElement>("#s-email");
290
+ await act(async () => {
291
+ Object.getOwnPropertyDescriptor(
292
+ HTMLInputElement.prototype,
293
+ "value",
294
+ )!.set!.call(email, "person@example.com");
295
+ email!.dispatchEvent(new Event("input", { bubbles: true }));
296
+ });
297
+
298
+ expect(sendBeacon).toHaveBeenCalled();
299
+ for (const [, body] of sendBeacon.mock.calls as unknown as [
300
+ string,
301
+ string,
302
+ ][]) {
303
+ const sent = JSON.parse(body);
304
+ expect(sent.anonymousId).toBeUndefined();
305
+ expect(sent.properties.analytics_consent).toBe("withheld");
306
+ }
307
+ expect(window.localStorage.getItem("agent-native.anonymous_id")).toBe(
308
+ null,
309
+ );
310
+ expect(document.cookie).not.toMatch(/an_aid=[^;]/);
311
+ } finally {
312
+ delete (window as Window & { __AGENT_NATIVE_CONFIG__?: unknown })
313
+ .__AGENT_NATIVE_CONFIG__;
314
+ window.sessionStorage.removeItem("an_consent_region");
315
+ Reflect.deleteProperty(
316
+ globalThis,
317
+ Symbol.for("agent-native.client.consent"),
318
+ );
319
+ Reflect.deleteProperty(navigator, "sendBeacon");
320
+ window.happyDOM.setURL(originalHref);
321
+ }
322
+ });
323
+
250
324
  it("sends a beta app's sign-up events to beta Analytics", async () => {
251
325
  const originalHref = window.location.href;
252
326
  const sendBeacon = vi.fn(() => true);
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  captureAttribution,
5
+ getAnalyticsAnonymousId,
5
6
  getAnalyticsSessionId,
6
7
  } from "@agent-native/core/client/analytics";
7
8
  import { frameworkRoutePrefix } from "@agent-native/core/client/api-path";
@@ -12,6 +13,7 @@ import {
12
13
  normalizeOAuthReturnPath,
13
14
  shouldRetryAuthSessionProbe,
14
15
  } from "@agent-native/core/client/auth/auth-page-helpers";
16
+ import { whenConsentDecided } from "@agent-native/core/client/consent";
15
17
  import { openOAuthPopup } from "@agent-native/core/client/oauth-popup";
16
18
  import { navigateForSession } from "@agent-native/core/client/use-session";
17
19
  import { normalizeLocaleCode } from "@agent-native/core/localization/shared";
@@ -65,7 +67,6 @@ type AuthRequestResult = {
65
67
  const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
66
68
  const TAB_STORAGE_KEY = "an.onboarding.tab";
67
69
  const PENDING_SIGNUP_EMAIL_STORAGE_KEY = "an.onboarding.pendingSignupEmail";
68
- const ANALYTICS_ANONYMOUS_ID_KEY = "agent-native.anonymous_id";
69
70
  const GOOGLE_AUTH_URL_PATH = "/_agent-native/google/auth-url";
70
71
  const BUILDER_DESKTOP_RETURN_ORIGIN = "http://127.0.0.1:8080";
71
72
  const useIsomorphicLayoutEffect =
@@ -261,15 +262,6 @@ async function requestJson(
261
262
  return { response, data, readable };
262
263
  }
263
264
 
264
- function generateAnonymousId(): string {
265
- try {
266
- if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
267
- } catch {
268
- // coercion-ok: the browser fallback remains usable when crypto is unavailable.
269
- }
270
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
271
- }
272
-
273
265
  function trackAuth(
274
266
  app: string,
275
267
  name: string,
@@ -298,9 +290,8 @@ function trackAuth(
298
290
  }
299
291
  ).__AGENT_NATIVE_CONFIG__;
300
292
  if (!config?.agentNativeAnalyticsPublicKey) return;
301
- const anonymousId = readStorage(ANALYTICS_ANONYMOUS_ID_KEY);
302
- if (!anonymousId) return;
303
293
  const sessionId = getAnalyticsSessionId();
294
+ const timestamp = new Date().toISOString();
304
295
  const endpoint = resolveLaneEndpoint(
305
296
  config.agentNativeAnalyticsEndpoint ??
306
297
  "https://analytics.agent-native.com/track",
@@ -313,23 +304,33 @@ function trackAuth(
313
304
  }> = [{ name, properties: legacyProperties }];
314
305
  const canonical = canonicalTrackingEvent(name, legacyProperties);
315
306
  if (canonical) events.push(canonical);
316
- for (const event of events) {
317
- const body = JSON.stringify({
318
- publicKey: config.agentNativeAnalyticsPublicKey,
319
- event: event.name,
320
- properties: event.properties,
321
- anonymousId,
322
- sessionId,
323
- timestamp: new Date().toISOString(),
324
- });
325
- if (navigator.sendBeacon?.(endpoint, body)) continue;
326
- void fetch(endpoint, {
327
- method: "POST",
328
- body,
329
- keepalive: true,
330
- headers: { "Content-Type": "text/plain;charset=UTF-8" },
331
- }).catch(() => undefined);
332
- }
307
+ const publicKey = config.agentNativeAnalyticsPublicKey;
308
+ // The visitor id waits for analytics consent, as in the app bundle.
309
+ whenConsentDecided((decided) => {
310
+ const anonymousId = decided ? getAnalyticsAnonymousId() : undefined;
311
+ for (const event of events) {
312
+ const body = JSON.stringify({
313
+ publicKey,
314
+ event: event.name,
315
+ properties: anonymousId
316
+ ? event.properties
317
+ : {
318
+ ...event.properties,
319
+ analytics_consent: decided ? "withheld" : "unknown",
320
+ },
321
+ anonymousId,
322
+ sessionId,
323
+ timestamp,
324
+ });
325
+ if (navigator.sendBeacon?.(endpoint, body)) continue;
326
+ void fetch(endpoint, {
327
+ method: "POST",
328
+ body,
329
+ keepalive: true,
330
+ headers: { "Content-Type": "text/plain;charset=UTF-8" },
331
+ }).catch(() => undefined);
332
+ }
333
+ });
333
334
  } catch {
334
335
  // coercion-ok: analytics is best effort and cannot block authentication.
335
336
  }
@@ -1036,16 +1037,8 @@ export function AuthPage(props: AuthPageProps) {
1036
1037
  }, [apiPath, redirectToSignedInApp, runtimeBasePathResolved, view]);
1037
1038
 
1038
1039
  React.useEffect(() => {
1039
- let anonymousId = readStorage(ANALYTICS_ANONYMOUS_ID_KEY);
1040
- if (!/^[A-Za-z0-9_-]{1,128}$/.test(anonymousId)) {
1041
- anonymousId = generateAnonymousId();
1042
- writeStorage(ANALYTICS_ANONYMOUS_ID_KEY, anonymousId);
1043
- }
1044
- try {
1045
- document.cookie = `an_aid=${encodeURIComponent(anonymousId)}; path=/; max-age=2592000; SameSite=Lax`;
1046
- } catch {
1047
- // coercion-ok: attribution cookies are optional for authentication.
1048
- }
1040
+ // Stores `an_aid` only with analytics consent, and asks for the region.
1041
+ getAnalyticsAnonymousId();
1049
1042
  // Capture is once per page load, so it must wait for a workspace's
1050
1043
  // inferred base path or `landing_path` falls back to the wrong home.
1051
1044
  if (runtimeBasePathResolved) {
@@ -0,0 +1,203 @@
1
+ import { useOnboardingChecklist } from "@agent-native/core/client/onboarding/use-onboarding-checklist";
2
+ import { IconCheck, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
3
+ import type { ComponentType, ReactNode } from "react";
4
+
5
+ import { Button } from "../../ui/button.js";
6
+ import { Progress } from "../../ui/progress.js";
7
+ import { Skeleton } from "../../ui/skeleton.js";
8
+ import { cn } from "../../utils.js";
9
+
10
+ export interface GettingStartedLabels {
11
+ title: string;
12
+ progress: (completeCount: number, totalCount: number) => string;
13
+ /** Read by screen readers on a finished row; the check mark is decorative. */
14
+ completed: string;
15
+ collapse: string;
16
+ expand: string;
17
+ loadError: string;
18
+ retry: string;
19
+ }
20
+
21
+ export interface GettingStartedLinkProps {
22
+ to: string;
23
+ className?: string;
24
+ children: ReactNode;
25
+ }
26
+
27
+ function NativeLink({ to, className, children }: GettingStartedLinkProps) {
28
+ return (
29
+ <a href={to} className={className}>
30
+ {children}
31
+ </a>
32
+ );
33
+ }
34
+
35
+ export interface GettingStartedCardProps {
36
+ /** Name the steps were registered under with `registerOnboardingStep({ checklist })`. */
37
+ checklist: string;
38
+ labels: GettingStartedLabels;
39
+ /** Localized row titles by step id; a step without one shows its registered title. */
40
+ stepTitles?: Record<string, string>;
41
+ /** Router link for in-app rows so a click keeps the SPA and Cmd/Ctrl-click opens a tab. */
42
+ LinkComponent?: ComponentType<GettingStartedLinkProps>;
43
+ /** Number of rows to reserve while the first answer loads. */
44
+ expectedSteps?: number;
45
+ className?: string;
46
+ }
47
+
48
+ /**
49
+ * Progress checklist pinned to the app sidebar. Each app registers its own
50
+ * steps on the onboarding registry; the card only renders what the server
51
+ * derived. It renders nothing once every step is done.
52
+ */
53
+ export function GettingStartedCard({
54
+ checklist,
55
+ labels,
56
+ stepTitles,
57
+ LinkComponent = NativeLink,
58
+ expectedSteps = 6,
59
+ className,
60
+ }: GettingStartedCardProps) {
61
+ const {
62
+ steps,
63
+ completeCount,
64
+ totalCount,
65
+ dismissed,
66
+ collapsed,
67
+ status,
68
+ refresh,
69
+ setCollapsed,
70
+ } = useOnboardingChecklist(checklist);
71
+
72
+ const frame = cn(
73
+ "overflow-hidden rounded-[14px] border border-border bg-card text-card-foreground shadow-sm",
74
+ className,
75
+ );
76
+
77
+ if (status === "loading") {
78
+ return (
79
+ <div className={frame} aria-hidden>
80
+ <div className="space-y-3 px-4 py-3">
81
+ <Skeleton className="h-5 w-32" />
82
+ <Skeleton className="h-1.5 w-full rounded-full" />
83
+ </div>
84
+ <div className="border-t border-border">
85
+ {Array.from({ length: expectedSteps }, (_, index) => (
86
+ <div key={index} className="flex min-h-12 items-center gap-3 px-4">
87
+ <Skeleton className="size-4 rounded-full" />
88
+ <Skeleton className="h-3.5 w-28" />
89
+ </div>
90
+ ))}
91
+ </div>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ if (status === "error") {
97
+ return (
98
+ <div
99
+ role="alert"
100
+ className={cn(
101
+ frame,
102
+ "flex items-center justify-between gap-2 px-4 py-3",
103
+ )}
104
+ >
105
+ <span className="text-[13px] text-muted-foreground">
106
+ {labels.loadError}
107
+ </span>
108
+ <Button
109
+ type="button"
110
+ variant="outline"
111
+ size="xs"
112
+ onClick={() => void refresh()}
113
+ >
114
+ {labels.retry}
115
+ </Button>
116
+ </div>
117
+ );
118
+ }
119
+
120
+ if (dismissed || totalCount === 0 || completeCount === totalCount) {
121
+ return null;
122
+ }
123
+
124
+ const progressText = labels.progress(completeCount, totalCount);
125
+ const ToggleIcon = collapsed ? IconChevronUp : IconChevronDown;
126
+
127
+ return (
128
+ <section className={frame} aria-label={labels.title}>
129
+ <div className="space-y-3 px-4 py-3">
130
+ <div className="flex items-center justify-between gap-2">
131
+ <h2 className="text-base font-semibold leading-5">{labels.title}</h2>
132
+ <Button
133
+ type="button"
134
+ variant="ghost"
135
+ size="icon"
136
+ className="size-6"
137
+ aria-label={collapsed ? labels.expand : labels.collapse}
138
+ aria-expanded={!collapsed}
139
+ onClick={() => void setCollapsed(!collapsed)}
140
+ >
141
+ <ToggleIcon className="size-4" />
142
+ </Button>
143
+ </div>
144
+ <Progress
145
+ value={(completeCount / totalCount) * 100}
146
+ className="h-1.5 bg-muted"
147
+ aria-label={progressText}
148
+ />
149
+ </div>
150
+ {collapsed ? null : (
151
+ <ul className="border-t border-border">
152
+ {steps.map((step) => {
153
+ const rowClass =
154
+ "flex min-h-12 items-center gap-3 px-4 text-[13px]";
155
+ const content = (
156
+ <>
157
+ <span
158
+ aria-hidden
159
+ className={cn(
160
+ "flex size-4 shrink-0 items-center justify-center rounded-full border",
161
+ step.complete
162
+ ? "border-primary bg-primary text-primary-foreground"
163
+ : "border-muted-foreground/60",
164
+ )}
165
+ >
166
+ {step.complete ? <IconCheck className="size-3" /> : null}
167
+ </span>
168
+ <span
169
+ className={cn(
170
+ "min-w-0 flex-1 truncate",
171
+ step.complete && "text-muted-foreground",
172
+ )}
173
+ >
174
+ {stepTitles?.[step.id] ?? step.title}
175
+ </span>
176
+ {step.complete ? (
177
+ <span className="sr-only">{labels.completed}</span>
178
+ ) : null}
179
+ </>
180
+ );
181
+ return (
182
+ <li key={step.id}>
183
+ {step.href ? (
184
+ <LinkComponent
185
+ to={step.href}
186
+ className={cn(
187
+ rowClass,
188
+ "hover:bg-accent/60 focus-visible:bg-accent/60 focus-visible:outline-none",
189
+ )}
190
+ >
191
+ {content}
192
+ </LinkComponent>
193
+ ) : (
194
+ <div className={rowClass}>{content}</div>
195
+ )}
196
+ </li>
197
+ );
198
+ })}
199
+ </ul>
200
+ )}
201
+ </section>
202
+ );
203
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./FirstRunOnboarding.js";
2
+ export * from "./GettingStartedCard.js";
2
3
  export * from "./NewWorkspaceAppFlow.js";
3
4
  export * from "./OnboardingBanner.js";
4
5
  export * from "./OnboardingPanel.js";
@@ -39,6 +39,7 @@ import {
39
39
  type AppShellSkeletonLayout,
40
40
  } from "../shared/AppShellSkeleton.js";
41
41
  import { ClientOnly } from "../shared/ClientOnly.js";
42
+ import { ConsentBanner } from "../shared/ConsentBanner.js";
42
43
  import { EnvironmentBadge } from "../shared/EnvironmentBadge.js";
43
44
  import { RouteTransitionIndicator } from "../shared/RouteTransitionIndicator.js";
44
45
  import { RuntimeConfigNotice } from "../shared/RuntimeConfigNotice.js";
@@ -436,12 +437,17 @@ function ProvidersInner({
436
437
  settingsShortcut: boolean;
437
438
  children: React.ReactNode;
438
439
  }) {
440
+ // The banner reads the app's locale, so it sits inside the same provider.
439
441
  const localizedChildren =
440
442
  i18n === false ? (
441
- children
443
+ <>
444
+ {children}
445
+ {ownsDocument ? <ConsentBanner /> : null}
446
+ </>
442
447
  ) : (
443
448
  <AgentNativeI18nProvider {...(i18n ?? {})}>
444
449
  {children}
450
+ {ownsDocument ? <ConsentBanner /> : null}
445
451
  </AgentNativeI18nProvider>
446
452
  );
447
453
 
@@ -148,6 +148,7 @@ vi.mock("@agent-native/core/client/i18n", () => ({
148
148
  "settings.privacyDeletionDescription":
149
149
  "This records a deletion request; it does not delete data immediately. An administrator will verify your identity and complete the request under the deployment's retention and legal obligations.",
150
150
  "settings.privacyDocsLink": "Read privacy and data rights",
151
+ "consentBanner.cookieSettings": "Cookie settings",
151
152
  "common.cancel": "Cancel",
152
153
  "agentChat.auth.logOut": "Log out",
153
154
  }[key] ??
@@ -155,6 +156,11 @@ vi.mock("@agent-native/core/client/i18n", () => ({
155
156
  key,
156
157
  }));
157
158
 
159
+ const openConsentSettingsMock = vi.hoisted(() => vi.fn());
160
+ vi.mock("@agent-native/core/client/consent", () => ({
161
+ openConsentSettings: openConsentSettingsMock,
162
+ }));
163
+
158
164
  vi.mock("./SchedulingTimezoneField.js", () => ({
159
165
  SchedulingTimezoneField: () => <div data-testid="timezone" />,
160
166
  }));
@@ -454,4 +460,29 @@ describe("AccountSettingsForm name editing", () => {
454
460
  requestType: "deletion",
455
461
  });
456
462
  });
463
+
464
+ it("reopens the cookie banner from Privacy & data", async () => {
465
+ await act(async () => {
466
+ root.render(
467
+ <TooltipProvider>
468
+ <AccountSettingsForm />
469
+ </TooltipProvider>,
470
+ );
471
+ });
472
+ act(() => {
473
+ Array.from(
474
+ container.querySelectorAll<HTMLButtonElement>("#privacy-data button"),
475
+ )
476
+ .find((button) => button.textContent?.trim() === "Manage")
477
+ ?.click();
478
+ });
479
+
480
+ act(() => {
481
+ Array.from(document.querySelectorAll<HTMLButtonElement>("button"))
482
+ .find((button) => button.textContent?.trim() === "Cookie settings")
483
+ ?.click();
484
+ });
485
+
486
+ expect(openConsentSettingsMock).toHaveBeenCalledTimes(1);
487
+ });
457
488
  });
@@ -1,3 +1,4 @@
1
+ import { openConsentSettings } from "@agent-native/core/client/consent";
1
2
  import {
2
3
  useActionMutation,
3
4
  useActionQuery,
@@ -579,6 +580,13 @@ function PrivacySettings() {
579
580
  {t("settings.privacyRequestError")}
580
581
  </p>
581
582
  )}
583
+ <button
584
+ type="button"
585
+ onClick={openConsentSettings}
586
+ className="block text-xs font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
587
+ >
588
+ {t("consentBanner.cookieSettings")}
589
+ </button>
582
590
  <a
583
591
  href={docsUrl("privacy-and-data-rights")}
584
592
  target="_blank"
@@ -73,6 +73,7 @@ export {
73
73
  type SettingsGroupProps,
74
74
  type SettingsRowProps,
75
75
  } from "./SettingsRow.js";
76
+ export { ThemeFamilySettings } from "./account/ThemeFamilySettings.js";
76
77
  export {
77
78
  SettingsSection,
78
79
  SettingsSurfaceProvider,