@gusnips/react 0.9.24 → 0.9.25

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 (109) hide show
  1. package/README.md +36 -36
  2. package/dist/api-client.d.ts +51 -51
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +3 -3
  5. package/dist/api-error.d.ts +11 -11
  6. package/dist/api-error.js +11 -11
  7. package/dist/auth-email.d.ts +40 -0
  8. package/dist/auth-email.d.ts.map +1 -0
  9. package/dist/auth-email.js +106 -0
  10. package/dist/auth-email.js.map +1 -0
  11. package/dist/auth-store.d.ts +23 -23
  12. package/dist/auth-store.js +13 -13
  13. package/dist/copy.d.ts +3 -3
  14. package/dist/copy.js +3 -3
  15. package/dist/deploy-recovery.d.ts +40 -40
  16. package/dist/deploy-recovery.js +40 -40
  17. package/dist/describe-error.d.ts +68 -68
  18. package/dist/describe-error.d.ts.map +1 -1
  19. package/dist/describe-error.js +5 -5
  20. package/dist/error-boundary.d.ts +12 -12
  21. package/dist/guards.d.ts +15 -15
  22. package/dist/guards.js +3 -3
  23. package/dist/hydrate.d.ts +15 -16
  24. package/dist/hydrate.d.ts.map +1 -1
  25. package/dist/hydrate.js +9 -9
  26. package/dist/hydrate.js.map +1 -1
  27. package/dist/i18n.d.ts +15 -16
  28. package/dist/i18n.d.ts.map +1 -1
  29. package/dist/i18n.js +5 -6
  30. package/dist/i18n.js.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal-path.d.ts +8 -7
  36. package/dist/internal-path.d.ts.map +1 -1
  37. package/dist/internal-path.js +8 -7
  38. package/dist/internal-path.js.map +1 -1
  39. package/dist/prerender-contract.d.ts +7 -7
  40. package/dist/prerender-contract.js +7 -7
  41. package/dist/query.d.ts +16 -17
  42. package/dist/query.d.ts.map +1 -1
  43. package/dist/query.js +2 -2
  44. package/dist/query.js.map +1 -1
  45. package/dist/sse.d.ts +19 -20
  46. package/dist/sse.d.ts.map +1 -1
  47. package/dist/sse.js +18 -19
  48. package/dist/sse.js.map +1 -1
  49. package/dist/states.d.ts +12 -12
  50. package/dist/states.d.ts.map +1 -1
  51. package/dist/supabase-session.d.ts +47 -48
  52. package/dist/supabase-session.d.ts.map +1 -1
  53. package/dist/supabase-session.js +35 -35
  54. package/dist/supabase-session.js.map +1 -1
  55. package/dist/theme.d.ts +4 -4
  56. package/dist/theme.js +4 -4
  57. package/dist/ui/class-name.d.ts +3 -4
  58. package/dist/ui/class-name.d.ts.map +1 -1
  59. package/dist/ui/class-name.js +3 -4
  60. package/dist/ui/class-name.js.map +1 -1
  61. package/dist/ui/combobox.d.ts +11 -11
  62. package/dist/ui/combobox.d.ts.map +1 -1
  63. package/dist/ui/dialog.d.ts +5 -5
  64. package/dist/ui/dialog.js +2 -2
  65. package/dist/ui/drawer.d.ts +10 -10
  66. package/dist/ui/drawer.d.ts.map +1 -1
  67. package/dist/ui/drawer.js +2 -2
  68. package/dist/ui/input.d.ts +2 -2
  69. package/dist/ui/input.js +2 -2
  70. package/dist/ui/menu.d.ts +8 -8
  71. package/dist/ui/menu.d.ts.map +1 -1
  72. package/dist/ui/menu.js +4 -4
  73. package/dist/ui/menu.js.map +1 -1
  74. package/dist/ui/select.d.ts +12 -12
  75. package/dist/ui/select.d.ts.map +1 -1
  76. package/dist/ui/select.js +3 -3
  77. package/dist/ui/tabs.d.ts +14 -15
  78. package/dist/ui/tabs.d.ts.map +1 -1
  79. package/dist/ui/tabs.js +13 -14
  80. package/dist/ui/tabs.js.map +1 -1
  81. package/package.json +1 -1
  82. package/src/api-client.ts +51 -51
  83. package/src/api-error.ts +11 -11
  84. package/src/auth-email.test.ts +145 -0
  85. package/src/auth-email.ts +112 -0
  86. package/src/auth-store.ts +23 -23
  87. package/src/copy.ts +3 -3
  88. package/src/deploy-recovery.ts +40 -40
  89. package/src/describe-error.ts +68 -68
  90. package/src/error-boundary.tsx +12 -12
  91. package/src/guards.tsx +15 -15
  92. package/src/hydrate.ts +15 -16
  93. package/src/i18n.ts +15 -16
  94. package/src/index.ts +2 -0
  95. package/src/internal-path.ts +8 -7
  96. package/src/prerender-contract.ts +7 -7
  97. package/src/query.ts +16 -17
  98. package/src/sse.ts +19 -20
  99. package/src/states.ts +12 -12
  100. package/src/supabase-session.ts +46 -47
  101. package/src/theme.ts +4 -4
  102. package/src/ui/class-name.ts +3 -4
  103. package/src/ui/combobox.tsx +11 -11
  104. package/src/ui/dialog.tsx +5 -5
  105. package/src/ui/drawer.tsx +10 -10
  106. package/src/ui/input.tsx +2 -2
  107. package/src/ui/menu.tsx +8 -8
  108. package/src/ui/select.tsx +12 -12
  109. package/src/ui/tabs.tsx +14 -15
