@poly-x/react 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from "react";
2
- import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState } from "@poly-x/core";
2
+ import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState, TenantOption } from "@poly-x/core";
3
3
 
4
4
  //#region src/provider.d.ts
5
5
  interface PolyXProviderProps {
@@ -122,6 +122,95 @@ declare class PopupCancelledError extends Error {
122
122
  }
123
123
  declare function createLoginController(deps: LoginControllerDeps): LoginController;
124
124
  //#endregion
125
+ //#region src/warm-hop.d.ts
126
+ /**
127
+ * How long to wait for the silent attempt before giving up and showing the form.
128
+ *
129
+ * A guess, and labelled as one. The requirement it serves (FR-FALL-2) is that a slow or
130
+ * dead platform must never leave someone watching a spinner — and for that, *any* bound
131
+ * beats none. The person this protects is already signed out and gains nothing from
132
+ * waiting, so the bound is deliberately short. Override it if you have measured yours.
133
+ */
134
+ declare const WARM_HOP_TIMEOUT_MS = 5000;
135
+ type WarmHopResult = /** Signed in. The session cookie is set; `workspace` names the tenant actually entered. */{
136
+ status: "signed_in";
137
+ workspace?: string;
138
+ }
139
+ /**
140
+ * Signed in — but **not** where you asked.
141
+ *
142
+ * The platform only honours a workspace hint when the person has more than one candidate for
143
+ * that application; with exactly one it seats them there regardless and still answers
144
+ * "authorized". The session is valid and the person is somewhere they are entitled to be, so
145
+ * this is not a failure — but it is not what was requested, and being silently put to work in
146
+ * the wrong tenant is worse than being asked. Decide deliberately: accept it, or send them to
147
+ * a picker.
148
+ */
149
+ | {
150
+ status: "workspace_mismatch";
151
+ requested: string;
152
+ workspace: string;
153
+ } /** Several workspaces are available and none was requested — ask, never guess (D14). */ | {
154
+ status: "select_workspace";
155
+ workspaces: readonly TenantOption[];
156
+ } /** Recognised, but not entitled to this application or workspace (D15). */ | {
157
+ status: "no_access";
158
+ } /** Not recognised. The ordinary signed-out path — not an error. */ | {
159
+ status: "sign_in_required";
160
+ } /** The attempt could not be completed. Treated as signed-out by callers; surfaced for logs. */ | {
161
+ status: "failed";
162
+ reason: string;
163
+ };
164
+ /** Has an automatic attempt already run in this tab? */
165
+ declare function warmHopAttempted(): boolean;
166
+ /** Clear the once-per-entry marker. Exported for sign-out, so the next sign-in may try again. */
167
+ declare function resetWarmHop(): void;
168
+ interface WarmHopOptions {
169
+ /**
170
+ * Pin a workspace by tenant id, organization code or slug.
171
+ *
172
+ * The platform matches this **only within the person's own memberships**, so it can
173
+ * narrow a choice but never widen access — which is why passing it is safe even when the
174
+ * value came from something the page was holding.
175
+ */
176
+ workspace?: string;
177
+ /** Override the fall-back bound. See `WARM_HOP_TIMEOUT_MS`. */
178
+ timeoutMs?: number;
179
+ }
180
+ /**
181
+ * Run one silent attempt and report what the platform said.
182
+ *
183
+ * Never throws: every failure resolves to `failed`, because a caller in the middle of
184
+ * rendering a sign-in page has no better answer than "show the form" and an exception
185
+ * there is how a blank screen happens.
186
+ */
187
+ declare function attemptWarmHop(options?: WarmHopOptions): Promise<WarmHopResult>;
188
+ /**
189
+ * Move to another workspace (F028 / FR-SWITCH-1…6).
190
+ *
191
+ * The same mechanism as the entry attempt, aimed deliberately rather than on arrival — and
192
+ * deliberately **not** subject to the once-per-entry guard, because this one is a thing the
193
+ * person asked for.
194
+ *
195
+ * Eligibility is decided by the platform at this moment, not from any list held here, so a
196
+ * workspace revoked since the picker was drawn can never be entered (FR-SWITCH-4). Which
197
+ * refusal you get depends on what is left, verified against a live platform: still holding
198
+ * other workspaces gives `select_workspace` listing them; having lost them all gives
199
+ * `no_access`. Do not treat `no_access` as the only refusal.
200
+ *
201
+ * On `signed_in` the session cookie now names the new workspace. The caller decides what to
202
+ * do next — the SDK does not reload the page or discard anything on their behalf, because it
203
+ * cannot know what is unsaved on the screen (FR-SWITCH-7).
204
+ */
205
+ declare function switchWorkspace(workspace: string, options?: WarmHopOptions): Promise<WarmHopResult>;
206
+ /**
207
+ * The automatic entry attempt: at most once per tab, whatever the caller does (FR-HOP-2).
208
+ *
209
+ * Returns `null` when it declines to attempt — already tried this entry — so a caller can
210
+ * tell "we tried and they must sign in" from "we did not try".
211
+ */
212
+ declare function attemptWarmHopOnce(options?: WarmHopOptions): Promise<WarmHopResult | null>;
213
+ //#endregion
125
214
  //#region src/hooks.d.ts
126
215
  /** The raw session state machine value — tearing-free under concurrent React. */
127
216
  declare function useSession(): SessionState;
@@ -136,11 +225,52 @@ interface UseAuth {
136
225
  * BFF custody, where the token is httpOnly server-side by design (FR-SES-3).
137
226
  */
138
227
  getToken: () => Promise<string | null>;
228
+ /**
229
+ * Re-read the session's authorization from the server and resolve whether it actually
230
+ * changed (FR-CLIENT-3).
231
+ *
232
+ * This capability already existed on the BFF store but was not reachable from the public
233
+ * surface, so both consuming apps wrote their own refresher — and they already differ:
234
+ * one treats a module change as an authorization change, the other only a permission
235
+ * change. Pair it with `useLiveAuthz`/`startLiveAuthz`, which single-flights callers.
236
+ */
237
+ refresh: () => Promise<boolean>;
139
238
  }
140
239
  declare function useAuth(): UseAuth;
141
240
  /** Run this on your callback route to complete a sign-in (see `<AuthCallback>`). */
142
241
  declare function useAuthCallback(): () => Promise<void>;
143
242
  declare function useUser(): SessionClaims | null;
243
+ /** What `useWarmHop` reports while and after it tries. */
244
+ type WarmHopState = {
245
+ status: "idle";
246
+ } | {
247
+ status: "attempting";
248
+ } | {
249
+ status: "settled";
250
+ result: WarmHopResult;
251
+ };
252
+ /**
253
+ * Try a silent sign-in once when this component mounts (F027 / FR-HOP-2, FR-FALL-1…3).
254
+ *
255
+ * The contract that matters is D11's: **an attempt that fails must be invisible.** Someone who
256
+ * is genuinely signed out pays the whole cost of this feature and gets nothing from it, so they
257
+ * must not see a flicker, a spinner that outlives their patience, or — worst — a redirect loop.
258
+ * Hence: no navigation anywhere in the flow, a hard timeout, and a guard that survives the route
259
+ * change to the sign-in page (a guard in React state would reset exactly when it is needed).
260
+ *
261
+ * ```tsx
262
+ * const hop = useWarmHop();
263
+ * if (hop.status !== "settled" ) return null; // no half-drawn form
264
+ * if (hop.result.status === "signed_in") return <Redirect/>;
265
+ * return <SignIn />; // the ordinary path
266
+ * ```
267
+ *
268
+ * Strict Mode's double-mount does not produce two attempts: the marker is written before the
269
+ * first request goes out.
270
+ */
271
+ declare function useWarmHop(options?: WarmHopOptions & {
272
+ enabled?: boolean;
273
+ }): WarmHopState;
144
274
  //#endregion
145
275
  //#region src/context.d.ts
146
276
  interface PolyXContextValue {
@@ -239,6 +369,8 @@ interface SignInLabels {
239
369
  noAccess: string;
240
370
  passwordChangeRequired: string;
241
371
  genericError: string;
372
+ /** Reserved second-factor step (F020). Unreachable until MFA ships. */
373
+ verificationRequired?: string;
242
374
  }
243
375
  /**
244
376
  * `card` renders just the card — drop it into your own layout.
@@ -500,6 +632,70 @@ interface Viewport {
500
632
  */
501
633
  declare function resolveSide(preferred: SideOption, trigger: Rect, menu: Size, viewport: Viewport, gap?: number): Side;
502
634
  //#endregion
635
+ //#region src/components/user-profile.d.ts
636
+ /** Every user-facing string, overridable for i18n / white-labelling (FR-BRAND-4). */
637
+ interface UserProfileLabels {
638
+ trigger: string;
639
+ title: string;
640
+ description: string;
641
+ firstName: string;
642
+ lastName: string;
643
+ phoneNumber: string;
644
+ cnic: string;
645
+ gender: string;
646
+ photo: string;
647
+ changePhoto: string;
648
+ optional: string;
649
+ save: string;
650
+ saving: string;
651
+ cancel: string;
652
+ close: string;
653
+ loading: string;
654
+ loadFailed: string;
655
+ saveFailed: string;
656
+ saved: string;
657
+ signedOut: string;
658
+ genderUnset: string;
659
+ genderMale: string;
660
+ genderFemale: string;
661
+ genderOther: string;
662
+ }
663
+ interface UserProfileProps {
664
+ /** Replaces the trigger's contents. The SDK still owns the button and its dialog semantics. */
665
+ trigger?: ReactNode;
666
+ /** Render no trigger at all and control the dialog from outside (`open` / `onOpenChange`). */
667
+ open?: boolean;
668
+ defaultOpen?: boolean;
669
+ onOpenChange?: (open: boolean) => void;
670
+ /** Notified after a successful save, with the saved profile (FR-PROF-10). */
671
+ onSaved?: (user: Record<string, unknown>) => void;
672
+ /** Appended to the root element's class list. */
673
+ className?: string;
674
+ /** Appended to the trigger button's class list — the element a consumer places in their layout. */
675
+ triggerClassName?: string;
676
+ /** Appended to the dialog's class list. */
677
+ dialogClassName?: string;
678
+ labels?: Partial<UserProfileLabels>;
679
+ }
680
+ /**
681
+ * The signed-in person's own profile, as a dialog (v0.4 / FR-PROF).
682
+ *
683
+ * Every application that wanted this had to build the same four things: a form, a validation
684
+ * ruleset guessed at from error messages, a file upload, and — because the browser holds no
685
+ * credential under BFF custody — a server route to carry the call. The last of those is
686
+ * security-adjacent work that had no business being duplicated per app.
687
+ *
688
+ * The credential never comes near this component: it posts to the SDK's own route, which
689
+ * attaches the session server-side and takes the identity from the sealed session rather than
690
+ * from anything sent here. This surface cannot be aimed at another person's profile even if a
691
+ * caller tries (FR-CUSTODY-4 / D24).
692
+ *
693
+ * ```tsx
694
+ * <UserProfile onSaved={() => router.refresh()} />
695
+ * ```
696
+ */
697
+ declare function UserProfile(props: UserProfileProps): ReactNode;
698
+ //#endregion
503
699
  //#region src/components/user-button.d.ts
504
700
  /** Every user-facing string, overridable for i18n / white-labelling. */
505
701
  interface UserButtonLabels {
@@ -542,6 +738,17 @@ interface UserButtonProps {
542
738
  * hosted profile page, so the destination is the consumer's to own.
543
739
  */
544
740
  manageAccountUrl?: string;
741
+ /**
742
+ * Make "Manage account" open the SDK's own `<UserProfile/>` dialog instead of navigating
743
+ * (v0.4 / FR-PROF-8). Opt-in, so an app that already links its menu to its own account page
744
+ * keeps that behavior untouched (FR-COMPAT-2).
745
+ *
746
+ * Ignored when `manageAccountUrl` is set — a menu entry that both navigates and opens a
747
+ * dialog is a bug, so the explicit destination wins and this is a no-op.
748
+ */
749
+ showUserProfile?: boolean;
750
+ /** Passed through to the dialog `showUserProfile` opens. */
751
+ userProfileProps?: Omit<UserProfileProps, "open" | "onOpenChange">;
545
752
  /** Offer "Sign out everywhere" (ends every session for the user). Default `false`. */
546
753
  showSignOutEverywhere?: boolean;
547
754
  /** Start with the menu open. Default `false`. */
@@ -631,6 +838,23 @@ declare function UserAvatar({
631
838
  className
632
839
  }: UserAvatarProps): ReactNode;
633
840
  //#endregion
841
+ //#region src/auth-event.d.ts
842
+ /**
843
+ * Read a one-shot auth event published by the BFF (F017 / FR-CLIENT-5).
844
+ *
845
+ * Sign-in and sign-out are full-page navigations, so an app cannot simply react in the
846
+ * handler that triggered them — the page is gone. The BFF leaves a short-lived, readable
847
+ * cookie; the destination page consumes it once.
848
+ */
849
+ type AuthEventKind = "signed-in" | "signed-out";
850
+ /**
851
+ * Read the pending auth event and clear it, so it fires exactly once.
852
+ *
853
+ * Returns `null` when there is nothing pending — which is the normal case on almost every
854
+ * page load. Safe to call during render or in an effect; it is a no-op on the server.
855
+ */
856
+ declare function consumeAuthEvent(): AuthEventKind | null;
857
+ //#endregion
634
858
  //#region src/live-authz.d.ts
635
859
  /**
636
860
  * @poly-x/react/live-authz — keeps live claims current (SDK v2: FR-LIVE, FR-SIG, FR-TRIG).
@@ -699,6 +923,7 @@ declare function useAuthzChanged(callback: () => void): void;
699
923
  * F006; this entry is the state foundation (provider, hooks, browser seams).
700
924
  */
701
925
  declare const PACKAGE_NAME = "@poly-x/react";
702
- declare const version = "0.0.0";
926
+ /** The published version of this package, injected at build time. */
927
+ declare const version: string;
703
928
  //#endregion
704
- export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, startLiveAuthz, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, version };
929
+ export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, type AuthEventKind, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, UserProfile, type UserProfileLabels, type UserProfileProps, WARM_HOP_TIMEOUT_MS, type WarmHopOptions, type WarmHopResult, type WarmHopState, WebLocksLock, WindowBrowserBridge, attemptWarmHop, attemptWarmHopOnce, capturePreciseLocation, consumeAuthEvent, createLoginController, initialsFrom, resetWarmHop, resolveSide, startLiveAuthz, switchWorkspace, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, useWarmHop, version, warmHopAttempted };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState } from "@poly-x/core";
1
+ import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState, TenantOption } from "@poly-x/core";
2
2
  import { ReactNode } from "react";
