tempest-react-sdk 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +12 -5
  2. package/dist/app/AppProviders.cjs +1 -1
  3. package/dist/app/AppProviders.js +6 -6
  4. package/dist/audio/sfx-pool.cjs +2 -0
  5. package/dist/audio/sfx-pool.cjs.map +1 -0
  6. package/dist/audio/sfx-pool.js +62 -0
  7. package/dist/audio/sfx-pool.js.map +1 -0
  8. package/dist/audio/use-sfx-pool.cjs +2 -0
  9. package/dist/audio/use-sfx-pool.cjs.map +1 -0
  10. package/dist/audio/use-sfx-pool.js +18 -0
  11. package/dist/audio/use-sfx-pool.js.map +1 -0
  12. package/dist/auth/create-tempest-auth.cjs +1 -1
  13. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  14. package/dist/auth/create-tempest-auth.js +47 -44
  15. package/dist/auth/create-tempest-auth.js.map +1 -1
  16. package/dist/auth/lazy-with-retry.cjs +1 -1
  17. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  18. package/dist/auth/lazy-with-retry.js +8 -1
  19. package/dist/auth/lazy-with-retry.js.map +1 -1
  20. package/dist/components/AppBar/AppBar.cjs +1 -1
  21. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  22. package/dist/components/AppBar/AppBar.js +27 -25
  23. package/dist/components/AppBar/AppBar.js.map +1 -1
  24. package/dist/components/AppBar/use-sticky-body-warning.cjs +2 -0
  25. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -0
  26. package/dist/components/AppBar/use-sticky-body-warning.js +16 -0
  27. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -0
  28. package/dist/components/BarList/BarList.cjs +2 -0
  29. package/dist/components/BarList/BarList.cjs.map +1 -0
  30. package/dist/components/BarList/BarList.js +42 -0
  31. package/dist/components/BarList/BarList.js.map +1 -0
  32. package/dist/components/BarList/BarList.module.cjs +2 -0
  33. package/dist/components/BarList/BarList.module.cjs.map +1 -0
  34. package/dist/components/BarList/BarList.module.js +15 -0
  35. package/dist/components/BarList/BarList.module.js.map +1 -0
  36. package/dist/components/BarList/bar-list-model.cjs +2 -0
  37. package/dist/components/BarList/bar-list-model.cjs.map +1 -0
  38. package/dist/components/BarList/bar-list-model.js +18 -0
  39. package/dist/components/BarList/bar-list-model.js.map +1 -0
  40. package/dist/components/DataTable/DataTable.cjs +1 -1
  41. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  42. package/dist/components/DataTable/DataTable.js +141 -108
  43. package/dist/components/DataTable/DataTable.js.map +1 -1
  44. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  45. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  46. package/dist/components/DataTable/DataTable.module.js +13 -10
  47. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  48. package/dist/components/DataTable/LoadingRows.cjs +2 -0
  49. package/dist/components/DataTable/LoadingRows.cjs.map +1 -0
  50. package/dist/components/DataTable/LoadingRows.js +18 -0
  51. package/dist/components/DataTable/LoadingRows.js.map +1 -0
  52. package/dist/components/DataTable/use-dev-warnings.cjs +2 -0
  53. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -0
  54. package/dist/components/DataTable/use-dev-warnings.js +17 -0
  55. package/dist/components/DataTable/use-dev-warnings.js.map +1 -0
  56. package/dist/components/FilterBar/filter-apply.cjs +2 -0
  57. package/dist/components/FilterBar/filter-apply.cjs.map +1 -0
  58. package/dist/components/FilterBar/filter-apply.js +76 -0
  59. package/dist/components/FilterBar/filter-apply.js.map +1 -0
  60. package/dist/components/FilterBar/filter-query.cjs +2 -0
  61. package/dist/components/FilterBar/filter-query.cjs.map +1 -0
  62. package/dist/components/FilterBar/filter-query.js +38 -0
  63. package/dist/components/FilterBar/filter-query.js.map +1 -0
  64. package/dist/hooks/use-countdown.cjs +2 -0
  65. package/dist/hooks/use-countdown.cjs.map +1 -0
  66. package/dist/hooks/use-countdown.js +18 -0
  67. package/dist/hooks/use-countdown.js.map +1 -0
  68. package/dist/hooks/use-latest-ref.cjs +2 -0
  69. package/dist/hooks/use-latest-ref.cjs.map +1 -0
  70. package/dist/hooks/use-latest-ref.js +10 -0
  71. package/dist/hooks/use-latest-ref.js.map +1 -0
  72. package/dist/hooks/use-typewriter.cjs +2 -0
  73. package/dist/hooks/use-typewriter.cjs.map +1 -0
  74. package/dist/hooks/use-typewriter.js +28 -0
  75. package/dist/hooks/use-typewriter.js.map +1 -0
  76. package/dist/http/api-client.cjs +1 -1
  77. package/dist/http/api-client.cjs.map +1 -1
  78. package/dist/http/api-client.js +42 -43
  79. package/dist/http/api-client.js.map +1 -1
  80. package/dist/http/build-url.cjs +2 -0
  81. package/dist/http/build-url.cjs.map +1 -0
  82. package/dist/http/build-url.js +26 -0
  83. package/dist/http/build-url.js.map +1 -0
  84. package/dist/http/describe-api-error.cjs +2 -0
  85. package/dist/http/describe-api-error.cjs.map +1 -0
  86. package/dist/http/describe-api-error.js +22 -0
  87. package/dist/http/describe-api-error.js.map +1 -0
  88. package/dist/http/use-describe-api-error.cjs +2 -0
  89. package/dist/http/use-describe-api-error.cjs.map +1 -0
  90. package/dist/http/use-describe-api-error.js +15 -0
  91. package/dist/http/use-describe-api-error.js.map +1 -0
  92. package/dist/i18n/I18nProvider.cjs +1 -1
  93. package/dist/i18n/I18nProvider.cjs.map +1 -1
  94. package/dist/i18n/I18nProvider.js +4 -1
  95. package/dist/i18n/I18nProvider.js.map +1 -1
  96. package/dist/query/QueryProvider.cjs +1 -1
  97. package/dist/query/QueryProvider.cjs.map +1 -1
  98. package/dist/query/QueryProvider.js +13 -12
  99. package/dist/query/QueryProvider.js.map +1 -1
  100. package/dist/query/retry-policy.cjs +2 -0
  101. package/dist/query/retry-policy.cjs.map +1 -0
  102. package/dist/query/retry-policy.js +10 -0
  103. package/dist/query/retry-policy.js.map +1 -0
  104. package/dist/styles.css +1 -1
  105. package/dist/tempest-react-sdk.cjs +1 -1
  106. package/dist/tempest-react-sdk.d.ts +867 -10
  107. package/dist/tempest-react-sdk.js +276 -261
  108. package/dist/utils/compressed-storage.cjs +2 -0
  109. package/dist/utils/compressed-storage.cjs.map +1 -0
  110. package/dist/utils/compressed-storage.js +52 -0
  111. package/dist/utils/compressed-storage.js.map +1 -0
  112. package/dist/utils/csv.cjs +4 -0
  113. package/dist/utils/csv.cjs.map +1 -0
  114. package/dist/utils/csv.js +25 -0
  115. package/dist/utils/csv.js.map +1 -0
  116. package/dist/utils/format.cjs +1 -1
  117. package/dist/utils/format.cjs.map +1 -1
  118. package/dist/utils/format.js +11 -4
  119. package/dist/utils/format.js.map +1 -1
  120. package/dist/utils/numbers.cjs +1 -1
  121. package/dist/utils/numbers.cjs.map +1 -1
  122. package/dist/utils/numbers.js +5 -2
  123. package/dist/utils/numbers.js.map +1 -1
  124. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  125. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  126. package/dist/vite/tempest-pwa-dev-sw.js +10 -5
  127. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  128. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  129. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  130. package/dist/vite/tempest-pwa-manifest.js +17 -13
  131. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  132. package/dist/vite.d.ts +19 -2
  133. package/package.json +1 -1