package/src/query.ts CHANGED
@@ -3,29 +3,28 @@ import type { DefaultOptions } from "@tanstack/react-query";
3
3
  import { ApiError } from "./api-error.ts";
4
4
 
5
5
  /**
6
- * react-query defaults — the merge of three repos that each got part of this right.
6
+ * react-query defaults merged from three repos that each got part of this right.
7
7
  *
8
8
  * The default is three blind retries, which is wrong for an API that answers refusals
9
9
  * precisely: a 402, a 403 or a 451 says exactly the same thing three times, and a 429 we hammer
10
10
  * is a 429 we deserve. All three donors knew that much. What each knew alone:
11
11
  *
12
12
  * - one allowed **408** through, the only 4xx that a second attempt genuinely fixes;
13
- * - one refused to retry a 429 whose CODE says the limit is durable — a monthly quota or a
14
- * spent balance clears by buying, not by waiting, so retrying burns another request against
13
+ * - one refused to retry a 429 whose CODE says the limit is durable. A monthly quota or a spent balance clears by buying, not by waiting, so retrying burns another request against
15
14
  * the limiter and cannot succeed;
16
15
  * - one set **`refetchOnReconnect`**, which none of the others had, and which is the single
17
16
  * most useful refetch there is: coming back from a tunnel is exactly when the screen is stale.
18
17
  *
19
18
  * The second migration added the fourth rule, which no donor had: **when a 429 says how long,
20
19
  * believe it.** Its limiter answers `Retry-After: 60` on a per-minute window, and the default
21
- * backoff retries at 1s and 2s — two more requests that cannot succeed, both charged against the
22
- * same limiter, and three seconds added before the screen says anything. So a stated wait is
20
+ * backoff retries at 1s and 2s, sending two more requests that cannot succeed, both charged against
21
+ * the same limiter, and three seconds added before the screen says anything. So a stated wait is
23
22
  * waited out when it is short, and is an ANSWER when it is not.
24
23
  *
25
24
  * The fifth rule came from the server half of this fleet rather than from a donor: a refusal can
26
25
  * state that waiting will never fix it at all, by sending `details.retryAfterSecs: null`. Read as
27
- * "did not say how long" — which is what every version before this one did — that 429 is retried
28
- * against a limit no amount of time moves.
26
+ * "did not say how long", as every version before this one did, that 429 is retried against a limit
27
+ * no amount of time moves.
29
28
  *
30
29
  * This is invariant 8. The rule itself lives in `@gusnips/http/retry`, so an SDK with no React
31
30
  * in it follows the same one; what is here is the binding to react-query.
@@ -33,9 +32,9 @@ import { ApiError } from "./api-error.ts";
33
32
 
34
33
  export interface QueryDefaultsOptions {
35
34
  /**
36
- * Error codes that mean "this limit does not clear by waiting" — a spent monthly quota, a
37
- * balance that needs topping up. They usually arrive as 402 or 429; the status alone cannot
38
- * tell them apart from a burst limit, which is why the caller names them.
35
+ * Error codes for limits that do not clear by waiting, such as a spent monthly quota or a balance
36
+ * that needs topping up. They usually arrive as 402 or 429; the status alone cannot tell them
37
+ * apart from a burst limit, which is why the caller names them.
39
38
  *
40
39
  * Only needed where the server does not say so itself. One that sends
41
40
  * `details.retryAfterSecs: null` has already made the claim and `shouldRetry` reads it, which
@@ -47,10 +46,10 @@ export interface QueryDefaultsOptions {
47
46
  /**
48
47
  * Longest stated wait still worth retrying. Default 10 seconds.
49
48
  *
50
- * Only applies when the refusal named its own expiry (`Retry-After`, or `retryAfterSecs` in
51
- * the body). A burst limit that clears in two seconds is worth sitting out; a monthly ceiling
52
- * that clears in an hour is an answer, and holding a spinner for it helps nobody — show the
53
- * wait instead, which is what `humanizeWait` is for.
49
+ * Only applies when the refusal named its own expiry (`Retry-After`, or `retryAfterSecs` in the
50
+ * body). A burst limit that clears in two seconds is worth sitting out; a monthly ceiling that
51
+ * clears in an hour is an answer, and holding a spinner for it helps nobody. Show the wait
52
+ * instead, which is what `humanizeWait` is for.
54
53
  */
55
54
  maxRetryWaitSecs?: number;
56
55
  staleTime?: number;