3
3
 
4
4
  //#region src/provider.d.ts
@@ -122,6 +122,95 @@ declare class PopupCancelledError extends Error {
122
122
  }
123
123
  declare function createLoginController(deps: LoginControllerDeps): LoginController;
124
124
  //#endregion
125
+ //#region src/warm-hop.d.ts
126
+ /**
127
+ * How long to wait for the silent attempt before giving up and showing the form.
128
+ *
129
+ * A guess, and labelled as one. The requirement it serves (FR-FALL-2) is that a slow or
130
+ * dead platform must never leave someone watching a spinner — and for that, *any* bound
131
+ * beats none. The person this protects is already signed out and gains nothing from
132
+ * waiting, so the bound is deliberately short. Override it if you have measured yours.
133
+ */
134
+ declare const WARM_HOP_TIMEOUT_MS = 5000;
135
+ type WarmHopResult = /** Signed in. The session cookie is set; `workspace` names the tenant actually entered. */{
136
+ status: "signed_in";
137
+ workspace?: string;
138
+ }
139
+ /**
140
+ * Signed in — but **not** where you asked.
141
+ *
142
+ * The platform only honours a workspace hint when the person has more than one candidate for
143
+ * that application; with exactly one it seats them there regardless and still answers
144
+ * "authorized". The session is valid and the person is somewhere they are entitled to be, so
145
+ * this is not a failure — but it is not what was requested, and being silently put to work in
146
+ * the wrong tenant is worse than being asked. Decide deliberately: accept it, or send them to
147
+ * a picker.
148
+ */
149
+ | {
150
+ status: "workspace_mismatch";
151
+ requested: string;
152
+ workspace: string;
153
+ } /** Several workspaces are available and none was requested — ask, never guess (D14). */ | {
154
+ status: "select_workspace";
155
+ workspaces: readonly TenantOption[];
156
+ } /** Recognised, but not entitled to this application or workspace (D15). */ | {
157
+ status: "no_access";
158
+ } /** Not recognised. The ordinary signed-out path — not an error. */ | {
159
+ status: "sign_in_required";
160
+ } /** The attempt could not be completed. Treated as signed-out by callers; surfaced for logs. */ | {
161
+ status: "failed";
162
+ reason: string;
163
+ };
164
+ /** Has an automatic attempt already run in this tab? */
165
+ declare function warmHopAttempted(): boolean;
166
+ /** Clear the once-per-entry marker. Exported for sign-out, so the next sign-in may try again. */
167
+ declare function resetWarmHop(): void;
168
+ interface WarmHopOptions {
169
+ /**
170
+ * Pin a workspace by tenant id, organization code or slug.
171
+ *
172
+ * The platform matches this **only within the person's own memberships**, so it can
173
+ * narrow a choice but never widen access — which is why passing it is safe even when the
174
+ * value came from something the page was holding.
175
+ */
176
+ workspace?: string;
177
+ /** Override the fall-back bound. See `WARM_HOP_TIMEOUT_MS`. */
178
+ timeoutMs?: number;
179
+ }
180
+ /**
181
+ * Run one silent attempt and report what the platform said.
182
+ *
183
+ * Never throws: every failure resolves to `failed`, because a caller in the middle of
184
+ * rendering a sign-in page has no better answer than "show the form" and an exception
185
+ * there is how a blank screen happens.
186
+ */
187
+ declare function attemptWarmHop(options?: WarmHopOptions): Promise<WarmHopResult>;
188
+ /**
189
+ * Move to another workspace (F028 / FR-SWITCH-1…6).
190
+ *
191
+ * The same mechanism as the entry attempt, aimed deliberately rather than on arrival — and
192
+ * deliberately **not** subject to the once-per-entry guard, because this one is a thing the
193
+ * person asked for.
194
+ *
195
+ * Eligibility is decided by the platform at this moment, not from any list held here, so a
196
+ * workspace revoked since the picker was drawn can never be entered (FR-SWITCH-4). Which
197
+ * refusal you get depends on what is left, verified against a live platform: still holding
198
+ * other workspaces gives `select_workspace` listing them; having lost them all gives
199
+ * `no_access`. Do not treat `no_access` as the only refusal.
200
+ *
201
+ * On `signed_in` the session cookie now names the new workspace. The caller decides what to
202
+ * do next — the SDK does not reload the page or discard anything on their behalf, because it
203
+ * cannot know what is unsaved on the screen (FR-SWITCH-7).
204
+ */
205
+ declare function switchWorkspace(workspace: string, options?: WarmHopOptions): Promise<WarmHopResult>;
206
+ /**
207
+ * The automatic entry attempt: at most once per tab, whatever the caller does (FR-HOP-2).
208
+ *
209
+ * Returns `null` when it declines to attempt — already tried this entry — so a caller can
210
+ * tell "we tried and they must sign in" from "we did not try".
211
+ */
212
+ declare function attemptWarmHopOnce(options?: WarmHopOptions): Promise<WarmHopResult | null>;
213
+ //#endregion
125
214
  //#region src/hooks.d.ts