@@ -543,6 +543,16 @@ declare type AnyEventTarget = EventTarget | {
543
543
  current: EventTarget | null;
544
544
  } | null | undefined;
545
545
 
546
+ /**
547
+ * Translation key the {@link useDescribeApiError} hook looks up.
548
+ *
549
+ * A catalog that does not define it falls back to
550
+ * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the
551
+ * lookup misses and printing `tempest.error.offline` at the user would be worse
552
+ * than printing pt-BR at them.
553
+ */
554
+ export declare const API_ERROR_OFFLINE_KEY = "tempest.error.offline";
555
+
546
556
  export declare interface ApiClient {
547
557
  request<T>(path: string, options?: RequestOptions): Promise<T>;
548
558
  get<T>(path: string, options?: RequestOptions): Promise<T>;
@@ -554,8 +564,28 @@ export declare interface ApiClient {
554
564
  }
555
565
 
556
566
  export declare interface ApiClientConfig {
557
- /** Base URL for every request. Required. */
567
+ /**
568
+ * Base URL for every request. Required.
569
+ *
570
+ * May carry a path (`https://api.example.com/api`) — it is kept, and a
571
+ * request for `"/auth/login"` lands on `/api/auth/login`. May also be
572
+ * relative (`"/api"`), which resolves against the current origin and is the
573
+ * shape to use behind a dev-server or reverse proxy.
574
+ */
558
575
  baseURL: string;
576
+ /**
577
+ * Path segment every request is nested under, such as `"/api"` — the
578
+ * `root_path` a Tempest FastAPI service is usually mounted on.
579
+ *
580
+ * The alternative to writing it into `baseURL`, and the better one when the
581
+ * base comes from an environment variable that other things also use (an
582
+ * SSE endpoint, a media host): the variable stays the bare origin and only
583
+ * the client carries the prefix.
584
+ *
585
+ * Applied at most once — a path that already opens with the prefix is left
586
+ * alone, so call sites can migrate one at a time.
587
+ */
588
+ prefix?: string;
559
589
  /** Returns the current bearer token (or null/undefined). Called per request. */
560
590
  getToken?: () => string | null | undefined;
561
591
  /**
@@ -627,6 +657,12 @@ export declare interface ApiError {
627
657
  body?: unknown;
628
658
  }
629
659
 
660
+ /** The two fixed sentences {@link describeApiError} may need. */
661
+ export declare interface ApiErrorStrings {
662
+ /** Shown when the request never reached the server. */
663
+ offline: string;
664
+ }
665
+
630
666
  /**
631
667
  * Mobile-first top app bar for PWAs — leading (back / brand) + title +
632
668
  * trailing actions, sticky with safe-area padding out of the box.
@@ -675,7 +711,14 @@ export declare interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, "
675
711
  actions?: ReactNode;
676
712
  /** Center the title (three-column grid). Default `false` (left-aligned). */
677
713
  centered?: boolean;
678
- /** Stick to the top of the scroll container. Default `true`. */
714
+ /**
715
+ * Stick to the top of the scroll container. Default `true`.
716
+ *
717
+ * The page has to leave the body out of the scrolling: `body { overflow-x:
718
+ * hidden }` makes the body a scroll container and the bar scrolls away with
719
+ * the content. Use `overflow-x: clip` on `html` and `body` instead — in
720
+ * development the bar says so in the console when it detects it.
721
+ */
679
722
  sticky?: boolean;
680
723
  /** Visual tone. Default `"surface"`. */
681
724
  tone?: AppBarTone;
@@ -687,6 +730,37 @@ export declare interface AppBarProps extends Omit<HTMLAttributes<HTMLElement>, "
687
730
 
688
731
  export declare type AppBarTone = "surface" | "primary" | "transparent";
689
732
 
733
+ /**
734
+ * Run a filter set over an in-memory list.
735
+ *
736
+ * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies
737
+ * them. Filters combine with `AND`, matching the flat model the bar builds, and
738
+ * an incomplete filter is skipped rather than treated as a match of nothing — a
739
+ * half-filled form should not empty the table underneath it.
740
+ *
741
+ * Comparison follows the row's type, not the filter's: numbers compare
742
+ * numerically, dates compare by day, and everything else compares as text with
743
+ * `numeric: true` so `"item 2"` lands before `"item 10"`.
744
+ *
745
+ * A few behaviours differ from the SQL the server-side twin produces, and the
746
+ * difference is deliberate rather than accidental:
747
+ *
748
+ * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`
749
+ * for a `NULL` column and the row drops out. Here "is not paid" shows the rows
750
+ * with no status at all, which is what the chip claims.
751
+ * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server
752
+ * only matches `NULL`. A column that stores `""` instead of `NULL` is where
753
+ * the two disagree.
754
+ *
755
+ * @example
756
+ * const visible = applyFilters(orders, filters);
757
+ *
758
+ * @param items - The full list.
759
+ * @param filters - Applied filters; incomplete ones are ignored.
760
+ * @returns A new array with the rows that satisfy every complete filter.
761
+ */
762
+ export declare function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[];
763
+
690
764
  /**
691
765
  * Apply a {@link KanbanMove} to a column list, returning new arrays.
692
766
  *
@@ -1359,6 +1433,84 @@ export declare interface BarcodeScanResult {
1359
1433
  cornerPoints: readonly BarcodePoint[];
1360
1434
  }
1361
1435
 
1436
+ /**
1437
+ * A ranked distribution: label, proportional bar, value, optional share.
1438
+ *
1439
+ * The most common chart on an admin panel, and the one the SDK kept sending
1440
+ * people to write by hand — `Progress` is a single bar and `Sparkline` is a time
1441
+ * series, so a "users per plan" block ended up reimplemented per screen, each
1442
+ * with its own CSS and its own `.sort()`.
1443
+ *
1444
+ * It is a **list**, not a picture: `<ul>` / `<li>` with the value written as
1445
+ * text, the bar `aria-hidden` behind it. A screen reader reads "Free, 128, 62%"
1446
+ * because that text is there, not because an `aria-label` restates a drawing.
1447
+ * Which is also why the label never sits on top of the bar — text over a tinted
1448
+ * fill has to be re-verified against that fill, and the SDK has been caught by
1449
+ * that twice.
1450
+ *
1451
+ * Bar width is relative to the **largest** row, so the biggest bar fills the
1452
+ * track; the percentage is the row's share of the **total**. They are different
1453
+ * numbers on purpose: a width scaled by the total leaves every bar short in a
1454
+ * long list, which is when the chart is needed most.
1455
+ *
1456
+ * @example
1457
+ * <BarList
1458
+ * items={[{ label: "Free", value: 128 }, { label: "Pro", value: 32 }]}
1459
+ * valueFormatter={(n) => `${n} ativos`}
1460
+ * showPercentage
1461
+ * max={5}
1462
+ * otherLabel="Outros"
1463
+ * />
1464
+ *
1465
+ * @param props - Items plus presentation.
1466
+ * @returns The list.
1467
+ */
1468
+ export declare function BarList({ items, valueFormatter, showPercentage, sort, max, otherLabel, className, ...rest }: BarListProps): JSX.Element;
1469
+
1470
+ /** One entry of a {@link BarList}. */
1471
+ export declare interface BarListItem {
1472
+ /** What the row is called. */
1473
+ label: string;
1474
+ /** The measured amount. */
1475
+ value: number;
1476
+ /** Override the bar colour. Defaults to the chart series token for its position. */
1477
+ color?: string;
1478
+ }
1479
+
1480
+ export declare interface BarListProps extends Omit<HTMLAttributes<HTMLUListElement>, "children"> {
1481
+ /** The rows. Entries whose value is not finite are dropped. */
1482
+ items: readonly BarListItem[];
1483
+ /** Render the number. Defaults to the value as-is. */
1484
+ valueFormatter?: (value: number) => string;
1485
+ /** Show each row's share of the total next to its value. Default `false`. */
1486
+ showPercentage?: boolean;
1487
+ /** Ordering. Default `"desc"`, which is what a ranking means. */
1488
+ sort?: BarListSort;
1489
+ /** Keep at most this many rows. */
1490
+ max?: number;
1491
+ /**
1492
+ * Label for one aggregated row holding everything `max` cut off.
1493
+ *
1494
+ * Without it, `max` simply truncates. The aggregate row only appears when more
1495
+ * than one row was cut — collapsing a single row into "others" hides its name
1496
+ * for nothing.
1497
+ */
1498
+ otherLabel?: string;
1499
+ }
1500
+
1501
+ /** One row, with everything the component needs to draw it. */
1502
+ export declare interface BarListRow extends BarListItem {
1503
+ /** Share of the total, 0–100. */
1504
+ percentage: number;
1505
+ /** Bar width relative to the largest row, 0–100. */
1506
+ width: number;
1507
+ /** Position in the original palette cycle. */
1508
+ index: number;
1509
+ }
1510
+
1511
+ /** How the list orders itself before drawing. */
1512
+ export declare type BarListSort = "desc" | "asc" | "none";
1513
+
1362
1514
  /**
1363
1515
  * Decode a base64url string into bytes.
1364
1516
  *
@@ -1552,6 +1704,79 @@ export declare const BREAKPOINTS: Record<Breakpoint, number>;
1552
1704
 
1553
1705
  export { BrowserRouter }
1554
1706
 
1707
+ /**
1708
+ * Join a base URL, an optional prefix and a request path into an absolute URL.
1709
+ *
1710
+ * `new URL(path, base)` on its own is wrong for an API client. It follows the
1711
+ * URL spec, where a path starting with `/` is absolute against the *origin* and
1712
+ * therefore discards whatever path the base carried: a client on
1713
+ * `https://api.example.com/api` asked for `/auth/login` reaches
1714
+ * `https://api.example.com/auth/login`, and every request 404s with nothing in
1715
+ * the config that looks wrong. This function resolves the path against the base
1716
+ * *path* instead, so the leading slash is a matter of taste rather than a
1717
+ * silent 404, and `baseURL` + `prefix` are interchangeable ways to say the same
1718
+ * thing.
1719
+ *
1720
+ * The prefix is applied at most once: a path that already opens with it — say
1721
+ * `"/api/auth/login"` under a `"/api"` prefix — is left alone, so a codebase
1722
+ * migrating to `prefix` can move its call sites one at a time. The check is per
1723
+ * segment, so `/api-keys` is not mistaken for an already-prefixed path.
1724
+ *
1725
+ * A path that is itself an absolute URL wins over all of this, which is how a
1726
+ * client reaches a second host (a signed upload endpoint, a CDN) without a
1727
+ * second client.
1728
+ *
1729
+ * @example
1730
+ * buildApiUrl("https://api.example.com", "/auth/login", { prefix: "/api" });
1731
+ * // "https://api.example.com/api/auth/login"
1732
+ *
1733
+ * buildApiUrl("https://api.example.com/api", "auth/login");
1734
+ * // "https://api.example.com/api/auth/login"
1735
+ *
1736
+ * @param baseURL - Absolute base URL, or a path relative to the current origin.
1737
+ * @param path - The request path, or an absolute URL to use as-is.
1738
+ * @param options - Optional prefix and query params.
1739
+ * @returns The absolute URL to fetch.
1740
+ * @throws When `baseURL` is relative and there is no `location` to resolve it.
1741
+ */
1742
+ export declare function buildApiUrl(baseURL: string, path: string, options?: BuildApiUrlOptions): string;
1743
+
1744
+ /** Options accepted by {@link buildApiUrl}. */
1745
+ export declare interface BuildApiUrlOptions {
1746
+ /**
1747
+ * Path segment every request is nested under, such as `"/api"`. Joined
1748
+ * after the path the `baseURL` already carries.
1749
+ */
1750
+ prefix?: string;
1751
+ /** Query params to append. `undefined` and `null` values are skipped. */
1752
+ params?: Record<string, string | number | boolean | undefined | null>;
1753
+ }
1754
+
1755
+ /**
1756
+ * Order, truncate and measure the rows.
1757
+ *
1758
+ * Two different numbers come out of this, and conflating them is the classic bug
1759
+ * in a hand-written bar list:
1760
+ *
1761
+ * - `percentage` is the row's share of the **total**, which is what the label
1762
+ * claims when it reads "32%".
1763
+ * - `width` is relative to the **largest** row, so the biggest bar fills the
1764
+ * track. Scaling width by the total instead leaves every bar short in a list of
1765
+ * many small values, and the chart stops being readable exactly when it has the
1766
+ * most rows.
1767
+ *
1768
+ * The total counts positive values only. A negative amount draws no bar (a bar of
1769
+ * negative width does not exist) and reports 0%, but its number is still shown —
1770
+ * hiding the row would be worse than showing an odd one.
1771
+ *
1772
+ * @param items - The rows as given.
1773
+ * @param sort - Ordering to apply. `"none"` keeps the caller's order.
1774
+ * @param max - Keep at most this many rows.
1775
+ * @param otherLabel - Aggregate what `max` cut into one row with this label.
1776
+ * @returns The rows to draw, in order.
1777
+ */
1778
+ export declare function buildBarListRows(items: readonly BarListItem[], sort: BarListSort, max: number | undefined, otherLabel: string | undefined): BarListRow[];
1779
+
1555
1780
  /**
1556
1781
  * Builds an `intent://` URL that re-opens the current page inside Chrome on
1557
1782
  * Android. When Chrome is missing, the fallback URL points to the Play Store so
@@ -2328,6 +2553,66 @@ export declare interface CommandProps {
2328
2553
  */
2329
2554
  export declare function compareValues(a: unknown, b: unknown): number;
2330
2555
 
2556
+ /**
2557
+ * Typed `localStorage` wrapper that gzips what it writes.
2558
+ *
2559
+ * Mirrors {@link storage} so the two are interchangeable at the call site; the
2560
+ * difference is only in how the value is encoded.
2561
+ */
2562
+ export declare const compressedStorage: {
2563
+ /**
2564
+ * Read and decompress a key.
2565
+ *
2566
+ * @typeParam T - The expected value shape.
2567
+ * @param key - Storage key.
2568
+ * @param fallback - Returned when the key is absent, unreadable, or corrupt.
2569
+ * @returns The stored value, or `fallback`.
2570
+ */
2571
+ get<T>(key: string, fallback: T): T;
2572
+ /**
2573
+ * Compress and write a key.
2574
+ *
2575
+ * When compression itself fails the value is written as plain JSON rather
2576
+ * than dropped: a slightly larger record still loads, an absent one does
2577
+ * not. Only a storage-level failure — quota, blocked storage — loses the
2578
+ * write.
2579
+ *
2580
+ * @typeParam T - The value being stored.
2581
+ * @param key - Storage key.
2582
+ * @param value - Any JSON-serializable value.
2583
+ */
2584
+ set<T>(key: string, value: T): void;
2585
+ };
2586
+
2587
+ /**
2588
+ * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's
2589
+ * cross-tab sync and SSR guard for free.
2590
+ *
2591
+ * @example
2592
+ * const [save, setSave] = useLocalStorage("save", EMPTY_SAVE, compressedStorageCodec);
2593
+ */
2594
+ export declare const compressedStorageCodec: {
2595
+ serialize: typeof compressToString;
2596
+ deserialize: typeof decompressFromString;
2597
+ };
2598
+
2599
+ /**
2600
+ * Serialize a value to a gzipped, base64 string carrying the format marker.
2601
+ *
2602
+ * Base64 costs a third more characters than the raw compressed bytes, and
2603
+ * `localStorage` bills two bytes per character on top of that. Packing the
2604
+ * bytes into UTF-16 code units directly would be denser, but lone surrogates
2605
+ * survive neither every storage implementation nor a JSON round-trip, and a
2606
+ * save that decodes to garbage is far worse than one that is bigger. Even with
2607
+ * that overhead a typical JSON document lands well under a third of its
2608
+ * uncompressed size.
2609
+ *
2610
+ * @typeParam T - The value being stored.
2611
+ * @param value - Any JSON-serializable value.
2612
+ * @returns The encoded string, ready for `localStorage`.
2613
+ */
2614
+ export declare function compressToString<T>(value: T): string;
2615
+
2331
2616
  /**
2332
2617
  * Conditionally wraps its children with `wrapper`.
2333
2618
  *
@@ -3287,6 +3572,36 @@ export declare interface CreateSentryTelemetryAdapterOptions {
3287
3572
  breadcrumbCategory?: string;
3288
3573
  }
3289
3574
 
3575
+ /**
3576
+ * A pool of preallocated `<audio>` elements for short sound effects.
3577
+ *
3578
+ * `new Audio(src)` on every play allocates an element and re-enters the network
3579
+ * stack for a file the browser already has, which is the wrong shape for a
3580
+ * sound that fires dozens of times a minute — a UI blip, a hit, a pickup. The
3581
+ * pool allocates once per source and replays.
3582
+ *
3583
+ * This is deliberately not {@link createAudioPlayer}: that handle tracks a
3584
+ * single "current" clip with loop, sink routing and lifecycle callbacks, which
3585
+ * is what background music needs. Effects are the opposite case — many
3586
+ * sources, all short, fire-and-forget, and the only thing that matters is that
3587
+ * firing one is cheap.
3588
+ *
3589
+ * A blocked `play()` is swallowed. Browsers reject playback until the user has
3590
+ * interacted with the page, and a sound effect is by definition not worth
3591
+ * interrupting anything over; call {@link SfxPool.preload} after the first
3592
+ * interaction if you want the pool warm.
3593
+ *
3594
+ * @param options - Master volume, base URL, voices per source and pool size.
3595
+ * @returns The pool handle.
3596
+ *
3597
+ * @example
3598
+ * const sfx = createSfxPool({ volume: 0.6, baseUrl: import.meta.env.BASE_URL });
3599
+ * sfx.preload(["sfx/select.mp3", "sfx/back.mp3"]);
3600
+ *
3601
+ * <button onClick={() => sfx.play("sfx/select.mp3")}>Confirmar</button>
3602
+ */
3603
+ export declare function createSfxPool(options?: SfxPoolOptions): SfxPool;
3604
+
3290
3605
  /**
3291
3606
  * Create a typed Zustand store with optional `persist` middleware, wired with
3292
3607
  * Tempest defaults. This is the generic counterpart to `createAuthStore`: use
@@ -3377,6 +3692,16 @@ export declare function createTempestAuth<TUser, TCredentials = {
3377
3692
  export declare interface CreateTempestAuthOptions<TUser> {
3378
3693
  /** Base URL of the API. Required. */
3379
3694
  baseURL: string;
3695
+ /**
3696
+ * Path segment the API is mounted under, such as `"/api"`, forwarded to
3697
+ * every client this preset builds — see the `prefix` option of
3698
+ * `createApiClient`.
3699
+ *
3700
+ * Safe to combine with the default paths below: the prefix is applied at
3701
+ * most once, so `"/api/auth/login"` under a `"/api"` prefix stays
3702
+ * `"/api/auth/login"`.
3703
+ */
3704
+ prefix?: string;
3380
3705
  /** Login route (`POST`). Default: `"/api/auth/login"`. */
3381
3706
  loginPath?: string;
3382
3707
  /** Refresh route (`POST`). Default: `"/api/auth/refresh"`. */
@@ -3586,6 +3911,36 @@ export declare interface CredentialsContainerLike {
3586
3911
  }): Promise<Credential | null>;
3587
3912
  }
