@buildbase/sdk 0.0.88 → 0.0.90

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 (58) hide show
  1. package/AGENTS.md +7 -3
  2. package/CHANGELOG.md +515 -0
  3. package/README.md +120 -3
  4. package/dist/cli.mjs +1 -1
  5. package/dist/css/styles.css +1 -1
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.d.ts +1351 -12
  8. package/dist/index.mjs +1 -1
  9. package/dist/mcp/index.cjs +1 -1
  10. package/dist/mcp/index.d.ts +120 -2
  11. package/dist/mcp/index.mjs +1 -1
  12. package/dist/react/chunks/SettingsDialog-BVyV5Zfm.mjs +2 -0
  13. package/dist/react/chunks/SettingsDialog-Ch8X6WXy.cjs +2 -0
  14. package/dist/react/chunks/{SubscriptionDialog-B8FTFRtp.cjs → SubscriptionDialog-CGIvMKpT.cjs} +1 -1
  15. package/dist/react/chunks/SubscriptionDialog-g0LwHhbe.mjs +2 -0
  16. package/dist/react/chunks/ar-3aqWucom.mjs +2 -0
  17. package/dist/react/chunks/ar-DQVB89r8.cjs +2 -0
  18. package/dist/react/chunks/de-DO10bhnU.cjs +2 -0
  19. package/dist/react/chunks/de-HXQk5D6k.mjs +2 -0
  20. package/dist/react/chunks/es-BZsmhBN3.mjs +2 -0
  21. package/dist/react/chunks/es-Cxg-qhUh.cjs +2 -0
  22. package/dist/react/chunks/fr-COCMy3vW.cjs +2 -0
  23. package/dist/react/chunks/fr-rdnNHzxy.mjs +2 -0
  24. package/dist/react/chunks/hi-BB0Z-Dgn.cjs +2 -0
  25. package/dist/react/chunks/hi-t3RH7ITu.mjs +2 -0
  26. package/dist/react/chunks/ja-CDyla2g0.mjs +2 -0
  27. package/dist/react/chunks/ja-D92JgUlk.cjs +2 -0
  28. package/dist/react/chunks/react-DTq7A_PT.mjs +2 -0
  29. package/dist/react/chunks/react-DeapSbq6.cjs +2 -0
  30. package/dist/react/chunks/zh-C5HABZZV.mjs +2 -0
  31. package/dist/react/chunks/zh-DK2yA5wB.cjs +2 -0
  32. package/dist/react/index.cjs +1 -1
  33. package/dist/react/index.d.ts +1517 -14
  34. package/dist/react/index.mjs +1 -1
  35. package/docs/ERROR_CODES.md +218 -0
  36. package/docs/THEMING.md +89 -21
  37. package/docs/UI-CONFIG.md +23 -4
  38. package/docs/USER-IMPORT.md +320 -0
  39. package/package.json +2 -1
  40. package/dist/react/chunks/SettingsDialog-CKVE1PpH.mjs +0 -2
  41. package/dist/react/chunks/SettingsDialog-DrDmxFEj.cjs +0 -2
  42. package/dist/react/chunks/SubscriptionDialog-UN7o9Hqb.mjs +0 -2
  43. package/dist/react/chunks/ar-B2l90OZJ.cjs +0 -2
  44. package/dist/react/chunks/ar-DVVNZhXD.mjs +0 -2
  45. package/dist/react/chunks/de-BspJtvAC.mjs +0 -2
  46. package/dist/react/chunks/de-dURUqbl0.cjs +0 -2
  47. package/dist/react/chunks/es-DArjREi5.cjs +0 -2
  48. package/dist/react/chunks/es-vu4BmI3g.mjs +0 -2
  49. package/dist/react/chunks/fr-DVm5WCvZ.mjs +0 -2
  50. package/dist/react/chunks/fr-DhYDaYrX.cjs +0 -2
  51. package/dist/react/chunks/hi-C_nv_c1T.cjs +0 -2
  52. package/dist/react/chunks/hi-VW8xCj_K.mjs +0 -2
  53. package/dist/react/chunks/ja-C-b88dvw.mjs +0 -2
  54. package/dist/react/chunks/ja-CqtDkLHr.cjs +0 -2
  55. package/dist/react/chunks/react-CHivaJhA.mjs +0 -2
  56. package/dist/react/chunks/react-Ch_Suxxy.cjs +0 -2
  57. package/dist/react/chunks/zh-D1yfhUf0.mjs +0 -2
  58. package/dist/react/chunks/zh-D_tMpsNt.cjs +0 -2
@@ -1,2 +1,2 @@
1
1
  "use client";