126
215
  /** The raw session state machine value — tearing-free under concurrent React. */
127
216
  declare function useSession(): SessionState;
@@ -136,11 +225,52 @@ interface UseAuth {
136
225
  * BFF custody, where the token is httpOnly server-side by design (FR-SES-3).
137
226
  */
138
227
  getToken: () => Promise<string | null>;
228
+ /**
229
+ * Re-read the session's authorization from the server and resolve whether it actually
230
+ * changed (FR-CLIENT-3).
231
+ *
232
+ * This capability already existed on the BFF store but was not reachable from the public
233
+ * surface, so both consuming apps wrote their own refresher — and they already differ:
234
+ * one treats a module change as an authorization change, the other only a permission
235
+ * change. Pair it with `useLiveAuthz`/`startLiveAuthz`, which single-flights callers.
236
+ */
237
+ refresh: () => Promise<boolean>;
139
238
  }
140
239
  declare function useAuth(): UseAuth;
141
240
  /** Run this on your callback route to complete a sign-in (see `<AuthCallback>`). */
142
241
  declare function useAuthCallback(): () => Promise<void>;
143
242
  declare function useUser(): SessionClaims | null;
243
+ /** What `useWarmHop` reports while and after it tries. */
244
+ type WarmHopState = {
245
+ status: "idle";
246
+ } | {
247
+ status: "attempting";
248
+ } | {
249
+ status: "settled";
250
+ result: WarmHopResult;
251
+ };
252
+ /**
253
+ * Try a silent sign-in once when this component mounts (F027 / FR-HOP-2, FR-FALL-1…3).
254
+ *
255
+ * The contract that matters is D11's: **an attempt that fails must be invisible.** Someone who
256
+ * is genuinely signed out pays the whole cost of this feature and gets nothing from it, so they
257
+ * must not see a flicker, a spinner that outlives their patience, or — worst — a redirect loop.
258
+ * Hence: no navigation anywhere in the flow, a hard timeout, and a guard that survives the route
259
+ * change to the sign-in page (a guard in React state would reset exactly when it is needed).
260
+ *
261
+ * ```tsx
262
+ * const hop = useWarmHop();
263
+ * if (hop.status !== "settled" ) return null; // no half-drawn form
264
+ * if (hop.result.status === "signed_in") return <Redirect/>;
265
+ * return <SignIn />; // the ordinary path
266
+ * ```
267
+ *
268
+ * Strict Mode's double-mount does not produce two attempts: the marker is written before the
269
+ * first request goes out.
270
+ */
271
+ declare function useWarmHop(options?: WarmHopOptions & {
272
+ enabled?: boolean;
273
+ }): WarmHopState;
144
274
  //#endregion