3588
3913
 
3914
+ /** One column of the exported file. */
3915
+ export declare interface CsvColumn<T> {
3916
+ /** Property of the row this column reads from. Doubles as the column key. */
3917
+ key: keyof T;
3918
+ /** Column heading, written to the first line. */
3919
+ header: string;
3920
+ /**
3921
+ * Value for the file. Defaults to `String(row[key])`, with nullish becoming an
3922
+ * empty field.
3923
+ *
3924
+ * A `DataTableColumn` renders cells to `ReactNode`, which cannot be written to
3925
+ * a text file — a badge or a link would serialize as `[object Object]`. Give
3926
+ * the column this accessor and the export says what the badge said.
3927
+ */
3928
+ csv?: (row: T) => string | number | boolean | null | undefined;
3929
+ }
3930
+
3931
+ /** Options for {@link toCsv}. */
3932
+ export declare interface CsvOptions {
3933
+ /**
3934
+ * Field separator. Default `","`.
3935
+ *
3936
+ * Use `";"` for Excel on a locale whose decimal separator is the comma —
3937
+ * which is every pt-BR install — otherwise it opens the file in one column.
3938
+ */
3939
+ delimiter?: "," | ";";
3940
+ /** Prefix the output with a UTF-8 BOM. Default `true`. */
3941
+ bom?: boolean;
3942
+ }
3943
+
3589
3944
  /** Cursor-paginated response envelope (`CursorPaginationSchema[T]`). */