@@ -84,8 +83,8 @@ export function shouldRetry(
84
83
 
85
84
  /**
86
85
  * How long before the next attempt: the server's stated wait when there is one, an exponential
87
- * backoff with jitter otherwise, and never less than the backoff — a `Retry-After: 0` is a
88
- * server saying "immediately", which for a client that just got refused is still too soon.
86
+ * backoff with jitter otherwise, and never less than the backoff. A `Retry-After: 0` is a server
87
+ * saying "immediately", which for a client that just got refused is still too soon.
89
88
  */
90
89
  export function retryDelayMs(attemptIndex: number, error: unknown): number {
91
90
  return delayFor(attemptIndex, answerOf(error));
@@ -130,7 +129,7 @@ export function queryDefaults({
130
129
  * When it is ready, `refreshError` says whether the last refresh failed.
131
130
  *
132
131
  * **Never gate a screen on `isError`.** It is also true when a BACKGROUND refetch fails over data
133
- * already on screen — a window regaining focus, a poll, the invalidation after a save — so
132
+ * already on screen after a window regains focus, a poll or the invalidation after a save. So
134
133
  * `isError ? <failure panel> : <data>` throws away what the reader was looking at over one blip.
135
134
  * The adopters that had noticed each wrote `isLoadingError` beside a comment saying so; the ones
136
135
  * that had not were a boundary and a great many inline ternaries. A refresh that failed is not a
package/src/sse.ts CHANGED
@@ -1,26 +1,26 @@
1
1
  /**
2
2
  * Server-sent events: the framing and the transport, kept apart.
3
3
  *
4
- * Three donors had three readers and each had half the answer. One was spec-correct about the
5
- * wire but welded to `TextDecoderStream`, so the React Native app — whose fetch has no body
6
- * reader — could not use a line of it. One was a platform-free `feed(text)` that three
7
- * transports shared, and it silently dropped data. One was a whole second transport with its
8
- * own fetch and its own 401 handling, bypassing the app's only API client.
4
+ * Three donors had three readers and each had half the answer. One was spec-correct about the wire
5
+ * but welded to `TextDecoderStream`, so the React Native app, whose fetch has no body reader, could
6
+ * not use a line of it. One was a platform-free `feed(text)` that three transports shared, and it
7
+ * silently dropped data. One was a whole second transport with its own fetch and its own 401
8
+ * handling, bypassing the app's only API client.
9
9
  *
10
10
  * So: {@link createSseParser} is the state machine and knows about no platform at all;
11
11
  * {@link readSseStream} is fifteen lines of `ReadableStream` on top of it. A caller on a
12
12
  * platform without body readers keeps its own fifteen lines and shares the machine underneath.
13
13
  * (providerkit reached this exact split from the other direction and for the same reason.)
14
14
  *
15
- * And streams ride the ordinary API client — `client.request(path, {headers: {Accept:
16
- * "text/event-stream"}, timeoutMs: null})`. Not `EventSource`, which cannot carry an
17
- * Authorization header, and not a second fetch, which would need its own copy of the refresh
18
- * logic and would drift from it.
15
+ * And streams ride the ordinary API client: `client.request(path, {headers: {Accept:
16
+ * "text/event-stream"}, timeoutMs: null})`. Not `EventSource`, which cannot carry an Authorization
17
+ * header, and not a second fetch, which would need its own copy of the refresh logic and would
18
+ * drift from it.
19
19
  */
20
20
 
21
21
  /** One frame off the wire. `data` is raw text; see {@link createSseParser} on why. */
22
22
  export interface SseFrame {
23
- /** The `event:` field, or `"message"` — which is what the spec says an unnamed frame is. */
23
+ /** The `event:` field, or `"message"` for an unnamed frame, as the spec requires. */
24
24
  event: string;
25
25
  /** Every `data:` line in the frame, joined with newlines. Never parsed here. */
26
26
  data: string;
@@ -31,23 +31,22 @@ export interface SseFrame {
31
31
  /**
32
32
  * An incremental parser. Feed it text in whatever sizes the transport delivers.
33
33
  *
34
- * Frames split across reads are held until they complete, so a payload cut mid-line is never
35
- * lost or truncated — which is the failure this shape exists to prevent, and the one that only
36
- * shows up under a slow network.
34
+ * Frames split across reads are held until they complete, so a payload cut mid-line is never lost
35
+ * or truncated. That is the failure this shape exists to prevent, and the one that only shows up
36
+ * under a slow network.
37
37
  *
38
38
  * Three rules that are easy to get wrong, and one donor got each of them wrong:
39
39
  *
40
40
  * - **`data:` repeats.** A frame may carry several `data:` lines and they are JOINED with
41
- * newlines. One donor assigned instead of appending, so a multi-line payload arrived as its
42
- * last line only — no error, no warning, just a truncated message.
41
+ * newlines. One donor assigned instead of appending, so a multi-line payload arrived as its last line only, with no error or warning, just a truncated message.
43
42
  * - **Strip ONE leading space after the colon**, not all whitespace. `.trim()` is the obvious
44
43
  * thing and it corrupts any payload whose own content begins or ends with a space.
45
44
  * - **A frame with no `data:` is not dispatched.** That is what lets a server send bare
46
45
  * `event: heartbeat` keepalives, or `:` comment lines, without waking a consumer.
47
46
  *
48
- * `data` is handed back as a raw string and deliberately NOT JSON-parsed. One donor parsed here
49
- * and its three consumers wanted three different things — one wraps non-JSON, one uses a
50
- * tolerant parser, one parses directly. Parsing in the parser picks a policy for all of them.
47
+ * `data` is handed back as a raw string and deliberately NOT JSON-parsed. One donor parsed here and
48
+ * its three consumers wanted three different things: one wraps non-JSON, one uses a tolerant
49
+ * parser, one parses directly. Parsing in the parser picks a policy for all of them.
51
50
  */
52
51
  export function createSseParser(onFrame: (frame: SseFrame) => void): {
53
52
  feed: (text: string) => void;
@@ -95,8 +94,8 @@ function parseFrame(raw: string): SseFrame | null {
95
94
  * single-flight refresh, and a 402 or a 404 arriving as an `ApiError` the page can explain
96
95
  * rather than an `onerror` event with nothing in it.
97
96
  *
98
- * Cancels the reader on the way out however it leaves — a stream abandoned without cancelling
99
- * holds the connection open until the server gives up on it.
97
+ * Cancels the reader on the way out however it leaves. A stream abandoned without cancelling holds
98
+ * the connection open until the server gives up on it.
100
99
  */
101
100
  export async function readSseStream(
102
101
  response: Response,
package/src/states.ts CHANGED
@@ -5,10 +5,10 @@ import type { ReactNode } from "react";
5
5
  * somebody remembers.
6
6
  *
7
7
  * **No component ships here, on purpose.** Seven repos have an `EmptyState.tsx` and an
8
- * `ErrorState.tsx`, and read side by side they overlap 9–37%: one draws a tinted icon badge,
9
- * one a branded illustration set, one a mascot. What they genuinely share is the PROP SHAPE —
10
- * which is the rule itself, and the only part a package can hold without deciding how eight
11
- * brands look. Same reasoning as the styled Button: share behaviour, skin per product.
8
+ * `ErrorState.tsx`, and read side by side they overlap 9–37%: one draws a tinted icon badge, one a
9
+ * branded illustration set, one a mascot. What they genuinely share is the PROP SHAPE, which is the
10
+ * rule itself, and the only part a package can hold without deciding how eight brands look. Same
11
+ * reasoning as the styled Button: share behaviour, skin per product.
12
12
  *
13
13
  * Import the type, write your own component against it, and the compiler enforces the rest.
14
14
  */
@@ -22,16 +22,16 @@ import type { ReactNode } from "react";
22
22
  * `action` is optional only because some panels fill themselves once a job upstream finishes.
23
23
  */
24
24
  export interface EmptyStateProps {
25
- /** What this panel is for. "No lookups yet" — not "No results". */
25
+ /** What this panel is for. Use "No lookups yet", not "No results". */
26
26
  title: string;
27
27
  /** What belongs here, and what it will show once it does. */
28
28
  description: ReactNode;
29
29
  /** The way forward, as a control. Usually one button. */
30
30
  action?: ReactNode;
31
31
  /**
32
- * `3` inside a panel that already sits under a page heading — the default and the common
33
- * case. `1` when this composer IS the page (a 404, a suspended account), which otherwise
34
- * ships a document with no `h1` in it at all.
32
+ * Defaults to `3` for a panel that already sits under a page heading, the common case. `1` when
33
+ * this composer IS the page (a 404, a suspended account), which otherwise ships a document with
34
+ * no `h1` in it at all.
35
35
  *
36
36
  * Only one donor had this, and it is the difference between a screen reader announcing a
37
37
  * page and announcing nothing.
@@ -49,9 +49,9 @@ export interface EmptyStateProps {
49
49
  * Something did not work.
50
50
  *
51
51
  * `problem` names what failed, `cause` says why it most likely happened, `fix` says what to do
52
- * about it, and `action` is that fix as a control. **`fix` and `action` are both required** — a
53
- * required prop is the only version of "never dead-end" that a caller in a hurry cannot skip,
54
- * and the donor that made them required is the one whose error screens all have a way out.
52
+ * about it, and `action` is that fix as a control. **`fix` and `action` are both required**. A
53
+ * required prop is the only version of "never dead-end" that a caller in a hurry cannot skip, and
54
+ * the donor that made them required is the one whose error screens all have a way out.
55
55
  *
56
56
  * Not a place for a stack trace. `cause` is what a person can act on ("the site answered too
57
57
  * slowly"), with the request id, if there is one, alongside it in `reference`.
@@ -66,7 +66,7 @@ export interface ErrorStateProps {
66
66
  cause?: ReactNode;
67
67
  /** The way forward, in words. */
68
68
  fix: ReactNode;
69
- /** The way forward, as a control — usually a retry button. */
69
+ /** The way forward as a control, usually a retry button. */
70
70
  action: ReactNode;
71
71
  /** A request id or code, for a support thread. Set in mono, never shouted. */
72
72
  reference?: ReactNode;
@@ -43,33 +43,33 @@ type _StorageFitsCreateClient = Satisfied<
43
43
  * Auth FAILED rather than answered, so nothing is known about the credential it was handed:
44
44
  * either the request never landed, or GoTrue failed on its own.
45
45
  *
46
- * This is the question every site that ends a session has to ask — a token refresh, an API
47
- * door, and a callback choosing between "try again" and "that link is spent". Without it one
48
- * bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
49
- * everybody clicking a recovery link that it has expired, which sends them to ask the service
50
- * that is down for a replacement.
46
+ * This is the question every site that ends a session has to ask: a token refresh, an API door, and
47
+ * a callback choosing between "try again" and "that link is spent". Without it one bad minute at
48
+ * GoTrue signs out everybody whose token happened to need refreshing, and tells everybody clicking
49
+ * a recovery link that it has expired, which sends them to ask the service that is down for a
50
+ * replacement.
51
51
  *
52
52
  * Both clauses carry weight, and the second is why this is a function rather than one call.
53
53
  * `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
54
- * auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
55
- * still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
56
- * whichever version happens to be installed, which is not the same as being right, and a fleet
57
- * installs several versions at once. It is also a LIST rather than a range, so it has holes at
58
- * every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
59
- * 507 or a 599 with a JSON body arrives as a plain `AuthApiError`. Pinning a recent SDK does not
60
- * make the status clause redundant.
54
+ * auth-js owns. The list has drifted: 502, 503 and 504 at 2.91; those plus the 52x family and still
55
+ * no 500 at 2.106; 500 through 530 at 2.108. Code leaning on it alone is right at whichever version
56
+ * happens to be installed, which is not the same as being right, and a fleet installs several
57
+ * versions at once. It is also a LIST rather than a range, so it has holes at every version, drift
58
+ * or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a 507 or a 599 with a JSON
59
+ * body arrives as a plain `AuthApiError`. Pinning a recent SDK does not make the status clause
60
+ * redundant.
61
61
  *
62
- * The same statuses with a body that is NOT JSON — a proxy's HTML page — arrive as an
63
- * `AuthUnknownError`, which carries no status at any status, so nothing here can tell that 507
64
- * from a malformed 400. It answers false for both, on purpose: reading an unreadable 4xx as an
65
- * outage would leave a dead session retrying forever, which is the other half of the same bug.
62
+ * The same statuses with a body that is NOT JSON, such as a proxy's HTML page, arrive as an
63
+ * `AuthUnknownError`, which carries no status at any status, so nothing here can tell that 507 from
64
+ * a malformed 400. It answers false for both, on purpose: reading an unreadable 4xx as an outage
65
+ * would leave a dead session retrying forever, which is the other half of the same bug.
66
66
  *
67
67
  * Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
68
68
  * heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
69
69
  *
70
- * Anything that is not an auth error at all answers true: a failure from outside auth-js is not
71
- * a verdict on the credential either. Nothing at all answers false — `null` is what every
72
- * supabase-js auth call returns on success, and success is not an outage.
70
+ * Anything that is not an auth error at all answers true: a failure from outside auth-js is not a
71
+ * verdict on the credential either. Nothing at all answers false. `null` is what every supabase-js
72
+ * auth call returns on success, and success is not an outage.
73
73
  */
74
74
  export function isAuthOutage(error: unknown): boolean {
75
75
  // Nothing thrown is not an outage, and this clause exists because the function is exported.
@@ -171,12 +171,12 @@ function localStorageOrMemory(): SupportedStorage {
171
171
  * 2.116.0 and 41–51 s at 2.106.2. Nobody at a shared computer waits that long, and a page that
172
172
  * navigates away in the meantime takes the removal with it.
173
173
  *
174
- * It resolves once the stored session is gone. `error` is `null` when auth ended the session on
175
- * the server too. Otherwise it is the reason auth did not — its error, a thrown value, or the
176
- * deadline — and the session may still be live on the server and on other devices. `SIGNED_OUT`
177
- * arrives once auth-js has finished with the first call: about 100 ms later from 2.108.2 on, and
178
- * up to 22 s later at 2.106.2. If your screen does not reload, clear your own store when this
179
- * resolves instead of waiting for the event.
174
+ * It resolves once the stored session is gone. `error` is `null` when auth ended the session on the
175
+ * server too. Otherwise it is the reason auth did not: its error, a thrown value or the deadline.
176
+ * The session may still be live on the server and on other devices. `SIGNED_OUT` arrives once
177
+ * auth-js has finished with the first call: about 100 ms later from 2.108.2 on, and up to 22 s
178
+ * later at 2.106.2. If your screen does not reload, clear your own store when this resolves instead
179
+ * of waiting for the event.
180
180
  */
181
181
  export async function signOutEvenOffline(
182
182
  auth: Pick<SupabaseSessionAuth, "signOut">,
@@ -254,7 +254,7 @@ export function createSupabaseSessionAdapter(
254
254
  }
255
255
 
256
256
  /**
257
- * Every link type a GoTrue email can carry — not only the ones this app's templates send today.
257
+ * Every link type a GoTrue email can carry, including ones this app's templates do not send today.
258
258
  * One donor rejected `invite` because "invites are never sent", which was true of the product and
259
259
  * false of the deployment: an operator inviting somebody from Studio sent a branded email whose
260
260
  * link then said it was invalid. A type the templates never emit costs nothing to accept: the hash
@@ -262,9 +262,9 @@ export function createSupabaseSessionAdapter(
262
262
  *
263
263
  * Closed on purpose: auth-js's own `EmailOtpType` ends in `(string & {})`, so it cannot make a
264
264
  * destination map name every case. `Record<EmailLinkType, string>` can, and that is where an app
265
- * decides that `recovery` and `invite` both continue to the new-password screen — GoTrue creates
266
- * an invited account with NO password, so landing one "inside" signs them in once and locks them
267
- * out after.
265
+ * decides that `recovery` and `invite` both continue to the new-password screen. GoTrue creates an
266
+ * invited account with NO password, so landing one "inside" signs them in once and locks them out
267
+ * after.
268
268
  */
269
269
  export type EmailLinkType =
270
270
  "signup" | "invite" | "magiclink" | "recovery" | "email_change" | "email";
@@ -290,10 +290,10 @@ export type AuthCallback =
290
290
  | { kind: "email-link"; tokenHash: string; type: EmailLinkType }
291
291
  /**
292
292
  * A session arriving in the address: a PKCE `code`, or the implicit flow's tokens. In a browser
293
- * with `detectSessionInUrl` on, the client has already taken it — wait for the session, and do
294
- * not trade it again, which would be a second exchange racing the first. Where nothing reads
295
- * the address for you (React Native), `credential` is what `exchangeCodeForSession` or
296
- * `setSession` takes.
293
+ * with `detectSessionInUrl` on, the client has already taken it. Wait for the session, and do not
294
+ * trade it again, which would be a second exchange racing the first. Where nothing reads the
295
+ * address for you (React Native), `credential` is what `exchangeCodeForSession` or `setSession`
296
+ * takes.
297
297
  */
298
298
  | {
299
299
  kind: "session";
@@ -301,10 +301,9 @@ export type AuthCallback =
301
301
  credential: { code: string } | { access_token: string; refresh_token: string };
302
302
  }
303
303
  /**
304
- * GoTrue, or the provider behind it, reported a failure in the address. `code` is GoTrue's
305
- * stable `error_code` (`otp_expired`, `bad_oauth_state`, …) and is what to choose words by;
306
- * `error_description` is left out on purpose — it is English-only and often names the wrong
307
- * cause.
304
+ * GoTrue, or the provider behind it, reported a failure in the address. `code` is GoTrue's stable
305
+ * `error_code` (`otp_expired`, `bad_oauth_state`, …) and is what to choose words by;
306
+ * `error_description` is left out because it is English-only and often names the wrong cause.
308
307
  */
309
308
  | { kind: "error"; error: string | null; code: string | null; cancelled: boolean }
310
309
  /** Nothing usable: cut short between the mail client and here, or edited by hand. */
@@ -312,19 +311,19 @@ export type AuthCallback =
312
311
 
313
312
  /**
314
313
  * Reads an auth callback address. Pure, so every edge case is testable without a router or a
315
- * client — pass `location.search` and `location.hash`, or the two halves of a deep link.
314
+ * client. Pass `location.search` and `location.hash`, or the two halves of a deep link.
316
315
  *
317
- * GoTrue writes a failure into the FRAGMENT on every path and into the query only on some — the
318
- * query too after a provider round trip, the fragment alone after an implicit-flow email link —
319
- * so a callback reading one of the two misses a failure the other carries, and the person lands
316
+ * GoTrue writes a failure into the FRAGMENT on every path and into the query only on some. After a
317
+ * provider round trip, it writes both; after an implicit-flow email link, it writes the fragment
318
+ * alone. A callback reading one of the two misses a failure the other carries, and the person lands
320
319
  * on a screen that says nothing. Both are read here.
321
320
  *
322
- * `cancelled` is the provider refusing on its own, which for Google is the person pressing Cancel
323
- * — their choice, not a failure to report. It is NOT `error === "access_denied"`: GoTrue writes
324
- * that same value for an expired email link, a banned user and a disabled signup, each with an
325
- * `error_code` beside it, while a refusal relayed from the provider arrives with none. The obvious
326
- * test reads an expired link as a change of mind. And `error` itself is absent for a status
327
- * outside GoTrue's OAuth mapping — a rate-limited link arrives with `error_code` alone.
321
+ * `cancelled` is the provider refusing on its own, which for Google is the person pressing Cancel.
322
+ * That is their choice, not a failure to report. It is NOT `error === "access_denied"`: GoTrue
323
+ * writes that same value for an expired email link, a banned user and a disabled signup, each with
324
+ * an `error_code` beside it, while a refusal relayed from the provider arrives with none. The
325
+ * obvious test reads an expired link as a change of mind. And `error` itself is absent for a status
326
+ * outside GoTrue's OAuth mapping. A rate-limited link arrives with `error_code` alone.
328
327
  */
329
328
  export function parseAuthCallback(search: string | URLSearchParams, hash = ""): AuthCallback {
330
329
  const query = new URLSearchParams(search);
package/src/theme.ts CHANGED
@@ -109,10 +109,10 @@ declare global {
109
109
  * "system" is stored as the word, not as an empty key. An empty key means the default, and the
110
110
  * default is not always "system".
111
111
  *
112
- * **It writes to the page when the theme changes, and at no other time.** A page may override
113
- * the class for its own lifetime — a public booking page that is always light — and keeps that
114
- * override until the theme really changes. Repainting on every resync would undo it a moment
115
- * later, because `pageshow` fires on every load, not only on a back-forward restore.
112
+ * **It writes to the page when the theme changes, and at no other time.** A page may override the
113
+ * class for its own lifetime, as a public booking page that is always light does, and keeps that
114
+ * override until the theme really changes. Repainting on every resync would undo it a moment later,
115
+ * because `pageshow` fires on every load, not only on a back-forward restore.
116
116
  */
117
117
  export function startTheme(options: ThemeOptions): ThemeController {
118
118
  const running = window.__frontkitTheme;
@@ -10,10 +10,9 @@ export type StateClassName<State> = string | ((state: State) => string | undefin
10
10
  /**
11
11
  * Merge a wrapper's own classes with the caller's, keeping BOTH forms working.
12
12
  *
13
- * `cn()` on its own silently DROPS the function form — clsx walks strings, arrays and plain
14
- * objects, and a function is none of the three — so a caller styling by `open` or
15
- * `transitionStatus` would get an empty class and no warning. Always returning a function
16
- * keeps one code path for both.
13
+ * `cn()` on its own silently DROPS the function form. clsx walks strings, arrays and plain objects,
14
+ * and a function is none of the three, so a caller styling by `open` or `transitionStatus` would
15
+ * get an empty class and no warning. Always returning a function keeps one code path for both.
17
16
  */
18
17
  export function mergeClassName<State>(
19
18
  base: ClassValue,
@@ -37,7 +37,7 @@ export function comboboxInputValue<T>(
37
37
  return value === null ? "" : getLabel(value);
38
38
  }
39
39
 
40
- /** A loading state needs the line that says so — there is no default, and no English. */
40
+ /** Pass text for the loading state. There is no default text or English fallback. */
41
41
  type ComboboxLoadingState =
42
42
  { loading: boolean; loadingHint: ReactNode } | { loading?: undefined; loadingHint?: undefined };
43
43
 
@@ -60,15 +60,15 @@ export type ComboboxProps<T> = Omit<
60
60
  > & {
61
61
  value: T | null;
62
62
  /**
63
- * Items for the current query. Filtering is yours: this passes `filter={null}` to Base UI,
64
- * so the list shows exactly what you return — a server search, a fuzzy match, a bounded
65
- * page. Bound it here if it needs bounding; there is no cap in the wrapper, because a cap
66
- * would quietly truncate a list a caller meant to show whole.
63
+ * Items for the current query. Filtering is yours: this passes `filter={null}` to Base UI, so the
64
+ * list shows exactly what you return, whether it is a server search, a fuzzy match or a bounded
65
+ * page. Bound it here if it needs bounding; there is no cap in the wrapper, because a cap would
66
+ * quietly truncate a list a caller meant to show whole.
67
67
  */
68
68
  search: (query: string) => readonly T[];
69
69
  /** Stable identity of an item: its React key, and how two items are compared. */
70
70
  getKey: (item: T) => string;
71
- /** The item's text — what the input shows once it is picked, and what typeahead matches. */
71
+ /** The item's text, shown in the input once it is picked and used for typeahead matches. */
72
72
  getLabel: (item: T) => string;
73
73
  renderItem?: (item: T) => ReactNode;
74
74
  /**
@@ -76,7 +76,7 @@ export type ComboboxProps<T> = Omit<
76
76
  * end, and this is the line that offers one.
77
77
  */
78
78
  emptyHint: ReactNode;
79
- /** Label for the clear button. Required — it is a button with an icon and no text. */
79
+ /** Label for the clear button. Required because the button has an icon and no text. */
80
80
  clearLabel: string;
81
81
  /** Label for the button that opens the list. Required, same reason. */
82
82
  openLabel: string;
@@ -88,10 +88,10 @@ export type ComboboxProps<T> = Omit<
88
88
  popupClassName?: StateClassName<Primitive.Popup.State>;
89
89
  inputProps?: Primitive.Input.Props;
90
90
  /**
91
- * Positioner props — `align`, `side`, `sideOffset`, `collisionPadding`, and the stacking
92
- * order. The default `z-[60]` puts the list above the overlay layer (z-50: dialog, drawer);
93
- * raise it with `positionerProps={{ className: "z-[80]" }}` if the app stacks something
94
- * higher than a dialog above it.
91
+ * Positioner props: `align`, `side`, `sideOffset`, `collisionPadding` and the stacking order. The
92
+ * default `z-[60]` puts the list above the overlay layer (z-50: dialog, drawer); raise it with
93
+ * `positionerProps={{ className: "z-[80]" }}` if the app stacks something higher than a dialog
94
+ * above it.
95
95
  */
96
96
  positionerProps?: Primitive.Positioner.Props;
97
97
  } & ComboboxLoadingState;
package/src/ui/dialog.tsx CHANGED
@@ -22,9 +22,9 @@ export type DialogContentProps = Primitive.Popup.Props & {
22
22
  */
23
23
  label?: string;
24
24
  /**
25
- * Label for the built-in close button — REQUIRED to get one, which is the whole point:
26
- * passing the label is the only way to render the button, so an unlabelled close button
27
- * cannot be written. There is no default string, because a default would be English.
25
+ * Label for the built-in close button. REQUIRED to render the button: passing the label is the
26
+ * only way to render the button, so an unlabelled close button cannot be written. There is no
27
+ * default string, because a default would be English.
28
28
  */
29
29
  closeLabel?: string;
30
30
  backdropClassName?: StateClassName<Primitive.Backdrop.State>;
@@ -39,8 +39,8 @@ export type DialogContentProps = Primitive.Popup.Props & {
39
39
  * page behind it) and is capped to the viewport, so a tall dialog is never taller than the
40
40
  * screen it opens on.
41
41
  *
42
- * Everything else — padding, radius, shadow, type — is the product's. What is here is what
43
- * makes the primitive work.
42
+ * Padding, radius, shadow and type all belong to the product. What is here is what makes the
43
+ * primitive work.
44
44
  */
45
45
  export function DialogContent({
46
46
  className,
package/src/ui/drawer.tsx CHANGED
@@ -38,9 +38,9 @@ const popupSide: Record<DrawerSide, string> = {
38
38
  /**
39
39
  * Where the portal mounts, and whether the drawer covers the page or a box inside it.
40
40
  *
41
- * A scoped drawer positions itself `absolute` against a container instead of `fixed` against
42
- * the viewport — so it MUST be given that container, or it lands in `<body>` and positions
43
- * against a box that is not there. The two travel together in the type.
41
+ * A scoped drawer positions itself `absolute` against a container instead of `fixed` against the
42
+ * viewport, so it MUST be given that container, or it lands in `<body>` and positions against a box
43
+ * that is not there. The two travel together in the type.
44
44
  *
45
45
  * The container also needs a positioning context of its own (`relative`): `absolute` walks up
46
46
  * to the nearest positioned ancestor, so a static container hands the drawer the whole page
@@ -56,20 +56,20 @@ export type DrawerProps<Payload = unknown> = Omit<
56
56
  > &
57
57
  DrawerPortalTarget & {
58
58
  /**
59
- * Which edge it comes from. This also sets `swipeDirection` on the root — the two must
60
- * agree, and a mismatch is silent: the drawer looks right and dismisses on a swipe
61
- * towards the screen it is attached to.
59
+ * Which edge it comes from. This also sets `swipeDirection` on the root. The two must agree,
60
+ * and a mismatch is silent: the drawer looks right and dismisses on a swipe towards the screen
61
+ * it is attached to.
62
62
  */
63
63
  side?: DrawerSide;
64
64
  /** Accessible name, for a drawer with no visible `DrawerTitle`. */
65
65
  label?: string;
66
66
  children?: ReactNode;
67
- /** Classes for the popup — the panel itself. */
67
+ /** Classes for the popup panel. */
68
68
  className?: StateClassName<Primitive.Popup.State>;
69
69
  backdropClassName?: StateClassName<Primitive.Backdrop.State>;
70
70
  viewportClassName?: StateClassName<Primitive.Viewport.State>;
71
71
  contentClassName?: StateClassName<Primitive.Content.State>;
72
- /** Popup-level props the wrapper does not own — `initialFocus`, `finalFocus`, `data-*`. */
72
+ /** Popup-level props the wrapper does not own, such as `initialFocus`, `finalFocus` and `data-*`. */
73
73
  popupProps?: Primitive.Popup.Props;
74
74
  };
75
75
 
@@ -78,8 +78,8 @@ export type DrawerProps<Payload = unknown> = Omit<
78
78
  * composed once here because the parts have to be arranged in exactly this order to work at
79
79
  * all, and because two of the four rules below cost someone a bug that no test would catch.
80
80
  *
81
- * Everything the root takes — `open`, `onOpenChange`, `modal`, `snapPoints`, `actionsRef`,
82
- * `data-*` — passes straight through.
81
+ * Every root prop passes straight through, including `open`, `onOpenChange`, `modal`, `snapPoints`,
82
+ * `actionsRef` and `data-*`.
83
83
  */
84
84
  export function Drawer<Payload = unknown>({
85
85
  side = "right",
package/src/ui/input.tsx CHANGED
@@ -12,8 +12,8 @@ const TEXTAREA_MAX_HEIGHT = 160;
12
12
  /**
13
13
  * The inline height an autosizing textarea should carry, or `""` for "leave it to CSS".
14
14
  *
15
- * ponytail: this whole observer is here only until `field-sizing: content` is baseline —
16
- * one CSS line does the same job. Ceiling: it does not exist in every browser we support yet.
15
+ * ponytail: this whole observer is here only until `field-sizing: content` is baseline. One CSS
16
+ * line does the same job. Ceiling: it does not exist in every browser we support yet.
17
17
  */
18
18
  export function textareaHeight(clientWidth: number, scrollHeight: number): string {
19
19
  if (clientWidth < TEXTAREA_MIN_AUTO_WIDTH) return "";
package/src/ui/menu.tsx CHANGED
@@ -37,18 +37,18 @@ export type MenuContentProps = Primitive.Popup.Props & {
37
37
  /** Where the portal mounts. Defaults to `<body>`. */
38
38
  container?: Primitive.Portal.Props["container"];
39
39
  /**
40
- * Positioner props — `side`, `align`, `sideOffset`, `collisionPadding`, and the stacking
41
- * order. The default `z-[60]` puts the menu above the overlay layer (z-50: dialog, drawer)
42
- * so a menu opened inside a dialog is not painted behind it; raise it with
43
- * `positionerProps={{ className: "z-[80]" }}` if the app stacks something higher.
40
+ * Positioner props: `side`, `align`, `sideOffset`, `collisionPadding` and the stacking order. The
41
+ * default `z-[60]` puts the menu above the overlay layer (z-50: dialog, drawer) so a menu opened
42
+ * inside a dialog is not painted behind it; raise it with `positionerProps={{ className: "z-[80]"
43
+ * }}` if the app stacks something higher.
44
44
  */
45
45
  positionerProps?: Primitive.Positioner.Props;
46
46
  };
47
47
 
48
48
  /**
49
- * A dropdown of decisions — the account menu, a row's actions, a picker. Portal, positioner
50
- * and popup composed once, for the same reason the dialog's are: the arrangement is not
51
- * optional and getting it wrong throws a number.
49
+ * A dropdown for choices, such as the account menu, a row's actions or a picker. Portal, positioner
50
+ * and popup composed once, for the same reason the dialog's are: the arrangement is not optional
51
+ * and getting it wrong throws a number.
52
52
  *
53
53
  * Roving focus, typeahead, escape, outside-dismiss and focus return all come from Base UI.
54
54
  * Nothing here re-implements any of them.
@@ -146,7 +146,7 @@ export function MenuGroup({ label, children, ...rest }: Primitive.Group.Props &
146
146
  );
147
147
  }
148
148
 
149
- /** A set of choices — theme, locale, one axis of a filter. Same caption rule as `MenuGroup`. */
149
+ /** A set of choices for a theme, locale or one axis of a filter. Same caption rule as `MenuGroup`. */
150
150
  export function MenuRadioGroup({
151
151
  label,
152
152
  children,