2
- export{ba as APP_PERMISSION_FALLBACK_GROUP,bb as ApiVersion,bc as AuthApi,bd as AuthStatus,o as BBAction,be as BBScreen,q as BBStatus,bf as BB_PARAM,bg as BaseApi,bh as BetaForm,aq as BillingIntervals,bi as BuildBaseBadge,bj as BuildBaseProvider,bk as CURRENCY_DISPLAY,bl as CURRENCY_FLAG,bm as ConnectMcpGuide,C as ConnectedAgents,bn as ConnectedAgentsApi,bo as CreditActionsProvider,bp as CreditBalance,bq as CreditBalanceContextProvider,br as CreditBucketSource,bs as CreditBucketStatus,bt as CreditStorePage,z as CreditTransactionType,bu as DEFAULT_ROLE_PERMISSIONS,T as Devices,bv as DevicesApi,aH as DunningState,E as EmptyState,bw as EventEmitter,bx as FALLBACK_WORKSPACE_ROLE,by as FullScreenLoader,bz as INVITATION_ERROR_CODE,ao as InvoiceStatuses,L as LoadingState,bA as NotificationBell,bB as NotificationInbox,bC as NotificationsApi,bD as PLAN_CURRENCY_CODES,bE as PLAN_CURRENCY_OPTIONS,bF as PUSH_SERVICE_WORKER_SCRIPT,bG as PendingInvitations,P as Permission,bH as PricingPage,bI as PushApi,bJ as PushNotificationProvider,bK as QuotaUsageContextProvider,bL as SDKEvent,b5 as SETTINGS_SCREENS,bM as SUPPORTED_LOCALES,bj as SaaSOSProvider,t as SectionHeader,U as Sessions,bN as SessionsApi,bO as SettingsApi,a$ as SettingsScreen,b1 as SidebarNavItem,b0 as SidebarNavSection,s as StatusBanner,bP as SubscriptionContextProvider,ar as SubscriptionItemType,au as SubscriptionStatus,a6 as UPLOAD_ACCEPT,bQ as UPLOAD_MAX_BYTES,bR as UploadsApi,bS as UserApi,bT as WhenAuthenticated,bU as WhenCreditsAvailable,bV as WhenCreditsExhausted,bW as WhenCreditsLow,bX as WhenNoSubscription,bY as WhenNotTrialing,bZ as WhenPendingInvitations,b_ as WhenPermission,b$ as WhenQuotaAvailable,c0 as WhenQuotaExhausted,c1 as WhenQuotaOverage,c2 as WhenQuotaThreshold,c3 as WhenRoles,c4 as WhenSubscription,c5 as WhenSubscriptionToPlans,c6 as WhenTrialEnding,c7 as WhenTrialing,c8 as WhenUnauthenticated,c9 as WhenUserFeatureDisabled,ca as WhenUserFeatureEnabled,cb as WhenWorkspaceFeatureDisabled,cc as WhenWorkspaceFeatureEnabled,cd as WhenWorkspaceRoles,ce as WorkspaceApi,cf as WorkspaceSwitcher,cg as actionableInvitations,ad as appPermissionCatalogue,ch as calculateBillableSeats,ci as calculateSeatOverageCents,cj as calculateTotalSubscriptionCents,ck as cleanBBParams,cl as clearAuthIntent,cm as clearPendingInvite,cn as coerceBadgeRef,co as consumeAuthIntent,cp as createBBUrl,aJ as createCheckoutRedirectUrls,x as createCreditPurchaseRedirectUrls,cq as eventEmitter,cr as fillMcpTemplate,cs as formatCents,f as formatMinorAmountIntl,ct as formatOverageRate,cu as formatOverageRateWithLabel,cv as formatQuotaIncludedOverage,cw as formatQuotaWithPrice,cx as formatUploadLimit,b8 as getAvailableBillingIntervals,b7 as getAvailableCurrenciesFromPlans,aE as getBasePriceCents,aD as getBillingIntervalAndCurrencyFromPriceId,e as getCurrencyDecimals,a2 as getCurrencyFlag,g as getCurrencySymbol,cy as getDisplayCurrency,aG as getPerSeatPriceCents,b6 as getPricingVariant,at as getQuotaDisplayValue,as as getQuotaDisplayWithVariant,aO as getQuotaOverageCents,cz as getQuotaUnitLabelFromName,aF as getSeatPricing,aI as getStripePriceIdForInterval,cA as hasPermission,r as invalidateCreditBalance,cB as invalidateQuotaUsage,aK as invalidateSubscription,cC as inviterNameOf,cD as isInvitationActionable,cE as isInvitationError,aY as isInvitationLapsed,cF as isZeroDecimalCurrency,cG as mcpServerKey,b2 as mergeUIConfig,cH as minorAmountToDisplay,cI as parkPendingInvite,aV as pickDefaultRole,cJ as preflightUpload,cK as readBBParams,cL as readPendingInvite,aX as resendCooldownSeconds,cM as resolveMaxUsers,cN as resolvePermissions,ae as resolveRolePermissions,y as safeRedirect,cO as saveAuthIntent,cP as sortInvitations,cQ as uploadFailureOf,cR as uploadPolicyOf,cS as useAllQuotaUsage,aA as useBillingPortal,ay as useCancelSubscription,cT as useConnectedAgents,cU as useConnectedAgentsApi,cV as useConsumeCredits,ax as useCreateCheckoutSession,cW as useCreditBalance,j as useCreditBalanceContext,k as useCreditPackages,m as useCreditTransactions,cX as useDevices,cY as useDevicesApi,l as useExpiringCredits,cZ as useFullScreenLoader,c_ as useInvoice,am as useInvoices,c$ as useMcpConnection,d0 as useMyInvitations,d1 as useNotifications,d2 as useNotificationsApi,h as usePermissions,d3 as usePlanGroup,aB as usePlanGroupVersions,d4 as usePublicCreditPackages,d5 as usePublicPlanGroupVersion,d6 as usePublicPlans,n as usePurchaseCredits,aa as usePushNotifications,aM as useQuotaUsageContext,d7 as useQuotaUsageStatus,d8 as useRecordUsage,az as useResumeSubscription,aQ as useSaaSAuth,d9 as useSaaSOs,F as useSaaSSettings,A as useSaaSWorkspaces,aU as useSeatStatus,da as useSessions,db as useSessionsApi,dc as useSubscription,aN as useSubscriptionContext,aw as useSubscriptionManagement,u as useTranslation,dd as useTrialStatus,aW as useUIConfig,i as useUIVisibility,de as useUpdateSubscription,V as useUpload,df as useUploadsApi,dg as useUsageLogs,dh as useUserAttributes,di as useUserFeatures,aS as useWorkspaceInvitations,dj as validateInvite,aL as validateRedirectUrl,dk as workspaceIdOf,dl as workspaceNameOf,w as workspaceSettingsManager}from"./chunks/react-CHivaJhA.mjs";import"react/jsx-runtime";import"react";import"clsx";import"tailwind-merge";import"intl-messageformat";import"@radix-ui/react-slot";import"class-variance-authority";import"lucide-react";import"@radix-ui/react-dialog";import"@radix-ui/react-icons";import"@hookform/resolvers/zod";import"react-hook-form";import"@radix-ui/react-label";import"zod";import"@radix-ui/react-avatar";import"@radix-ui/react-radio-group";import"@radix-ui/react-scroll-area";import"@radix-ui/react-separator";import"@radix-ui/react-accordion";import"@radix-ui/react-alert-dialog";import"@radix-ui/react-popover";
2
+ export{bw as APP_PERMISSION_FALLBACK_GROUP,bx as ApiVersion,av as AuthApi,by as AuthStatus,E as BBAction,aA as BBScreen,F as BBStatus,az as BB_PARAM,bz as BaseApi,bA as BetaForm,aO as BillingIntervals,bB as BuildBaseBadge,bC as BuildBaseProvider,bD as CURRENCY_DISPLAY,bE as CURRENCY_FLAG,bF as ConnectMcpGuide,C as ConnectedAgents,bG as ConnectedAgentsApi,bH as CreditActionsProvider,bI as CreditBalance,bJ as CreditBalanceContextProvider,bK as CreditBucketSource,bL as CreditBucketStatus,bM as CreditStorePage,N as CreditTransactionType,bN as DEFAULT_ROLE_PERMISSIONS,a2 as Devices,bO as DevicesApi,b3 as DunningState,I as EmptyState,bP as EventEmitter,bQ as FALLBACK_WORKSPACE_ROLE,bR as FullScreenLoader,at as GuestUpgrade,bS as INVITATION_ERROR_CODE,bT as ImpersonationBanner,aM as InvoiceStatuses,L as LoadingState,bU as NotificationBell,bV as NotificationInbox,bW as NotificationsApi,bX as PLAN_CURRENCY_CODES,bY as PLAN_CURRENCY_OPTIONS,bZ as PUSH_SERVICE_WORKER_SCRIPT,b_ as PendingInvitations,P as Permission,b$ as PricingPage,c0 as PushApi,c1 as PushNotificationProvider,c2 as QuotaUsageContextProvider,c3 as SDKEvent,br as SETTINGS_SCREENS,c4 as SUPPORTED_LOCALES,bC as SaaSOSProvider,H as SectionHeader,a3 as Sessions,c5 as SessionsApi,c6 as SettingsApi,bk as SettingsScreen,bn as SidebarNavItem,bm as SidebarNavSection,S as StatusBanner,c7 as SubscriptionContextProvider,aP as SubscriptionItemType,aS as SubscriptionStatus,ah as UPLOAD_ACCEPT,c8 as UPLOAD_MAX_BYTES,c9 as UploadsApi,ca as UserApi,cb as WALLET_LINK_STATEMENT,aE as WalletFlowError,cc as WhenAuthenticated,cd as WhenCreditsAvailable,ce as WhenCreditsExhausted,cf as WhenCreditsLow,cg as WhenNoSubscription,ch as WhenNotTrialing,ci as WhenPendingInvitations,cj as WhenPermission,ck as WhenQuotaAvailable,cl as WhenQuotaExhausted,cm as WhenQuotaOverage,cn as WhenQuotaThreshold,co as WhenRoles,cp as WhenSubscription,cq as WhenSubscriptionToPlans,cr as WhenTrialEnding,cs as WhenTrialing,ct as WhenUnauthenticated,cu as WhenUserFeatureDisabled,cv as WhenUserFeatureEnabled,cw as WhenWorkspaceFeatureDisabled,cx as WhenWorkspaceFeatureEnabled,cy as WhenWorkspaceRoles,cz as WorkspaceApi,cA as WorkspaceSwitcher,cB as actionableInvitations,an as appPermissionCatalogue,cC as buildWalletMessage,cD as calculateBillableSeats,cE as calculateSeatOverageCents,cF as calculateTotalSubscriptionCents,cG as cleanBBParams,cH as clearAuthIntent,cI as clearPendingInvite,cJ as coerceBadgeRef,aD as connectWallet,cK as consumeAuthIntent,cL as createBBUrl,b5 as createCheckoutRedirectUrls,K as createCreditPurchaseRedirectUrls,cM as eventEmitter,cN as fillMcpTemplate,cO as formatCents,o as formatMinorAmountIntl,cP as formatOverageRate,cQ as formatOverageRateWithLabel,cR as formatQuotaIncludedOverage,cS as formatQuotaWithPrice,cT as formatUploadLimit,bu as getAvailableBillingIntervals,bt as getAvailableCurrenciesFromPlans,b0 as getBasePriceCents,a$ as getBillingIntervalAndCurrencyFromPriceId,q as getCurrencyDecimals,ad as getCurrencyFlag,n as getCurrencySymbol,cU as getDisplayCurrency,b2 as getPerSeatPriceCents,bs as getPricingVariant,aR as getQuotaDisplayValue,aQ as getQuotaDisplayWithVariant,b9 as getQuotaOverageCents,cV as getQuotaUnitLabelFromName,b1 as getSeatPricing,b4 as getStripePriceIdForInterval,cW as hasPermission,G as invalidateCreditBalance,cX as invalidateQuotaUsage,b6 as invalidateSubscription,cY as inviterNameOf,cZ as isImpersonationSignInLocked,c_ as isInvitationActionable,c$ as isInvitationError,bh as isInvitationLapsed,d0 as isReadOnlySession,d1 as isReauthRequired,d2 as isValidUsername,d3 as isZeroDecimalCurrency,d4 as mcpServerKey,bo as mergeUIConfig,d5 as minorAmountToDisplay,ax as normaliseUsername,d6 as parkPendingInvite,bf as pickDefaultRole,d7 as preflightUpload,d8 as readBBParams,d9 as readPendingInvite,bg as resendCooldownSeconds,da as resolveMaxUsers,db as resolvePermissions,ao as resolveRolePermissions,M as safeRedirect,dc as saveAuthIntent,dd as sortInvitations,de as toChecksumAddress,df as uploadFailureOf,dg as uploadPolicyOf,dh as useAllQuotaUsage,aY as useBillingPortal,aW as useCancelSubscription,di as useConnectedAgents,dj as useConnectedAgentsApi,dk as useConsumeCredits,aV as useCreateCheckoutSession,dl as useCreditBalance,v as useCreditBalanceContext,w as useCreditPackages,y as useCreditTransactions,dm as useDevices,dn as useDevicesApi,x as useExpiringCredits,dp as useFullScreenLoader,dq as useGuestUpgrade,dr as useInvoice,aK as useInvoices,ds as useMcpConnection,dt as useMyInvitations,du as useNotifications,dv as useNotificationsApi,s as usePermissions,dw as usePlanGroup,aZ as usePlanGroupVersions,dx as usePublicCreditPackages,dy as usePublicPlanGroupVersion,dz as usePublicPlans,z as usePurchaseCredits,al as usePushNotifications,b7 as useQuotaUsageContext,dA as useQuotaUsageStatus,dB as useRecordUsage,aX as useResumeSubscription,aB as useSaaSAuth,au as useSaaSOs,Q as useSaaSSettings,O as useSaaSWorkspaces,be as useSeatStatus,dC as useSessions,dD as useSessionsApi,dE as useSubscription,b8 as useSubscriptionContext,aU as useSubscriptionManagement,c as useTranslation,dF as useTrialStatus,d as useUIConfig,t as useUIVisibility,dG as useUpdateSubscription,a4 as useUpload,dH as useUploadsApi,dI as useUsageLogs,dJ as useUserAttributes,dK as useUserFeatures,bc as useWorkspaceInvitations,dL as validateInvite,aF as validateRedirectUrl,dM as workspaceIdOf,dN as workspaceNameOf,A as workspaceSettingsManager}from"./chunks/react-DTq7A_PT.mjs";import"react/jsx-runtime";import"react";import"clsx";import"tailwind-merge";import"lucide-react";import"intl-messageformat";import"@radix-ui/react-slot";import"class-variance-authority";import"@radix-ui/react-dialog";import"@radix-ui/react-icons";import"@hookform/resolvers/zod";import"react-hook-form";import"@radix-ui/react-label";import"zod";import"@radix-ui/react-avatar";import"@radix-ui/react-radio-group";import"@radix-ui/react-scroll-area";import"@radix-ui/react-separator";import"@radix-ui/react-accordion";import"@radix-ui/react-alert-dialog";import"@radix-ui/react-popover";
@@ -36,6 +36,224 @@ server answered with.
36
36
  | ---------------------- | ----------------------------------------------------------------------------------------------- |