3590
3945
  export declare interface CursorPage<T> {
3591
3946
  /** The rows for this batch. */
@@ -3744,7 +4099,7 @@ export declare interface DataProviderOptions {
3744
4099
  * render without changing behaviour. That is why `exhaustive-deps` is silenced on
3745
4100
  * that dependency array.
3746
4101
  */
3747
- export declare function DataTable<T>({ data, columns, pageSize, searchable, searchKeys, initialSort, rowKey, emptyMessage, onCellChange, editLabels, className, ...rest }: DataTableProps<T>): JSX.Element;
4102
+ export declare function DataTable<T>({ data, columns, pageSize, searchable, searchKeys, initialSort, rowKey, emptyMessage, onCellChange, editLabels, totalItems, page: controlledPage, onPageChange, manualSort, onSortChange, manualSearch, onSearchChange, loading, className, ...rest }: DataTableProps<T>): JSX.Element;
3748
4103
 
3749
4104
  /** One accepted cell edit, handed to `onCellChange`. */
3750
4105
  export declare interface DataTableCellChange<T> {
@@ -3824,7 +4179,13 @@ export declare interface DataTableEditLabels {
3824
4179
  export declare type DataTableEditorType = "text" | "number" | "date" | "email" | "tel" | "url";
3825
4180
 
3826
4181
  export declare interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {
3827
- /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */
4182
+ /**
4183
+ * The rows to work with.
4184
+ *
4185
+ * By default this is the **full** dataset and sorting, searching and paging
4186
+ * all happen in memory. Pass `totalItems` and it becomes the current page as
4187
+ * the server returned it, with those three delegated to the caller.
4188
+ */
3828
4189
  data: T[];
3829
4190
  /** Column definitions. */
3830
4191
  columns: DataTableColumn<T>[];
@@ -3851,6 +4212,51 @@ export declare interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement
3851
4212
  onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;
3852
4213
  /** Override the PT-BR copy of the editing affordances. */
3853
4214
  editLabels?: Partial<DataTableEditLabels>;
4215
+ /**
4216
+ * Total row count across every page — the `total` of a paginated envelope.
4217
+ *
4218
+ * Passing it switches the table to **server mode**: `data` is read as the
4219
+ * current page, the page count comes from this number instead of
4220
+ * `data.length`, and sorting and searching are delegated to the caller
4221
+ * (see `manualSort` / `manualSearch`, which are implied here). Pair it with
4222
+ * `page` and `onPageChange`.
4223
+ */
4224
+ totalItems?: number;
4225
+ /** Current page, 1-based. Controlled — required in server mode. */
4226
+ page?: number;
4227
+ /** Called with the next page. Required whenever `page` is controlled. */
4228
+ onPageChange?: (page: number) => void;
4229
+ /**
4230
+ * Sorting is the caller's job: clicking a header reports through
4231
+ * `onSortChange` and the rows are left in the order they arrived.
4232
+ *
4233
+ * Implied by `totalItems`, because sorting the page in memory would sort
4234
+ * *that page only* while the header claims the whole table is ordered.
4235
+ */
4236
+ manualSort?: boolean;
4237
+ /** Called with the next sort state — `null` when the header cycles back to unsorted. */
4238
+ onSortChange?: (sort: DataTableSort<T> | null) => void;
4239
+ /**
4240
+ * Searching is the caller's job: typing reports through `onSearchChange` and
4241
+ * the rows are left as they arrived.
4242
+ *
4243
+ * Implied by `totalItems`. Filtering the current page would hide the rows
4244
+ * that do not match *on this page* and show nothing for a term that only
4245
+ * matches on page three — an empty table that looks like "no results".
4246
+ */
4247
+ manualSearch?: boolean;
4248
+ /** Called with the current search term (debouncing, if any, is the caller's). */
4249
+ onSearchChange?: (term: string) => void;
4250
+ /**
4251
+ * A fetch is in flight.
4252
+ *
4253
+ * With rows already on screen they stay put, dimmed and `aria-busy`, so the
4254
+ * page does not jump under the cursor between pages. With no rows yet it
4255
+ * renders placeholder lines at full height, which is a different statement
4256
+ * from `emptyMessage`: "loading" and "there is nothing" are not the same
4257
+ * screen.
4258
+ */
4259
+ loading?: boolean;
3854
4260
  }
3855
4261
 
3856
4262
  export declare interface DataTableSort<T> {
@@ -3953,6 +4359,20 @@ export declare interface DecodedJWT {
3953
4359
  */
3954
4360
  export declare function decodeJWT(token: string): DecodedJWT;
3955
4361
 
4362
+ /**
4363
+ * Decode a string produced by {@link compressToString}.
4364
+ *
4365
+ * A string without the marker is parsed as plain JSON, so values written before
4366
+ * compression was enabled — or by a write that fell back after `gzipSync`
4367
+ * failed — still read back.
4368
+ *
4369
+ * @typeParam T - The expected value shape.
4370
+ * @param raw - The stored string.
4371
+ * @returns The decoded value.
4372
+ * @throws If the payload is neither valid compressed data nor valid JSON.
4373
+ */
4374
+ export declare function decompressFromString<T>(raw: string): T;
4375
+
3956
4376
  /**
3957
4377
  * Recursively merge `source` into `target`, returning a new object.
3958
4378
  *
@@ -3974,6 +4394,15 @@ export declare function decodeJWT(token: string): DecodedJWT;
3974
4394
  */
3975
4395
  export declare function deepMerge<T>(target: T, source: Partial<T>): T;
3976
4396
 
4397
+ /**
4398
+ * PT-BR defaults, used when no strings are supplied and no catalog answers.
4399
+ *
4400
+ * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so
4401
+ * the default here matches rather than introducing an English string that only
4402
+ * shows up on a network failure.
4403
+ */
4404
+ export declare const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings;
4405
+
3977
4406
  /**
3978
4407
  * What a scanner looks for when you do not say.
3979
4408
  *
@@ -4035,6 +4464,36 @@ export declare const DEFAULT_PUB_KEY_CRED_PARAMS: {
4035
4464
  */
4036
4465
  export declare function defineRoutes(routes: TempestRouteObject[]): TempestRouteObject[];
4037
4466
 
4467
+ /**
4468
+ * Turn any caught value into a sentence worth showing.
4469
+ *
4470
+ * The funnel, in order:
4471
+ *
4472
+ * 1. A request that never reached the server — `status === 0`, or a non-API
4473
+ * error thrown while the browser reports itself offline — produces the
4474
+ * offline sentence. This is the step apps skip, and skipping it renders
4475
+ * "erro 0" or a raw `TypeError` at the user.
4476
+ * 2. The backend's own `detail`, which is the most specific thing available and
4477
+ * is already written for a person.
4478
+ * 3. `fallback`, with `(HTTP <status>)` appended when a status is known, so the
4479
+ * screenshot in the support ticket carries the one fact a developer needs.
4480
+ *
4481
+ * Pure on purpose: it works in an interceptor, in a logger and anywhere outside
4482
+ * the React tree. {@link useDescribeApiError} is the same funnel with the
4483
+ * sentences resolved through `I18nProvider`.
4484
+ *
4485
+ * @example
4486
+ * catch (error) {
4487
+ * toast(describeApiError(error, "Não foi possível salvar o pedido"));
4488
+ * }
4489
+ *
4490
+ * @param error - The caught value, of any shape.
4491
+ * @param fallback - What to say when the error carries nothing better.
4492
+ * @param strings - Overrides for the fixed sentences.
4493
+ * @returns A sentence to show the user.
4494
+ */
4495
+ export declare function describeApiError(error: unknown, fallback: string, strings?: Partial<ApiErrorStrings>): string;
4496
+
4038
4497
  /**
4039
4498
  * One filter, in words: `"Status é Pago"`.
4040
4499
  *
@@ -4169,6 +4628,25 @@ export declare type DividerVariant = "solid" | "dashed";
4169
4628
 
4170
4629
  export declare type DocumentVisibility = "visible" | "hidden";
4171
4630
 
4631
+ /**
4632
+ * Build a CSV and hand it to the user.
4633
+ *
4634
+ * Goes through {@link shareOrDownloadBlob}, so on a phone it opens the native
4635
+ * share sheet and everywhere else it downloads — the same path every other
4636
+ * generated artifact in the SDK takes, instead of a fourth hand-rolled `<a
4637
+ * download>`.
4638
+ *
4639
+ * @example
4640
+ * await downloadCsv(users, COLUMNS, "usuarios.csv");
4641
+ *
4642
+ * @param rows - The rows to export.
4643
+ * @param columns - Columns, in the order they should appear.
4644
+ * @param fileName - File name offered to the user. Default `"export.csv"`.
4645
+ * @param options - Delimiter and BOM, forwarded to {@link toCsv}.
4646
+ * @returns A promise that resolves once the share or download completes.
4647
+ */
4648
+ export declare function downloadCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], fileName?: string, options?: CsvOptions): Promise<void>;
4649
+
4172
4650
  /**
4173
4651
  * Sliding side panel. Same building blocks as {@link Modal} but anchored to
4174
4652
  * an edge. Locks body scroll while open.
@@ -4637,6 +5115,51 @@ export declare type FilterPredicate<T> = (item: T, search: string) => boolean;
4637
5115
  */
4638
5116
  export declare function filtersFromSearchParams(params: URLSearchParams, fields: readonly FilterField[]): Filter[];
4639
5117
 
5118
+ /**
5119
+ * Encode filters as query params for a paginated backend.
5120
+ *
5121
+ * The counterpart of {@link applyFilters}: same filter set, evaluated by the
5122
+ * database instead of by the browser, which is the only option once the list is
5123
+ * paginated on the server and the page in memory is not the whole result.
5124
+ *
5125
+ * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already
5126
+ * reads (`BaseRepository._apply_filters` → `build_filter_condition`):
5127
+ *
5128
+ * | Operator | Param |
5129
+ * | --- | --- |
5130
+ * | `eq` | `field` (or `field__iexact` for the `name` column) |
5131
+ * | `ne` | `field__ne` |
5132
+ * | `contains` | `field__icontains` |
5133
+ * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |
5134
+ * | `between` | `field__between` twice, low value first |
5135
+ * | `in` | `field__in` once per value |
5136
+ * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |
5137
+ *
5138
+ * Returns `URLSearchParams` rather than a plain object because `between` carries
5139
+ * a pair and `in` carries a list: an object would keep only the last value of
5140
+ * each, silently narrowing the filter. Repeated params are also how FastAPI
5141
+ * receives a `list[str]` declared with `Query`.
5142
+ *
5143
+ * Two things the backend has to hold up its end of, or the filter fails quietly:
5144
+ *
5145
+ * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`
5146
+ * only forwards fields the subclass declares, so a `status__ne` the schema
5147
+ * never mentions is dropped by FastAPI before the repository sees it — no
5148
+ * error, no filtering.
5149
+ * - **`isnull` matches `NULL` only.** A column that stores `""` for "no value"
5150
+ * will not answer an `empty` filter, while {@link applyFilters} treats blank
5151
+ * text as empty.
5152
+ *
5153
+ * @example
5154
+ * const params = filtersToQueryParams(filters);
5155
+ * params.set("page", String(page));
5156
+ * const data = await api.get(`/orders?${params}`);
5157
+ *
5158
+ * @param filters - Applied filters; incomplete ones are ignored.
5159
+ * @returns Params ready to append to a request URL.
5160
+ */
5161
+ export declare function filtersToQueryParams(filters: readonly Filter[]): URLSearchParams;
5162
+
4640
5163
  /**
4641
5164
  * Serialize filters into URL search params.
4642
5165
  *
@@ -4840,6 +5363,30 @@ export declare function formatCurrency(value: number): string;
4840
5363
  */
4841
5364
  export declare function formatDate(value: string | Date): string;
4842
5365
 
5366
+ /**
5367
+ * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an
5368
+ * `<input type="date">` accepts.
5369
+ *
5370
+ * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,
5371
+ * which is the reflex and which is wrong: `toISOString` converts to UTC first, so
5372
+ * anything after 21:00 in UTC-3 reports the next day and the form opens on the
5373
+ * wrong date. `formatDate` cannot fill this role because a date input rejects
5374
+ * `dd/MM/yyyy` outright.
5375
+ *
5376
+ * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut
5377
+ * is load-bearing rather than an optimisation: `new Date("2026-05-16")` is parsed
5378
+ * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the
5379
+ * exact value a backend sent would move it back a day.
5380
+ *
5381
+ * @example
5382
+ * <input type="date" defaultValue={formatDateForInput(order.createdAt)} />
5383
+ *
5384
+ * @param value - ISO string or Date.
5385
+ * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —
5386
+ * which is what a date input reads as "no value", unlike `"Invalid Date"`.
5387
+ */
5388
+ export declare function formatDateForInput(value: string | Date): string;
5389
+
4843
5390
  /**
4844
5391
  * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.
4845
5392
  *
@@ -6186,17 +6733,22 @@ declare interface LayoutProps {
6186
6733
  }
6187
6734
 
6188
6735
  /**
6189
- * Wrap `React.lazy` with automatic retry. Common cause of failure:
6190
- * deployed-then-cached `index.html` references chunk filenames that no
6191
- * longer exist. Retrying after a short delay typically picks up the new
6192
- * bundle; a final `location.reload()` recovers from stale `index.html`.
6736
+ * Wrap `React.lazy` with automatic retry and a `preload()` method.
6737
+ *
6738
+ * Common cause of failure: deployed-then-cached `index.html` references chunk
6739
+ * filenames that no longer exist. Retrying after a short delay typically picks
6740
+ * up the new bundle; a final `location.reload()` recovers from stale
6741
+ * `index.html`.
6193
6742
  *
6194
6743
  * @example
6195
6744
  * const Settings = lazyWithRetry(() => import("./Settings"));
6745
+ *
6746
+ * // Warm the chunk when the route becomes likely, not when it is needed.
6747
+ * <a href="/settings" onMouseEnter={() => void Settings.preload()}>Settings</a>
6196
6748
  */
6197
6749
  export declare function lazyWithRetry<T extends ComponentType<unknown>>(factory: () => Promise<{
6198
6750
  default: T;
6199
- }>, options?: LazyWithRetryOptions): ReturnType<typeof lazy<T>>;
6751
+ }>, options?: LazyWithRetryOptions): PreloadableLazy<T>;
6200
6752
 
6201
6753
  export declare interface LazyWithRetryOptions {
6202
6754
  /** Max attempts. Default: 3. */
@@ -8005,6 +8557,33 @@ export declare interface PcmAudio {
8005
8557
  sampleRate: number;
8006
8558
  }
8007
8559
 
8560
+ /**
8561
+ * What percentage of `total` is `part`, with an empty base yielding `0`.
8562
+ *
8563
+ * The guard is the whole point: `(part / total) * 100` produces `NaN` when the
8564
+ * base is zero and `Infinity` when only the base is missing, and both reach the
8565
+ * screen as a broken label — `NaN%` on an empty dashboard is the single most
8566
+ * common way a panel announces that it has no data yet.
8567
+ *
8568
+ * Returns a **0–100** number, which is what a bar width and a label want.
8569
+ * `formatPercent` takes a 0–1 fraction instead, so pair them as
8570
+ * `formatPercent(percentOf(a, b) / 100)` — or skip `percentOf` and pass the
8571
+ * fraction straight in.
8572
+ *
8573
+ * The result is not clamped: 120% of a target is a real number somebody wants to
8574
+ * see, and capping it would hide the interesting case.
8575
+ *
8576
+ * @example
8577
+ * percentOf(3, 4); // 75
8578
+ * percentOf(5, 0); // 0
8579
+ * percentOf(12, 8); // 150
8580
+ *
8581
+ * @param part - The measured amount.
8582
+ * @param total - The base it is measured against.
8583
+ * @returns The percentage, or `0` when the base is zero or either input is not finite.
8584
+ */
8585
+ export declare function percentOf(part: number, total: number): number;
8586
+
8008
8587
  /**
8009
8588
  * Extract a permission list from a JWT.
8010
8589
  *
@@ -8181,6 +8760,12 @@ export declare interface PlayAudioOptions {
8181
8760
  onError?: (error: unknown) => void;
8182
8761
  }
8183
8762
 
8763
+ /** Per-play overrides. */
8764
+ export declare interface PlaySfxOptions {
8765
+ /** Volume for this play, `0`–`1`, multiplied by the pool's master. Default `1`. */
8766
+ volume?: number;
8767
+ }
8768
+
8184
8769
  /**
8185
8770
  * Pick the singular or plural form of a word based on `count`.
8186
8771
  *
@@ -8276,6 +8861,29 @@ export declare interface PostHogLike {
8276
8861
  reset?: () => void;
8277
8862
  }
8278
8863
 
8864
+ /** A lazy component that can also be fetched ahead of being rendered. */
8865
+ export declare type PreloadableLazy<T extends ComponentType<unknown>> = ReturnType<typeof lazy<T>> & {
8866
+ /**
8867
+ * Start fetching the chunk now, before anything renders it.
8868
+ *
8869
+ * Call it on the interaction that makes the route likely — hovering the
8870
+ * link, opening the menu that holds it, finishing the step before it — so
8871
+ * the chunk is warm by the time the user commits and the suspense fallback
8872
+ * never appears.
8873
+ *
8874
+ * Shares its work with the render path: whichever fires first performs the
8875
+ * single fetch and the other awaits the same promise. Safe to call
8876
+ * repeatedly.
8877
+ *
8878
+ * @returns The module, once loaded. Rejects when every retry failed; that
8879
+ * rejection is already handled internally, so a fire-and-forget call
8880
+ * never surfaces as an unhandled rejection.
8881
+ */
8882
+ preload: () => Promise<{
8883
+ default: T;
8884
+ }>;
8885
+ };
8886
+
8279
8887
  /** A model whose cached size should appear in the report. */
8280
8888
  export declare interface ProfiledModel {
8281
8889
  /** Label for the report row, e.g. `"detector"`. */
@@ -8475,7 +9083,12 @@ export declare type QRMode = "numeric" | "alphanumeric" | "byte";
8475
9083
 
8476
9084
  /**
8477
9085
  * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with
8478
- * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).
9086
+ * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for
9087
+ * failures worth replaying).
9088
+ *
9089
+ * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other
9090
+ * than 408/429 is the server refusing on purpose, and replaying it only doubles
9091
+ * the network log while the spinner keeps turning.
8479
9092
  */
8480
9093
  export declare function QueryProvider({ children, client, defaultOptions }: QueryProviderProps): JSX.Element;
8481
9094
 
@@ -9441,6 +10054,46 @@ export declare interface SequentialScaleOptions {
9441
10054
  */
9442
10055
  export declare function setAudioOutput(element: HTMLMediaElement | null, sinkId: string): Promise<boolean>;
9443
10056
 
10057
+ /** Imperative handle over a pool of short sound effects. */
10058
+ export declare interface SfxPool {
10059
+ /** Play a clip, allocating and caching its element on first use. */
10060
+ play: (src: string, options?: PlaySfxOptions) => void;
10061
+ /** Fetch clips ahead of the first play, so it is not silent while the file downloads. */
10062
+ preload: (src: string | string[]) => void;
10063
+ /** Set the master volume, applying it to anything already sounding. */
10064
+ setVolume: (volume: number) => void;
10065
+ /** Stop one source, or every source when called with no argument. */
10066
+ stop: (src?: string) => void;
10067
+ /** Release every element. Call on unmount. */
10068
+ dispose: () => void;
10069
+ }
10070
+
10071
+ /** Options for {@link createSfxPool}. */
10072
+ export declare interface SfxPoolOptions {
10073
+ /**
10074
+ * Master volume, `0`–`1`, multiplied into every per-play volume. Default
10075
+ * `1`. Wire it to the app's sound setting so one value governs the lot.
10076
+ */
10077
+ volume?: number;
10078
+ /**
10079
+ * Prefix applied to sources that are not absolute URLs — typically Vite's
10080
+ * `import.meta.env.BASE_URL`, so a build served from a subpath resolves.
10081
+ * Default `""`.
10082
+ */
10083
+ baseUrl?: string;
10084
+ /**
10085
+ * Elements kept per source. `1` (the default) restarts the clip on every
10086
+ * play, which is what a menu blip wants. Raise it to let a sound overlap
10087
+ * itself — a hit landing while the previous one is still ringing.
10088
+ */
10089
+ voices?: number;
10090
+ /**
10091
+ * Maximum number of distinct sources held. When exceeded, the least
10092
+ * recently played source is released. Default `48`.
10093
+ */
10094
+ maxSources?: number;
10095
+ }
10096
+
9444
10097
  /**
9445
10098
  * Wrap the Web Share API with a uniform result object. Falls through to
9446
10099
  * `unsupported: true` when the browser lacks `navigator.share`, leaving the
@@ -9493,6 +10146,25 @@ export declare interface ShareResult {
9493
10146
  error?: unknown;
9494
10147
  }
9495
10148
 
10149
+ /**
10150
+ * Default `retry` for SDK queries: replay transport and server failures, never a
10151
+ * deliberate client-side refusal.
10152
+ *
10153
+ * A flat `retry: 1` replays a 403 on an admin-only listing and a 404 for a record
10154
+ * somebody deleted. The server meant both — the second attempt returns the same
10155
+ * answer, doubles the network log and holds the spinner on screen for the length
10156
+ * of another round trip. A network failure or a 5xx is a different thing: it may
10157
+ * well succeed on the next try, so those keep the previous behaviour.
10158
+ *
10159
+ * @example
10160
+ * new QueryClient({ defaultOptions: { queries: { retry: shouldRetryQuery } } });
10161
+ *
10162
+ * @param failureCount - Attempts already made, as react-query counts them.
10163
+ * @param error - The rejection value from the query function.
10164
+ * @returns Whether react-query should try again.
10165
+ */
10166
+ export declare function shouldRetryQuery(failureCount: number, error: unknown): boolean;
10167
+
9496
10168
  /**
9497
10169
  * Conditionally render children based on the viewport breakpoint.
9498
10170
  *
@@ -10642,6 +11314,28 @@ export declare interface ToastProviderProps {
10642
11314
 
10643
11315
  export declare type ToastVariant = "success" | "warning" | "error" | "info";
10644
11316
 
11317
+ /**
11318
+ * Serialize rows to CSV text, RFC 4180 style.
11319
+ *
11320
+ * Rows are separated by `\r\n` — the RFC's terminator and the one Excel is least
11321
+ * surprised by. A row is emitted for the header even when `rows` is empty, so the
11322
+ * person who opens the file sees which columns they asked for instead of a blank
11323
+ * document.
11324
+ *
11325
+ * @example
11326
+ * const csv = toCsv(users, [
11327
+ * { key: "name", header: "Nome" },
11328
+ * { key: "email", header: "E-mail" },
11329
+ * { key: "plan", header: "Plano", csv: (user) => user.plan.label },
11330
+ * ]);
11331
+ *
11332
+ * @param rows - The rows to export.
11333
+ * @param columns - Columns, in the order they should appear.
11334
+ * @param options - Delimiter and BOM.
11335
+ * @returns The complete file contents.
11336
+ */
11337
+ export declare function toCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], options?: CsvOptions): string;
11338
+
10645
11339
  /**
10646
11340
  * A pressable two-state button — like a checkbox styled as a button.
10647
11341
  *
@@ -11598,6 +12292,41 @@ export declare interface UseClipboardResult {
11598
12292
  reset: () => void;
11599
12293
  }
11600
12294
 
12295
+ /**
12296
+ * Counts down the time left on a window that started at a known instant.
12297
+ *
12298
+ * Written around a timestamp rather than a decrementing counter on purpose: the
12299
+ * remaining time is recomputed from `Date.now()` on every tick, so a throttled
12300
+ * background tab, a slow frame or a `setInterval` that drifts cannot make the
12301
+ * countdown disagree with the clock. Remounting recovers the correct value too,
12302
+ * which a counter held in state cannot do.
12303
+ *
12304
+ * The interval stops once it reaches zero instead of ticking forever behind a
12305
+ * clamp.
12306
+ *
12307
+ * @param durationMs - Length of the window.
12308
+ * @param startedAt - Epoch ms the window opened (`Date.now()` when it started).
12309
+ * @param options - Tick cadence.
12310
+ * @returns Milliseconds remaining, never below `0`.
12311
+ *
12312
+ * @example
12313
+ * const remaining = useCountdown(60_000, lastSentAt);
12314
+ *
12315
+ * <button disabled={remaining > 0}>
12316
+ * {remaining > 0 ? `Reenviar em ${Math.ceil(remaining / 1000)}s` : "Reenviar código"}
12317
+ * </button>
12318
+ */
12319
+ export declare function useCountdown(durationMs: number, startedAt: number, options?: UseCountdownOptions): number;
12320
+
12321
+ /** Options for {@link useCountdown}. */
12322
+ export declare interface UseCountdownOptions {
12323
+ /**
12324
+ * How often the remaining time is recomputed, in ms. Default `1000`, which
12325
+ * suits a "try again in 3s" label; drop it to ~50 to drive a progress bar.
12326
+ */
12327
+ tickMs?: number;
12328
+ }
12329
+
11601
12330
  /**
11602
12331
  * Numeric counter clamped to an optional `[min, max]` range.
11603
12332
  *
@@ -11712,6 +12441,31 @@ export declare function useDelete<T>(resource: string, options?: UseDeleteOption
11712
12441
  /** Options for the delete mutation (mutationFn + onSuccess are provided). */
11713
12442
  export declare type UseDeleteOptions<T> = Omit<UseMutationOptions<T, Error, string | number>, "mutationFn">;
11714
12443
 
12444
+ /**
12445
+ * {@link describeApiError} with its fixed sentences resolved through the active
12446
+ * `I18nProvider`.
12447
+ *
12448
+ * The funnel is not duplicated — this hook only supplies the strings and calls
12449
+ * the pure function. Which is also why both exist: the pure one runs in an
12450
+ * interceptor or a logger, where there is no React tree to read a context from,
12451
+ * and this one runs in a component without every caller passing translations
12452
+ * down by hand.
12453
+ *
12454
+ * Works with no provider at all: i18n is opt-in in this SDK, so a missing
12455
+ * provider — or a catalog that never defined `tempest.error.offline` — falls
12456
+ * back to the pt-BR default rather than crashing or printing the raw key.
12457
+ *
12458
+ * @example
12459
+ * const describe = useDescribeApiError();
12460
+ * const { mutate } = useMutation({
12461
+ * mutationFn: save,
12462
+ * onError: (error) => toast(describe(error, t("orders.saveFailed"))),
12463
+ * });
12464
+ *
12465
+ * @returns A stable `(error, fallback) => string` function.
12466
+ */
12467
+ export declare function useDescribeApiError(): (error: unknown, fallback: string) => string;
12468
+
11715
12469
  /**
11716
12470
  * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.
11717
12471
  *
@@ -12011,6 +12765,40 @@ export declare interface UseKeyboardShortcutOptions {
12011
12765
  ignoreInput?: boolean;
12012
12766
  }
12013
12767
 
12768
+ /**
12769
+ * Keeps a ref pointing at the most recent value it was given.
12770
+ *
12771
+ * The escape hatch for reading fresh state inside something that must not be
12772
+ * re-created when that state changes: an interval, a subscription, an event
12773
+ * listener registered once on mount. Listing the value in the effect's
12774
+ * dependencies would tear the effect down and set it back up on every change;
12775
+ * omitting it captures the value from the render that created the closure and
12776
+ * never sees another. The ref is neither — a stable object whose `current` is
12777
+ * always current.
12778
+ *
12779
+ * The assignment happens **during render**, for the same reason
12780
+ * {@link useStableCallback} does it: moving it into an effect opens a
12781
+ * one-commit staleness window, where an effect declared earlier in the same
12782
+ * commit reads the previous render's value.
12783
+ *
12784
+ * Reach for {@link useStableCallback} instead when the value is a function you
12785
+ * want to *call* — it hands back a callable with a stable identity, rather than
12786
+ * making every call site reach through `.current`.
12787
+ *
12788
+ * @typeParam T - The tracked value.
12789
+ * @param value - The value to track. Written on every render.
12790
+ * @returns A stable ref whose `current` holds the latest `value`.
12791
+ *
12792
+ * @example
12793
+ * const optionsRef = useLatestRef(options);
12794
+ *
12795
+ * useEffect(() => {
12796
+ * const id = setInterval(() => poll(optionsRef.current), 5_000);
12797
+ * return () => clearInterval(id);
12798
+ * }, []); // the interval survives every options change, and still reads the latest
12799
+ */
12800
+ export declare function useLatestRef<T>(value: T): RefObject<T>;
12801
+
12014
12802
  /**
12015
12803
  * Query a paginated list of a resource through the active {@link useDataProvider}.
12016
12804
  *
@@ -12609,6 +13397,18 @@ export declare interface UseOnlineOptions {
12609
13397
  timeoutMs?: number;
12610
13398
  }
12611
13399
 
13400
+ /**
13401
+ * Access translation helpers when there may be no {@link I18nProvider} above.
13402
+ *
13403
+ * Returns `null` instead of throwing, which is what a piece of the SDK needs
13404
+ * when it wants to be translated where a catalog exists and still work where one
13405
+ * does not — i18n is opt-in in this SDK, and a helper that demanded a provider
13406
+ * would turn "you did not configure translations" into a crash.
13407
+ *
13408
+ * @returns The context value, or `null` outside a provider.
13409
+ */
13410
+ export declare function useOptionalI18n(): I18nContextValue | null;
13411
+
12612
13412
  /**
12613
13413
  * Offset-pagination hook over TanStack Query for the fastapi-pagination /
12614
13414
  * Tempest envelope (`{ items, total, page, size, pages }`).
@@ -13187,6 +13987,30 @@ export declare interface UseSetResult<T> {
13187
13987
  size: number;
13188
13988
  }
13189
13989
 
13990
+ /**
13991
+ * Component-scoped {@link createSfxPool}, with the lifecycle wired up.
13992
+ *
13993
+ * The pool is created once — through a lazy `useState` initializer, so no
13994
+ * element is allocated on a render that gets discarded — and disposed on
13995
+ * unmount, so its elements do not outlive the screen that owns them.
13996
+ *
13997
+ * `volume` is tracked separately: changing it calls `setVolume` on the existing
13998
+ * pool rather than rebuilding it, which would throw away every clip the user
13999
+ * has already downloaded — exactly the cost the pool exists to avoid.
14000
+ * `baseUrl`, `voices` and `maxSources` are read once, at creation.
14001
+ *
14002
+ * @param options - Passed through to {@link createSfxPool}.
14003
+ * @returns A stable pool handle.
14004
+ *
14005
+ * @example
14006
+ * const sfx = useSfxPool({ volume: settings.sfxVolume / 100, baseUrl: import.meta.env.BASE_URL });
14007
+ *
14008
+ * useEffect(() => { sfx.preload(["sfx/select.mp3", "sfx/back.mp3"]); }, [sfx]);
14009
+ *
14010
+ * <button onClick={() => sfx.play("sfx/select.mp3")}>Confirmar</button>
14011
+ */
14012
+ export declare function useSfxPool(options?: SfxPoolOptions): SfxPool;
14013
+
13190
14014
  /**
13191
14015
  * Drag-to-reorder for a list, with a **keyboard path of equal standing**.
13192
14016
  *
@@ -13537,6 +14361,39 @@ export declare interface UseTorchResult {
13537
14361
  */
13538
14362
  export declare function useTranslate(): I18nContextValue["t"];
13539
14363
 
14364
+ /**
14365
+ * Reveals a string one character at a time.
14366
+ *
14367
+ * Changing `text` restarts the reveal. The reset happens during render rather
14368
+ * than in an effect, so the new string never flashes in full for one frame
14369
+ * before the animation takes over.
14370
+ *
14371
+ * Always give the reader a way out: an animation that cannot be skipped is a
14372
+ * tax on anyone re-reading or moving fast, which is what `skip` is for.
14373
+ *
14374
+ * @param text - The full string to reveal. Nullish is treated as empty.
14375
+ * @param speedMs - Delay between characters. `0` or less renders instantly,
14376
+ * which is the hook-safe way to honour `prefers-reduced-motion`.
14377
+ * @returns The revealed prefix, whether it finished, and a `skip` action.
14378
+ *
14379
+ * @example
14380
+ * const reduced = useMediaQuery("(prefers-reduced-motion: reduce)");
14381
+ * const { displayedText, isComplete, skip } = useTypewriter(line, reduced ? 0 : 30);
14382
+ *
14383
+ * return <p onClick={skip}>{displayedText}{isComplete ? "" : "▌"}</p>;
14384
+ */
14385
+ export declare function useTypewriter(text: string, speedMs: number): UseTypewriterResult;
14386
+
14387
+ /** State returned by {@link useTypewriter}. */
14388
+ export declare interface UseTypewriterResult {
14389
+ /** The prefix of `text` revealed so far. */
14390
+ displayedText: string;
14391
+ /** `true` once the whole string is on screen. */
14392
+ isComplete: boolean;
14393
+ /** Reveal the rest immediately — wire it to a tap or a key press. */
14394
+ skip: () => void;
14395
+ }
14396
+
13540
14397
  /**
13541
14398
  * Update a record and invalidate both the list and the single-record caches.
13542
14399
  *