145
275
  //#region src/context.d.ts
146
276
  interface PolyXContextValue {
@@ -239,6 +369,8 @@ interface SignInLabels {
239
369
  noAccess: string;
240
370
  passwordChangeRequired: string;
241
371
  genericError: string;
372
+ /** Reserved second-factor step (F020). Unreachable until MFA ships. */
373
+ verificationRequired?: string;
242
374
  }
243
375
  /**
244
376
  * `card` renders just the card — drop it into your own layout.
@@ -500,6 +632,70 @@ interface Viewport {
500
632
  */
501
633
  declare function resolveSide(preferred: SideOption, trigger: Rect, menu: Size, viewport: Viewport, gap?: number): Side;
502
634
  //#endregion
635
+ //#region src/components/user-profile.d.ts
636
+ /** Every user-facing string, overridable for i18n / white-labelling (FR-BRAND-4). */
637
+ interface UserProfileLabels {
638
+ trigger: string;
639
+ title: string;
640
+ description: string;
641
+ firstName: string;
642
+ lastName: string;
643
+ phoneNumber: string;
644
+ cnic: string;
645
+ gender: string;
646
+ photo: string;
647
+ changePhoto: string;
648
+ optional: string;
649
+ save: string;
650
+ saving: string;
651
+ cancel: string;
652
+ close: string;
653
+ loading: string;
654
+ loadFailed: string;
655
+ saveFailed: string;
656
+ saved: string;
657
+ signedOut: string;
658
+ genderUnset: string;
659
+ genderMale: string;
660
+ genderFemale: string;
661
+ genderOther: string;
662
+ }
663
+ interface UserProfileProps {
664
+ /** Replaces the trigger's contents. The SDK still owns the button and its dialog semantics. */
665
+ trigger?: ReactNode;
666
+ /** Render no trigger at all and control the dialog from outside (`open` / `onOpenChange`). */
667
+ open?: boolean;
668
+ defaultOpen?: boolean;
669
+ onOpenChange?: (open: boolean) => void;
670
+ /** Notified after a successful save, with the saved profile (FR-PROF-10). */
671
+ onSaved?: (user: Record<string, unknown>) => void;
672
+ /** Appended to the root element's class list. */
673
+ className?: string;
674
+ /** Appended to the trigger button's class list — the element a consumer places in their layout. */
675
+ triggerClassName?: string;
676
+ /** Appended to the dialog's class list. */
677
+ dialogClassName?: string;
678
+ labels?: Partial<UserProfileLabels>;
679
+ }
680
+ /**
681
+ * The signed-in person's own profile, as a dialog (v0.4 / FR-PROF).
682
+ *
683
+ * Every application that wanted this had to build the same four things: a form, a validation
684
+ * ruleset guessed at from error messages, a file upload, and — because the browser holds no
685
+ * credential under BFF custody — a server route to carry the call. The last of those is
686
+ * security-adjacent work that had no business being duplicated per app.
687
+ *
688
+ * The credential never comes near this component: it posts to the SDK's own route, which
689
+ * attaches the session server-side and takes the identity from the sealed session rather than
690
+ * from anything sent here. This surface cannot be aimed at another person's profile even if a
691
+ * caller tries (FR-CUSTODY-4 / D24).
692
+ *
693
+ * ```tsx
694
+ * <UserProfile onSaved={() => router.refresh()} />
695
+ * ```
696
+ */
697
+ declare function UserProfile(props: UserProfileProps): ReactNode;
698
+ //#endregion
503
699
  //#region src/components/user-button.d.ts
504
700
  /** Every user-facing string, overridable for i18n / white-labelling. */
505
701
  interface UserButtonLabels {
@@ -542,6 +738,17 @@ interface UserButtonProps {
542
738
  * hosted profile page, so the destination is the consumer's to own.
543
739
  */
544
740
  manageAccountUrl?: string;
741
+ /**
742
+ * Make "Manage account" open the SDK's own `<UserProfile/>` dialog instead of navigating
743
+ * (v0.4 / FR-PROF-8). Opt-in, so an app that already links its menu to its own account page
744
+ * keeps that behavior untouched (FR-COMPAT-2).
745
+ *
746
+ * Ignored when `manageAccountUrl` is set — a menu entry that both navigates and opens a
747
+ * dialog is a bug, so the explicit destination wins and this is a no-op.
748
+ */
749
+ showUserProfile?: boolean;
750
+ /** Passed through to the dialog `showUserProfile` opens. */
751
+ userProfileProps?: Omit<UserProfileProps, "open" | "onOpenChange">;
545
752
  /** Offer "Sign out everywhere" (ends every session for the user). Default `false`. */
546
753
  showSignOutEverywhere?: boolean;
547
754
  /** Start with the menu open. Default `false`. */
@@ -631,6 +838,23 @@ declare function UserAvatar({
631
838
  className
632
839
  }: UserAvatarProps): ReactNode;
633
840
  //#endregion
841
+ //#region src/auth-event.d.ts
842
+ /**
843
+ * Read a one-shot auth event published by the BFF (F017 / FR-CLIENT-5).
844
+ *
845
+ * Sign-in and sign-out are full-page navigations, so an app cannot simply react in the
846
+ * handler that triggered them — the page is gone. The BFF leaves a short-lived, readable
847
+ * cookie; the destination page consumes it once.
848
+ */
849
+ type AuthEventKind = "signed-in" | "signed-out";
850
+ /**
851
+ * Read the pending auth event and clear it, so it fires exactly once.
852
+ *
853
+ * Returns `null` when there is nothing pending — which is the normal case on almost every
854
+ * page load. Safe to call during render or in an effect; it is a no-op on the server.
855
+ */
856
+ declare function consumeAuthEvent(): AuthEventKind | null;
857
+ //#endregion
634
858
  //#region src/live-authz.d.ts
635
859
  /**
636
860
  * @poly-x/react/live-authz — keeps live claims current (SDK v2: FR-LIVE, FR-SIG, FR-TRIG).
@@ -699,6 +923,7 @@ declare function useAuthzChanged(callback: () => void): void;
699
923
  * F006; this entry is the state foundation (provider, hooks, browser seams).
700
924
  */
701
925
  declare const PACKAGE_NAME = "@poly-x/react";
702
- declare const version = "0.0.0";
926
+ /** The published version of this package, injected at build time. */
927
+ declare const version: string;
703
928
  //#endregion
704
- export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, startLiveAuthz, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, version };
929
+ export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, type AuthEventKind, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, UserProfile, type UserProfileLabels, type UserProfileProps, WARM_HOP_TIMEOUT_MS, type WarmHopOptions, type WarmHopResult, type WarmHopState, WebLocksLock, WindowBrowserBridge, attemptWarmHop, attemptWarmHopOnce, capturePreciseLocation, consumeAuthEvent, createLoginController, initialsFrom, resetWarmHop, resolveSide, startLiveAuthz, switchWorkspace, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, useWarmHop, version, warmHopAttempted };