37
37
  | `INSUFFICIENT_CREDITS` | A spend exceeds the balance. Carries `available` and `requested`, and the server answered `402` |
38
38
 
39
+ ## Two-step sign-in codes
40
+
41
+ From the authenticator-app calls on `WorkspaceApi` (`getMfaStatus`,
42
+ `startTotpEnrollment`, `confirmTotpEnrollment`, `disableTotp`,
43
+ `regenerateBackupCodes`). Lower case and hyphenated (`guest_account` aside), because they are the
44
+ server's `reason` passed through unchanged; the type is `MfaErrorCode`. Each
45
+ error also carries the HTTP `status`, and its message is the server's sentence,
46
+ written to be shown to the user.
47
+
48
+ | Code | Status | When it occurs |
49
+ | ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `mfa-off` | 403 | The app has two-step sign-in turned off, so a setup cannot start |
51
+ | `already-enrolled` | 409 | An authenticator app is already set up; remove it before adding another |
52
+ | `not-enrolling` | 409 | A confirm arrived with no setup pending; start again |
53
+ | `invalid-code` | 400 | The code did not match. On remove and new backup codes it counts toward the lockout |
54
+ | `not-enrolled` | 404 | Remove or new backup codes, with no authenticator app set up |
55
+ | `required-by-org` | 403 | The app requires two-step sign-in, so it cannot be removed |
56
+ | `locked` | 429 | Too many wrong codes; wait for the window to pass |
57
+ | `guest_account` | 409 | A guest creates an account before setting up a second step (also from `startSmsEnrollment`); the Security screen says so in its own words |
58
+
59
+ ## Password change codes
60
+
61
+ From `changePassword` on `WorkspaceApi`. Lower case with underscores, because
62
+ they are the server's `reason` passed through unchanged; the type is
63
+ `PasswordChangeErrorCode`. Each error also carries the HTTP `status`, and its
64
+ message is the server's sentence, written to be shown to the user. The
65
+ profile's `passwordSet` says ahead of time whether there is a password to
66
+ change.
67
+
68
+ | Code | Status | When it occurs |
69
+ | ------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `password_not_set` | 409 | The account has no password the person knows (social sign-up, a wallet, or a code or link). Not counted toward the lockout; set one with Forgot password? |
71
+ | `invalid` | 400 | The current password is wrong (counted toward the sign-in lockout), or the new one appeared in a known data breach |
72
+ | `locked` | 429 | Too many wrong passwords; wait up to 15 minutes for the window to pass, or reset the password, which lifts it |
73
+
74
+ ## Change email codes
75
+
76
+ From `requestEmailChange` and `confirmEmailChange` on `WorkspaceApi`. Lower
77
+ case with underscores, because they are the server's `reason` passed through
78
+ unchanged; the type is `EmailChangeErrorCode`. Each error also carries the HTTP
79
+ `status`, and its message is the server's sentence, written to be shown to the
80
+ user.
81
+
82
+ | Code | Status | When it occurs |
83
+ | ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | `email_unavailable` | 409 | The new address cannot be used in this app, whether it is taken or invalid; the sentence is the same either way |
85
+ | `linked_social_account` | 409 | Only from an older server: the account had a linked social sign-in, so its email could not change. A current server does not send it |
86
+ | `cooldown` | 429 | A code went out moments ago. Carries `retryAfter`, the seconds until another can be sent |
87
+ | `disposable_email` | 400 | The app blocks disposable addresses and the new one is from such a domain |
88
+ | `invalid_code` | 400 | The code did not match |
89
+ | `code_expired` | 410 | No code is pending, it lapsed, or it used its tries; send a new one. The session is fine, so the SDK does not sign the user out for it |
90
+
91
+ ## Connected account codes
92
+
93
+ From `getConnectedAccounts`, `linkAccount` and `unlinkAccount` on
94
+ `WorkspaceApi`. Lower case with underscores, because they are the server's
95
+ `reason` passed through unchanged; the type is `ConnectedAccountErrorCode`.
96
+ Each error also carries the HTTP `status`, and its message is the server's
97
+ sentence, written to be shown to the user. None of them signs the user out;
98
+ only a 401 does.
99
+
100
+ | Code | Status | When it occurs |
101
+ | --------------------- | ------ | ------------------------------------------------------------------------------------------------ |
102
+ | `reauth_required` | 403 | Removing one needs a sign-in or re-confirmation in the last 10 minutes; see Session refusals |
103
+ | `last_sign_in_method` | 409 | It is the only way the user can sign in (`canUnlink` is false); add another sign-in method first |
104
+ | `already_linked` | 409 | The user already has an account from that provider linked |
105
+ | `method_unavailable` | 404 | The method is not a social sign-in on this app |
106
+
107
+ A link that fails at the provider comes back on the page URL instead, as
108
+ `?error=...&reason=...` (`AccountLinkErrorReason`), which the Security screen
109
+ reads and shows once:
110
+
111
+ | Reason | When it occurs |
112
+ | ------------------- | ----------------------------------------------------------------------------------------------- |
113
+ | `link_conflict` | That provider account is already linked to another user of this app |
114
+ | `provider_error` | The provider answered with an error, or the person cancelled there |
115
+ | `provider_failed` | The provider's answer could not be completed into a link |
116
+ | `no_verified_email` | The provider account has no verified email address (GitHub's can have none) |
117
+ | `session_ended` | The session that started the link ended before it came back |
118
+ | `email_unverified` | A guest connected an account with no verified address to create theirs with; nothing was linked |
119
+ | `email_unavailable` | A guest: the provider account's address cannot be used here (it has an account, or is refused) |
120
+ | `disposable_email` | A guest: the provider account's address is a disposable one, which the app blocks |
121
+ | `signup_closed` | A guest: the app has sign-up closed, so the account cannot be created |
122
+ | `signup_restricted` | A guest: sign-up is not available for that address (the app's allowlist or blocklist) |
123
+ | `waitlist_required` | A guest: the app signs people up from its waitlist, and that address is not approved |
124
+ | `not_a_guest` | The guest became an account in another tab before the link came back |
125
+
126
+ The screen shows each in the user's language and takes `error` and `reason`
127
+ off the URL. A `reason` it does not know is left on the URL, since `error` is
128
+ a common name for an app's own parameter.
129
+
130
+ ## A call nobody answered
131
+
132
+ When the API never answers (the browser is offline, a proxy or gateway in
133
+ between fails and sends its own page, or the request times out), the error
134
+ carries no `code`. The Security screen, Confirm it's you, the account
135
+ screen's email change and username, deleting the account and the guest
136
+ upgrade say "The server could not be reached. Check your connection and try
137
+ again." in the user's language (`errors.unreachable`) instead of the
138
+ browser's words ("Failed to fetch", "Load failed") or the API layer's
139
+ fallback ("Failed to change password (502: Bad Gateway)"). A gateway's page
140
+ keeps its HTTP `status` on the error. `useGuestUpgrade().error` says
141
+ `unreachable: true` for it.
142
+
143
+ ## Phone codes
144
+
145
+ From the phone calls on `WorkspaceApi` (`addPhone`, `confirmPhone`,
146
+ `removePhone`) and the text-message factor for two-step sign-in
147
+ (`startSmsEnrollment`, `confirmSmsEnrollment`, `disableSms`). Lower case with
148
+ underscores, because they are the server's `reason` passed through unchanged;
149
+ the type is `PhoneErrorCode`. Each error also carries the HTTP `status`, and
150
+ its message is the server's sentence, written to be shown to the user. None of
151
+ them signs the user out; only a 401 does.
152
+
153
+ | Code | Status | When it occurs |
154
+ | --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ |
155
+ | `invalid_phone` | 400 | The number is not a valid phone number |
156
+ | `method_unavailable` | 400 | From `addPhone`: the app does not send codes the way asked for (by text message, when no `channel` is given) |
157
+ | `phone_unavailable` | 409 | The number is on another account |
158
+ | `cooldown` | 429 | A code went to that number moments ago. Carries `retryAfter`, the seconds until another can be sent |
159
+ | `rate_limited` | 429 | Past the number's or the address's hourly or daily limit |
160
+ | `number_refused` | 422 | The app's SMS provider will not send to that number |
161
+ | `sms_unavailable` | 503 | Codes cannot be sent right now: no provider, a provider failure, or the app's daily cap reached |
162
+ | `invalid_code` | 400 | The code did not match |
163
+ | `code_expired` | 410 | No code is pending, it lapsed, or it used its tries; send a new one. The session is fine, so the SDK does not sign out |
164
+ | `reauth_required` | 403 | Removing the phone or turning text message codes off needs a sign-in or re-confirmation in the last 10 minutes; see Session refusals |
165
+ | `last_sign_in_method` | 409 | The phone is the only way the user can sign in; add another sign-in method first |
166
+ | `mfa_sms_enrolled` | 409 | Text message codes for two-step sign-in go to this phone; turn them off before removing it |
167
+ | `phone_required` | 409 | Turning text message codes on needs a verified phone; add one first |
168
+ | `factor_not_allowed` | 403 | The app does not allow text message codes for two-step sign-in |
169
+
170
+ ## Session refusals
171
+
172
+ Any session-authenticated call may answer one of these, beside its own codes.
173
+ The type is `SessionRefusalCode`. None of them signs the user out.
174
+
175
+ | Code | Status | When it occurs |
176
+ | ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `reauth_required` | 403 | The change needs a recent sign-in. Carries `maxAgeSeconds` and, when it wants the second step too, `factors: ['mfa']`. Re-confirm with `useSaaSAuth().requireReverification({ factors })`; `isReauthRequired(error)` |
178
+ | `read_only_session` | 403 | An app owner is viewing as the user, read-only, so nothing can change. Show the sentence; `isReadOnlySession(error)` |
179
+ | `impersonation_sign_in_locked` | 403 | An app owner is signed in as the user from the console, read-only or not, and the call changes how the user signs in: email, phone, username, password, second step, passkeys, linked accounts or a guest's upgrade. Those stay the user's. Show the sentence; `isImpersonationSignInLocked(error)` |
180
+
181
+ ## Re-confirm codes
182
+
183
+ From `WorkspaceApi.startReverification` (and `requireReverification`). The
184
+ type is `ReverifyErrorCode`.
185
+
186
+ | Code | Status | When it occurs |
187
+ | ---------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
188
+ | `reverify_unavailable` | 400 | Nobody signed in to this session (an API-key exchange), an owner holds it as the user, or the user is a guest. The Security screen signs out and in instead, except for a guest and while an owner holds the session: then it says why and stays signed in |
189
+ | `invalid_return_url` | 400 | The return URL is not one of the auth client's redirect URLs. `requireReverification` then comes back through `auth.redirectUrl` |
190
+
191
+ ## Account deletion codes
192
+
193
+ From `requestAccountDeletion` and `cancelAccountDeletion` on `WorkspaceApi`.
194
+ The type is `AccountDeletionErrorCode`.
195
+
196
+ | Code | Status | When it occurs |
197
+ | ------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198
+ | `reauth_required` | 403 | Deleting needs a recent sign-in; see Session refusals |
199
+ | `owns_workspaces` | 409 | The user created a workspace that has other members or a subscription. Carries `workspaces: [{ id, name }]`. Nothing hands a workspace over: remove the other members and cancel the subscription, or delete the workspace |
200
+ | `read_only_session` | 403 | An app owner is viewing as the user |
201
+
202
+ ## Username codes
203
+
204
+ From `setUsername` and `removeUsername` on `WorkspaceApi`. The type is
205
+ `UsernameErrorCode`.
206
+
207
+ | Code | Status | When it occurs |
208
+ | ------------------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
209
+ | `invalid_username` | 400 | Not 3 to 30 of `a-z 0-9 _ . -` starting and ending with a letter or digit, two dots in a row, or a reserved word |
210
+ | `username_taken` | 409 | Another account here has it |
211
+ | `usernames_off` | 400 | The app has usernames off |
212
+ | `username_required` | 409 | Removing it, while the app requires one |
213
+
214
+ ## Guest upgrade codes
215
+
216
+ From `requestGuestUpgrade` and `confirmGuestUpgrade` on `WorkspaceApi` (and
217
+ `useGuestUpgrade`). The type is `GuestUpgradeErrorCode`. None of them signs the
218
+ guest out.
219
+
220
+ | Code | Status | When it occurs |
221
+ | ------------------- | ------ | ---------------------------------------------------------------------------- |
222
+ | `email_unavailable` | 409 | The address cannot be used here: it is taken or invalid |
223
+ | `not_a_guest` | 409 | The user is already a real account |
224
+ | `signup_restricted` | 403 | Sign-up is not available for this address (the app's allowlist or blocklist) |
225
+ | `waitlist_required` | 403 | The app signs people up from its waitlist; join it first |
226
+ | `signup_closed` | 403 | The app has sign-up closed |
227
+ | `disposable_email` | 400 | The app blocks disposable addresses |
228
+ | `breached_password` | 400 | The password appeared in a known data breach |
229
+ | `cooldown` | 429 | A code went out moments ago. Carries `retryAfter` |
230
+ | `invalid_code` | 400 | The code did not match |
231
+ | `code_expired` | 410 | No code is pending, it lapsed, or it used its tries; send a new one |
232
+
233
+ A guest also gets `guest_account` (409) from `requestEmailChange` and from
234
+ `linkWallet`: they create an account first.
235
+
236
+ ## Wallet codes
237
+
238
+ From `requestWalletNonce` and `linkWallet` on `WorkspaceApi` (and
239
+ `connectWallet`). The type is `WalletErrorCode`.
240
+
241
+ | Code | Status | When it occurs |
242
+ | -------------------- | ------ | -------------------------------------------------------------------------------------------------------- |
243
+ | `method_unavailable` | 403 | The app offers no wallet sign-in |
244
+ | `chain_not_allowed` | 400 | The app does not take that kind of wallet, or that network |
245
+ | `invalid_message` | 400 | The message was not made for this page (its domain or URI), or its time is off |
246
+ | `invalid_signature` | 400 | The signature is not the wallet's |
247
+ | `nonce_expired` | 401 | The nonce lapsed (5 minutes) or was spent; start again. A refusal, so the SDK does not sign the user out |
248
+ | `link_conflict` | 409 | The wallet is connected to another account here |
249
+ | `guest_account` | 409 | A guest creates an account before connecting a wallet |
250
+
251
+ `connectWallet` throws a `WalletFlowError` instead when the wallet stops it
252
+ before the server answers: `no_wallet` (nothing injected for that chain),
253
+ `rejected` (the person said no), `wallet_busy` (the wallet is already asking
254
+ something), `wrong_network` (on a network the app does not take, and it would
255
+ not switch), `no_account` (no address shared) and `wallet_failed`.
256
+
39
257
  ## Upload codes
40
258
 
41
259
  From `UploadsApi.upload()` and `useUpload().upload()`, with the HTTP `status`
package/docs/THEMING.md CHANGED
@@ -36,31 +36,98 @@ Load your overrides after the SDK stylesheet, against the SDK's own selector:
36
36
  --muted-foreground: 240 4% 46%; /* secondary text, hints, labels */
37
37
  --border: 240 6% 90%;
38
38
 
39
- /* Status colors — banners, badges, progress bars */
40
- --destructive: 0 84% 60%; /* errors, danger zone, overage */
41
- --success: 142 72% 29%; /* success banners, paid badges, checkmarks */
42
- --warning: 26 90% 37%; /* warnings, expiring credits, near-limit */
43
- --info: 224 76% 48%; /* info notices, selected plans, usage bars */
39
+ /* Status colors: each a fill (tints, bars, buttons) and a text tone */
40
+ --destructive: 0 72% 45%; /* Delete buttons, error tints, overage bars */
41
+ --destructive-text: 0 72% 36%; /* errors, banner text, Sign out */
42
+ --success: 142 72% 26%; /* success tints, paid badges, seat bars */
43
+ --success-text: 142 72% 19%; /* success messages, checkmarks */
44
+ --warning: 26 90% 33%; /* warning tints, near-limit bars */
45
+ --warning-text: 26 90% 24%; /* warnings, expiring credits */
46
+ --info: 224 76% 48%; /* info tints, the current plan, usage bars */
47
+ --info-text: 224 76% 38%; /* info notices, plan highlights */
44
48
  }
45
49
  ```
46
50
 
47
51
  ## Full variable reference
48
52
 
49
- | Variable | Used for |
50
- | -------------------------------------------- | ----------------------------------------------- |
51
- | `--background` / `--foreground` | Page surface and main text |
52
- | `--card` / `--card-foreground` | Card surfaces |
53
- | `--popover` / `--popover-foreground` | Dropdowns, popovers |
54
- | `--primary` / `--primary-foreground` | Buttons, active sidebar item, selected states |
55
- | `--secondary` / `--secondary-foreground` | Secondary buttons |
56
- | `--muted` / `--muted-foreground` | Subtle fills, secondary text, disabled states |
57
- | `--accent` / `--accent-foreground` | Hover highlights |
58
- | `--destructive` / `--destructive-foreground` | Errors, delete actions, overage indicators |
59
- | `--success` / `--success-foreground` | Success banners, active/paid badges |
60
- | `--warning` / `--warning-foreground` | Warnings, permission notices, near-limit states |
61
- | `--info` / `--info-foreground` | Info notices, plan highlights, usage bars |
62
- | `--border` / `--input` / `--ring` | Borders, input outlines, focus rings |
63
- | `--radius` | Border radius scale (lg/md/sm derive from it) |
53
+ | Variable | Used for |
54
+ | -------------------------------------------- | --------------------------------------------------------------------------------- |
55
+ | `--background` / `--foreground` | Page surface and main text |
56
+ | `--card` / `--card-foreground` | Card surfaces |
57
+ | `--popover` / `--popover-foreground` | Dropdowns, popovers |
58
+ | `--primary` / `--primary-foreground` | Buttons, active sidebar item, selected states |
59
+ | `--secondary` / `--secondary-foreground` | Secondary buttons |
60
+ | `--muted` / `--muted-foreground` | Subtle fills, secondary text, disabled states |
61
+ | `--accent` / `--accent-foreground` | Hover highlights |
62
+ | `--destructive` / `--destructive-foreground` | Red fills (Delete buttons, error tints, overage bars) and the text on them |
63
+ | `--destructive-text` | Red text: errors, banner and badge text, Sign out |
64
+ | `--success` / `--success-foreground` | Success fills and tints (banners, paid badges, seat bars) and the text on them |
65
+ | `--success-text` | Success text and checkmarks |
66
+ | `--warning` / `--warning-foreground` | Warning fills and tints (banners, near-limit bars) and the text on them |
67
+ | `--warning-text` | Warning text: permission notices, expiring credits |
68
+ | `--info` / `--info-foreground` | Info fills and tints (notices, the current plan, usage bars) and the text on them |
69
+ | `--info-text` | Info text: notices, plan highlights |
70
+ | `--border` / `--input` / `--ring` | Borders, input outlines, focus rings |
71
+ | `--radius` | Border radius scale (lg/md/sm derive from it) |
72
+
73
+ ## Status colours: a fill and a text tone
74
+
75
+ Each status colour (`destructive`, `success`, `warning`, `info`) does several
76
+ jobs, and one value cannot do them all. Behind white text (a Delete button) it
77
+ has to be dark enough for the white to read; as text on a tinted banner it has
78
+ to be darker still in light mode; as a progress bar or focus ring it has to
79
+ stand out from the page. In dark mode the fill behind white text and the text
80
+ on the page pull apart completely. So each status has two tokens:
81
+
82
+ | Token | Utilities | Used for |
83
+ | ----------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
84
+ | `--<status>` | `bg-<status>`, `bg-<status>/10`, `border-<status>/20`, `ring-<status>` | Fills and tints: buttons, banner and badge backgrounds, progress bars, borders |
85
+ | `--<status>-text` | `text-<status>`, `text-<status>/80` | Text and icons: messages, banner and badge text, Sign out, checkmarks |
86
+
87
+ The shipped values reach WCAG AA in every use: 4.5:1 for small text, and 3:1
88
+ for a progress bar, a focus ring or a state border against what it touches.
89
+ Light / dark:
90
+
91
+ | Use | Destructive | Success | Warning | Info |
92
+ | ----------------------- | ------------- | -------------- | -------------- | -------------- |
93
+ | Text on the page | 8.0:1 / 7.3:1 | 9.2:1 / 11.5:1 | 9.2:1 / 10.8:1 | 9.0:1 / 10.2:1 |
94
+ | Banner (10% tint) | 6.8:1 / 7.0:1 | 7.9:1 / 9.9:1 | 8.0:1 / 9.6:1 | 7.8:1 / 9.2:1 |
95
+ | Badge (15% tint) | 6.3:1 / 6.8:1 | 7.3:1 / 8.9:1 | 7.4:1 / 8.8:1 | 7.2:1 / 8.5:1 |
96
+ | Hint line (`/80`) | 4.8:1 / 4.8:1 | 4.9:1 / 6.8:1 | 4.9:1 / 6.6:1 | 4.9:1 / 6.3:1 |
97
+ | Label on the fill | 5.5:1 / 5.2:1 | 6.0:1 / 10.2:1 | 6.0:1 / 8.3:1 | 6.7:1 / 6.1:1 |
98
+ | Label, hovered | 4.9:1 / 6.1:1 | 4.9:1 / 8.3:1 | 4.9:1 / 6.8:1 | 5.4:1 / 5.0:1 |
99
+ | Bar on its track | 4.5:1 / 3.4:1 | 4.8:1 / 8.9:1 | 4.8:1 / 7.6:1 | 5.3:1 / 5.7:1 |
100
+ | Bar or ring on the page | 5.8:1 / 3.7:1 | 6.0:1 / 11.5:1 | 6.0:1 / 9.4:1 | 6.7:1 / 6.8:1 |
101
+
102
+ Destructive keeps white text on its fill in dark mode, so its dark fill is a
103
+ mid red, light enough to stand out from the page and dark enough for the white.
104
+ Success, warning and info carry dark text on a light fill in dark mode, as
105
+ before.
106
+
107
+ The SDK's test suite (`src/styles/theme-contrast.test.ts` in the repository)
108
+ measures every use of every status colour on the CSS the build produces,
109
+ light and dark, including the text on sidebar badges, hovered buttons inside
110
+ banners and the current plan column, and fails below the minimum.
111
+
112
+ ### If you set only the fill
113
+
114
+ The SDK declares every `--<status>-text` itself, so overriding only
115
+ `--success` (say) changes the fills and leaves success text on the SDK's tone.
116
+ That is on purpose. A fallback such as `var(--success-text, var(--success))`
117
+ would never apply, since the SDK's own declaration is always there, and text
118
+ that followed the fill would bring back the contrast failures this split fixes.
119
+ Instead each text tone is chosen to read on whatever fill you pick: for any
120
+ value within 30 degrees of the shipped hue (saturation 40% or more) that meets
121
+ the fill's own uses (its label at 4.5:1, hovered too, and its bars, rings and
122
+ borders at 3:1), every text use stays at 4.5:1 or better on its 5 to 25% tints,
123
+ in light and dark. The same test checks that sweep.
124
+
125
+ For text in your own hue, set `--<status>-text` as well, in your light rule and
126
+ your dark one, to a value with 4.5:1 on your `--background`, on `--muted`, and
127
+ on a 25% tint of your fill over each. Check it again if you change
128
+ `--background` or `--muted`. To go back to a single value, write
129
+ `--success-text: var(--success);` in both rules, knowing that a fill dark
130
+ enough for white text is usually too dark to read as text in dark mode.
64
131
 
65
132
  ## Dark mode
66
133
 
@@ -89,10 +156,11 @@ CSS has no media query of its own, so nothing competes with it.
89
156
 
90
157
  ## Components that work without the stylesheet
91
158
 
92
- `FullScreenLoader` and the `ErrorBoundary` fallback use inline styles so they render correctly before/without CSS loading. They still read the variables (with hard-coded fallbacks), so brand overrides apply to them too once your CSS is present.
159
+ `FullScreenLoader` and the `ErrorBoundary` fallback use inline styles so they render correctly before/without CSS loading. They still read the variables (with hard-coded fallbacks), so brand overrides apply to them too once your CSS is present. The `ErrorBoundary` fallback carries the `.saas-os-ui` class, so it gets the SDK's tokens (and yours, written against that selector) even though it renders outside every other SDK root.
93
160
 
94
161
  ## Rules for SDK contributors
95
162
 
96
163
  - Never use Tailwind palette classes (`gray-*`, `blue-*`, `red-*`, `green-*`, `amber-*`, `slate-*`, `bg-white`, `text-white`) in components — they bypass theming. Use the semantic tokens above.
97
164
  - Soft status fills are alpha tints of the token: `bg-success/10` (background), `border-success/20` (border), `text-success` (text).
165
+ - Status text is `text-<status>`, which `tailwind.config.js` points at `--<status>-text`; fills, tints, bars, rings and borders stay on `--<status>`. Never colour text with `hsl(var(--success))` (or any status fill) in an inline style or an arbitrary value, paint a status background at an alpha other than 5, 10, 15, 80 or 90%, or put a progress bar on a muted track: `src/styles/theme-contrast.test.ts` fails on each.
98
166
  - CI-check locally: `grep -rE '(bg|text|border|divide)-[a-z]+-[0-9]{2,3}|bg-white|text-white|#[0-9a-fA-F]{3,6}' src/components src/providers --include='*.tsx'` should return nothing (the pattern catches every Tailwind palette shade and raw hex).
package/docs/UI-CONFIG.md CHANGED
@@ -51,6 +51,14 @@ Fine-grained switches inside each settings screen. All default to `true`.
51
51
  | `settings.profile.country` | Country dropdown |
52
52
  | `settings.profile.currency` | Currency dropdown |
53
53
  | `settings.profile.timezone` | Timezone dropdown |
54
+ | `settings.profile.changeEmail` | Change-email flow (offered only while the server allows it for the user) |
55
+ | `settings.profile.username` | Username field (shown while usernames are on or the user has one) |
56
+ | `settings.security.password` | Change-password form (hide it for an app without password sign-in) |
57
+ | `settings.security.phone` | Phone number section (hide it for an app that sends no phone codes) |
58
+ | `settings.security.twoStep` | Two-step sign-in section (also hidden while the org has it turned off) |
59
+ | `settings.security.connectedAccounts` | Connected accounts section (also hidden while there is nothing to list) |
60
+ | `settings.security.wallets` | Connect a wallet buttons (connected wallets stay listed) |
61
+ | `settings.security.deleteAccount` | Delete account section, and the notice atop every screen while scheduled |
54
62
  | `settings.security.passkeyRename` | Rename action on passkey rows |
55
63
  | `settings.security.passkeyDelete` | Delete action on passkey rows |
56
64
  | `settings.general.nameEdit` | Workspace name field |
@@ -80,6 +88,17 @@ ui={{
80
88
  }}
81
89
  ```
82
90
 
91
+ ## Impersonation banner
92
+
93
+ While an app owner is viewing the app as the signed-in person (the profile's
94
+ `impersonation`), the provider pins `<ImpersonationBanner />` to the bottom of
95
+ the window. `impersonationBanner: false` hides it, for an app that places
96
+ `<ImpersonationBanner fixed={false} />` in its own layout instead.
97
+
98
+ ```tsx
99
+ ui={{ impersonationBanner: false }}
100
+ ```
101
+
83
102
  ## Workspace switcher
84
103
 
85
104
  Client-side switcher control, ANDed with the server settings (`showSwitcher`, `canCreateWorkspace`):
@@ -93,10 +112,10 @@ Client-side switcher control, ANDed with the server settings (`showSwitcher`, `c
93
112
 
94
113
  ## Behavior
95
114
 
96
- | Config key | Effect |
97
- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
98
- | `behavior.autoOpenPlanDialog` | `false` disables the automatic plan-picker popup when a workspace has no subscription. Explicit `selectPlan` deep links (e.g. from `PricingPage`) still open it. |
99
- | `behavior.trialEndingDays` | Global default threshold for `<WhenTrialEnding>` (default 3). A `daysThreshold` prop still wins per usage. |
115
+ | Config key | Effect |
116
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
117
+ | `behavior.autoOpenPlanDialog` | `false` disables the automatic plan-picker popup when a workspace has no subscription, and stops `createWorkspace()` opening Plan & Billing on the new workspace (which hiding `settings.sections.subscription` also does). Explicit `selectPlan` deep links (e.g. from `PricingPage`) still open it. |
118
+ | `behavior.trialEndingDays` | Global default threshold for `<WhenTrialEnding>` (default 3). A `daysThreshold` prop still wins per usage. |
100
119
 
101
120
  ## String overrides (`messages`)
102
121