@eifi1/ui-kit 0.52.0 → 0.54.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 (290) hide show
  1. package/README.md +52 -59
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/auth/auth-errors.d.ts +36 -0
  5. package/dist/auth/auth-errors.js +20 -0
  6. package/dist/auth/auth-errors.js.map +1 -1
  7. package/dist/billing/cancel-subscription.d.ts +5 -14
  8. package/dist/billing/cancel-subscription.js +1 -3
  9. package/dist/billing/cancel-subscription.js.map +1 -1
  10. package/dist/billing/plan-card.d.ts +5 -13
  11. package/dist/billing/plan-card.js +3 -11
  12. package/dist/billing/plan-card.js.map +1 -1
  13. package/dist/billing/plan-switch.d.ts +5 -14
  14. package/dist/billing/plan-switch.js +1 -3
  15. package/dist/billing/plan-switch.js.map +1 -1
  16. package/dist/chart.d.ts +3 -14
  17. package/dist/chart.js +64 -11
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/amount-input.d.ts +2 -1
  20. package/dist/components/amount-input.js.map +1 -1
  21. package/dist/components/calendar-heatmap.js +4 -2
  22. package/dist/components/calendar-heatmap.js.map +1 -1
  23. package/dist/components/currency-option-list.d.ts +2 -1
  24. package/dist/components/currency-option-list.js +10 -3
  25. package/dist/components/currency-option-list.js.map +1 -1
  26. package/dist/components/currency-select.d.ts +3 -1
  27. package/dist/components/currency-select.js.map +1 -1
  28. package/dist/components/danger-confirm.d.ts +22 -1
  29. package/dist/components/danger-confirm.js +50 -28
  30. package/dist/components/danger-confirm.js.map +1 -1
  31. package/dist/components/data-table-filter-popover.d.ts +2 -1
  32. package/dist/components/data-table-filter-popover.js +3 -2
  33. package/dist/components/data-table-filter-popover.js.map +1 -1
  34. package/dist/components/data-table-filters.d.ts +33 -3
  35. package/dist/components/data-table-filters.js +6 -4
  36. package/dist/components/data-table-filters.js.map +1 -1
  37. package/dist/components/date-field.d.ts +1 -1
  38. package/dist/components/date-field.js.map +1 -1
  39. package/dist/components/date-picker.d.ts +1 -1
  40. package/dist/components/date-picker.js.map +1 -1
  41. package/dist/components/date-range-picker.d.ts +4 -17
  42. package/dist/components/date-range-picker.js +2 -2
  43. package/dist/components/date-range-picker.js.map +1 -1
  44. package/dist/components/dialog-frame.d.ts +3 -1
  45. package/dist/components/dialog-frame.js +6 -2
  46. package/dist/components/dialog-frame.js.map +1 -1
  47. package/dist/components/mini-calendar.js +3 -2
  48. package/dist/components/mini-calendar.js.map +1 -1
  49. package/dist/components/modal.d.ts +27 -0
  50. package/dist/components/modal.js +5 -3
  51. package/dist/components/modal.js.map +1 -1
  52. package/dist/components/nav-pills.d.ts +1 -1
  53. package/dist/components/nav-pills.js.map +1 -1
  54. package/dist/components/number-input.js.map +1 -1
  55. package/dist/components/series-chart-axes.d.ts +1 -1
  56. package/dist/components/series-chart-axes.js.map +1 -1
  57. package/dist/components/series-chart.d.ts +64 -19
  58. package/dist/components/series-chart.js +2 -1
  59. package/dist/components/series-chart.js.map +1 -1
  60. package/dist/components/stat-tile.d.ts +21 -2
  61. package/dist/components/stat-tile.js +4 -2
  62. package/dist/components/stat-tile.js.map +1 -1
  63. package/dist/components/treemap-labels.d.ts +6 -1
  64. package/dist/components/treemap-labels.js +2 -2
  65. package/dist/components/treemap-labels.js.map +1 -1
  66. package/dist/components/treemap.d.ts +29 -4
  67. package/dist/components/treemap.js +22 -9
  68. package/dist/components/treemap.js.map +1 -1
  69. package/dist/currencies.d.ts +16 -2
  70. package/dist/currencies.js +2 -1
  71. package/dist/currencies.js.map +1 -1
  72. package/dist/data-table.d.ts +1 -6
  73. package/dist/data-table.js +1 -9
  74. package/dist/data-table.js.map +1 -1
  75. package/dist/demo/demo-countdown.d.ts +0 -5
  76. package/dist/demo/demo-countdown.js +0 -4
  77. package/dist/demo/demo-countdown.js.map +1 -1
  78. package/dist/eslint/index.js +1 -1
  79. package/dist/feedback/feedback-context-labels.js +2 -1
  80. package/dist/feedback/feedback-context-labels.js.map +1 -1
  81. package/dist/feedback/feedback-context.d.ts +19 -3
  82. package/dist/feedback/feedback-context.js +12 -1
  83. package/dist/feedback/feedback-context.js.map +1 -1
  84. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  85. package/dist/feedback/feedback-diagnostics.js +27 -0
  86. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  87. package/dist/feedback/feedback-dialog.d.ts +10 -0
  88. package/dist/feedback/feedback-dialog.js +6 -4
  89. package/dist/feedback/feedback-dialog.js.map +1 -1
  90. package/dist/feedback/feedback-mutation.d.ts +11 -23
  91. package/dist/feedback/feedback-mutation.js +1 -9
  92. package/dist/feedback/feedback-mutation.js.map +1 -1
  93. package/dist/feedback/feedback-public-list.d.ts +1 -6
  94. package/dist/feedback/feedback-public-list.js +1 -2
  95. package/dist/feedback/feedback-public-list.js.map +1 -1
  96. package/dist/feedback/feedback-record.d.ts +5 -0
  97. package/dist/feedback/feedback-record.js.map +1 -1
  98. package/dist/feedback/feedback-row-detail-labels.js +1 -8
  99. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  100. package/dist/feedback/feedback-row-detail.d.ts +6 -50
  101. package/dist/feedback/feedback-row-detail.js +3 -8
  102. package/dist/feedback/feedback-row-detail.js.map +1 -1
  103. package/dist/feedback/feedback-status-undo.d.ts +3 -19
  104. package/dist/feedback/feedback-status-undo.js.map +1 -1
  105. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  106. package/dist/feedback/feedback-submit-dialog.js +15 -3
  107. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  108. package/dist/feedback/feedback-submit.d.ts +30 -2
  109. package/dist/feedback/feedback-submit.js +5 -2
  110. package/dist/feedback/feedback-submit.js.map +1 -1
  111. package/dist/feedback/feedback-thread.d.ts +0 -9
  112. package/dist/feedback/feedback-thread.js.map +1 -1
  113. package/dist/feedback.d.ts +6 -40
  114. package/dist/feedback.js +88 -46
  115. package/dist/feedback.js.map +1 -1
  116. package/dist/hooks/use-hotkey.js +11 -1
  117. package/dist/hooks/use-hotkey.js.map +1 -1
  118. package/dist/i18n/defaults.js +0 -4
  119. package/dist/i18n/defaults.js.map +1 -1
  120. package/dist/i18n/german.js +5 -20
  121. package/dist/i18n/german.js.map +1 -1
  122. package/dist/i18n/kit-labels.d.ts +5 -14
  123. package/dist/i18n/kit-labels.js.map +1 -1
  124. package/dist/i18n/locales/en.js +5 -31
  125. package/dist/i18n/locales/en.js.map +3 -3
  126. package/dist/i18n/locales/es.js +5 -20
  127. package/dist/i18n/locales/es.js.map +1 -1
  128. package/dist/i18n/locales/fr.js +5 -20
  129. package/dist/i18n/locales/fr.js.map +1 -1
  130. package/dist/i18n/locales/hu.js +5 -20
  131. package/dist/i18n/locales/hu.js.map +1 -1
  132. package/dist/i18n/locales/it.js +5 -21
  133. package/dist/i18n/locales/it.js.map +1 -1
  134. package/dist/i18n/locales/zh.js +5 -20
  135. package/dist/i18n/locales/zh.js.map +1 -1
  136. package/dist/i18n/review.js +9 -4
  137. package/dist/i18n/review.js.map +1 -1
  138. package/dist/i18n/shared-labels.js +0 -1
  139. package/dist/i18n/shared-labels.js.map +1 -1
  140. package/dist/index.d.ts +18 -67
  141. package/dist/index.js +28 -103
  142. package/dist/index.js.map +1 -1
  143. package/dist/lib/dates.d.ts +7 -1
  144. package/dist/lib/dates.js +8 -1
  145. package/dist/lib/dates.js.map +1 -1
  146. package/dist/lib/format.d.ts +37 -2
  147. package/dist/lib/format.js +25 -1
  148. package/dist/lib/format.js.map +1 -1
  149. package/dist/lib/intl-cache.d.ts +4 -2
  150. package/dist/lib/intl-cache.js.map +1 -1
  151. package/dist/lib/translation-review.d.ts +26 -0
  152. package/dist/lib/translation-review.js +14 -0
  153. package/dist/lib/translation-review.js.map +1 -1
  154. package/dist/rhf/fields.d.ts +2 -2
  155. package/dist/rhf/fields.js.map +1 -1
  156. package/dist/search/search-index.d.ts +0 -6
  157. package/dist/search/search-index.js +0 -4
  158. package/dist/search/search-index.js.map +1 -1
  159. package/dist/search.d.ts +1 -8
  160. package/dist/search.js +2 -7
  161. package/dist/search.js.map +1 -1
  162. package/dist/shell/account-menu-language.d.ts +7 -21
  163. package/dist/shell/account-menu-language.js +1 -3
  164. package/dist/shell/account-menu-language.js.map +1 -1
  165. package/dist/shell/account-menu-text-size.d.ts +9 -23
  166. package/dist/shell/account-menu-text-size.js +2 -3
  167. package/dist/shell/account-menu-text-size.js.map +1 -1
  168. package/dist/shell/app-shell.d.ts +0 -10
  169. package/dist/shell/app-shell.js +1 -7
  170. package/dist/shell/app-shell.js.map +1 -1
  171. package/dist/shell/company-switcher.d.ts +1 -1
  172. package/dist/shell/company-switcher.js.map +1 -1
  173. package/dist/shell/dev-menu.d.ts +1 -1
  174. package/dist/shell/dev-menu.js.map +1 -1
  175. package/dist/shell/top-bar-parts-labels.d.ts +1 -1
  176. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  177. package/dist/shell.d.ts +7 -29
  178. package/dist/shell.js +12 -42
  179. package/dist/shell.js.map +1 -1
  180. package/dist/sync.d.ts +2 -6
  181. package/dist/sync.js +2 -15
  182. package/dist/sync.js.map +1 -1
  183. package/dist/theme/chart-palette.d.ts +0 -5
  184. package/dist/theme/chart-palette.js +0 -2
  185. package/dist/theme/chart-palette.js.map +1 -1
  186. package/package.json +1 -1
  187. package/src/account/delete-account-setting.tsx +3 -2
  188. package/src/auth/auth-errors.ts +69 -0
  189. package/src/billing/cancel-subscription.tsx +5 -16
  190. package/src/billing/plan-card.tsx +7 -24
  191. package/src/billing/plan-switch.tsx +5 -16
  192. package/src/chart.ts +62 -13
  193. package/src/components/amount-input.tsx +2 -1
  194. package/src/components/calendar-heatmap.tsx +8 -2
  195. package/src/components/currency-option-list.tsx +12 -4
  196. package/src/components/currency-select.tsx +3 -1
  197. package/src/components/danger-confirm.tsx +80 -27
  198. package/src/components/data-table-filter-popover.tsx +19 -11
  199. package/src/components/data-table-filters.ts +36 -8
  200. package/src/components/date-field.tsx +1 -1
  201. package/src/components/date-picker.tsx +0 -1
  202. package/src/components/date-range-picker.tsx +6 -22
  203. package/src/components/dialog-frame.tsx +12 -4
  204. package/src/components/mini-calendar.tsx +5 -2
  205. package/src/components/modal.tsx +36 -7
  206. package/src/components/nav-pills.tsx +1 -1
  207. package/src/components/number-input.tsx +4 -3
  208. package/src/components/series-chart-axes.ts +6 -1
  209. package/src/components/series-chart.tsx +78 -21
  210. package/src/components/stat-tile.tsx +32 -3
  211. package/src/components/treemap-labels.ts +11 -3
  212. package/src/components/treemap.tsx +65 -12
  213. package/src/currencies.ts +23 -3
  214. package/src/data-table.ts +1 -11
  215. package/src/demo/demo-countdown.ts +3 -8
  216. package/src/feedback/feedback-context-labels.ts +1 -0
  217. package/src/feedback/feedback-context.tsx +34 -2
  218. package/src/feedback/feedback-diagnostics.tsx +41 -0
  219. package/src/feedback/feedback-dialog.tsx +16 -4
  220. package/src/feedback/feedback-mutation.ts +12 -33
  221. package/src/feedback/feedback-public-list.tsx +1 -7
  222. package/src/feedback/feedback-record.ts +5 -0
  223. package/src/feedback/feedback-row-detail-labels.ts +1 -8
  224. package/src/feedback/feedback-row-detail.tsx +21 -58
  225. package/src/feedback/feedback-status-undo.ts +4 -18
  226. package/src/feedback/feedback-submit-dialog.tsx +15 -2
  227. package/src/feedback/feedback-submit.tsx +36 -3
  228. package/src/feedback/feedback-thread.tsx +0 -10
  229. package/src/feedback.ts +89 -48
  230. package/src/hooks/use-hotkey.ts +24 -1
  231. package/src/i18n/defaults.ts +0 -4
  232. package/src/i18n/german.ts +4 -19
  233. package/src/i18n/kit-labels.tsx +5 -14
  234. package/src/i18n/locales/es.ts +4 -19
  235. package/src/i18n/locales/fr.ts +4 -19
  236. package/src/i18n/locales/hu.ts +4 -19
  237. package/src/i18n/locales/it.ts +4 -20
  238. package/src/i18n/locales/zh.ts +4 -19
  239. package/src/i18n/review.ts +9 -4
  240. package/src/i18n/shared-labels.ts +0 -1
  241. package/src/index.ts +44 -114
  242. package/src/lib/dates.ts +24 -2
  243. package/src/lib/format.ts +66 -3
  244. package/src/lib/intl-cache.ts +4 -2
  245. package/src/lib/translation-review.ts +52 -0
  246. package/src/rhf/fields.tsx +2 -2
  247. package/src/search/search-index.ts +0 -15
  248. package/src/search.ts +8 -8
  249. package/src/shell/account-menu-language.tsx +7 -27
  250. package/src/shell/account-menu-text-size.tsx +13 -32
  251. package/src/shell/app-shell.tsx +0 -13
  252. package/src/shell/company-switcher.tsx +1 -1
  253. package/src/shell/dev-menu.tsx +1 -1
  254. package/src/shell/top-bar-parts-labels.ts +1 -1
  255. package/src/shell.ts +17 -44
  256. package/src/sync.ts +2 -15
  257. package/src/theme/chart-palette.ts +0 -2
  258. package/tokens.css +4 -4
  259. package/dist/billing/dialog-close.d.ts +0 -29
  260. package/dist/billing/dialog-close.js +0 -8
  261. package/dist/billing/dialog-close.js.map +0 -1
  262. package/dist/components/field-sync-labels.d.ts +0 -3
  263. package/dist/components/field-sync-labels.js +0 -12
  264. package/dist/components/field-sync-labels.js.map +0 -1
  265. package/dist/components/field-sync.d.ts +0 -186
  266. package/dist/components/field-sync.js +0 -269
  267. package/dist/components/field-sync.js.map +0 -1
  268. package/dist/components/section-nav.d.ts +0 -44
  269. package/dist/components/section-nav.js +0 -33
  270. package/dist/components/section-nav.js.map +0 -1
  271. package/dist/components/sign-chip-labels.d.ts +0 -3
  272. package/dist/components/sign-chip-labels.js +0 -11
  273. package/dist/components/sign-chip-labels.js.map +0 -1
  274. package/dist/components/sign-chip.d.ts +0 -77
  275. package/dist/components/sign-chip.js +0 -47
  276. package/dist/components/sign-chip.js.map +0 -1
  277. package/dist/lib/isin.d.ts +0 -38
  278. package/dist/lib/isin.js +0 -36
  279. package/dist/lib/isin.js.map +0 -1
  280. package/dist/shell/role-switcher.d.ts +0 -28
  281. package/dist/shell/role-switcher.js +0 -31
  282. package/dist/shell/role-switcher.js.map +0 -1
  283. package/src/billing/dialog-close.ts +0 -34
  284. package/src/components/field-sync-labels.ts +0 -14
  285. package/src/components/field-sync.tsx +0 -504
  286. package/src/components/section-nav.tsx +0 -72
  287. package/src/components/sign-chip-labels.ts +0 -13
  288. package/src/components/sign-chip.tsx +0 -133
  289. package/src/lib/isin.ts +0 -72
  290. package/src/shell/role-switcher.tsx +0 -53
package/src/index.ts CHANGED
@@ -93,6 +93,10 @@ export {
93
93
  reviewWrite,
94
94
  mergeReviews,
95
95
  dropReviews,
96
+ // 0.54: a save or a reset of any size under server-kit's cap of 2000 (kastlan reuse
97
+ // audit 2026-11-02, proposal 5).
98
+ saveReviewsInChunks,
99
+ clearReviewsInChunks,
96
100
  translationCorrections,
97
101
  // 0.25: grouping and the approvals' Undo (keksdose live #377).
98
102
  groupTranslationRows,
@@ -122,13 +126,11 @@ export type {
122
126
  TranslationReviewSwipeAction,
123
127
  TranslationReviewSwipeBinding,
124
128
  } from "./lib/translation-review";
125
- // 0.22.0, the inputs round: the country list and the IBAN, ISIN and phone helpers that
129
+ // 0.22.0, the inputs round: the country list and the IBAN and phone helpers that
126
130
  // CountrySelect, IbanInput and PhoneInput are built on.
127
131
  export { COUNTRY_CODES, countryName } from "./lib/countries";
128
132
  export { IBAN_LENGTHS, compactIban, formatIban, ibanCheckDigits, ibanProblem, isValidIban, isQrIban } from "./lib/iban";
129
133
  export type { IbanKind, IbanProblem } from "./lib/iban";
130
- // 0.52: deprecated, removed in 0.53 (MOD-5; tagged in lib/isin).
131
- export { formatIsin, isinCheckDigit, isValidIsin } from "./lib/isin";
132
134
  export { PHONE_DIAL_CODES, PHONE_COUNTRIES, parsePhone, formatNationalPhone, formatPhone, isE164 } from "./lib/phone";
133
135
  export type { PhoneCountryCode, PhoneCountry, ParsedPhone } from "./lib/phone";
134
136
  // 0.23.0: ColumnMapper's pure half (Kurvenschmiede's columns input, keksdose's import map).
@@ -177,14 +179,9 @@ export type { LongPressOptions, LongPressResult } from "./hooks/use-long-press";
177
179
  export { useNoIndex } from "./hooks/use-noindex";
178
180
  // 0.49 KIT-9: the window's title from page, section and app, in the provider's format.
179
181
  export { useDocumentTitle } from "./hooks/use-document-title";
180
- // 0.49 KIT-1b: U+2212 before a number as "-", the copy handler's rule, for an app's text.
181
- // 0.52: deprecated with the other 54 names no app, test or showcase page reads (refactor
182
- // audit 2026-10-30, API-4, decision 4). Each is tagged on its specifier here, not on its
183
- // declaration: the module keeps it for the kit's own use, only the barrel lets it go.
184
- export {
185
- /** @deprecated 0.52 — a formatter's `minus: "ascii"`, where the figure is made (API-4); removed in 0.53 */
186
- asciiMinusInNumbers,
187
- } from "./lib/clipboard-minus";
182
+ // 0.53: the 55 names no app reads left the entry points (deprecated in 0.52, refactor audit
183
+ // 2026-10-30, API-4, decision 4) — here 0.49 KIT-1b's `asciiMinusInNumbers`, the copy
184
+ // handler's rule, among them. Their modules keep the ones the kit itself uses.
188
185
  export { DEFAULT_DOCUMENT_TITLE_FORMAT, formatDocumentTitle } from "./lib/document-title";
189
186
  export type { DocumentTitleParts } from "./lib/document-title";
190
187
  export type { SwipeStage, RowSwipeOptions, RowSwipeReturn } from "./hooks/use-row-swipe";
@@ -344,11 +341,6 @@ export * from "./components/multi-select";
344
341
  export * from "./components/tooltip";
345
342
  export * from "./components/user-avatar";
346
343
  export * from "./components/settings-fields";
347
- // Sync state for a database-backed field: the engine (`useFieldSync`) and the
348
- // affordance (`FieldSyncIndicator`), kept separate because the state is useful
349
- // without the dot — a form can gate its submit on any field being `pending`.
350
- // 0.52: deprecated as a whole, removed in 0.53 (MOD-5; tagged in the module).
351
- export * from "./components/field-sync";
352
344
  export * from "./components/month-picker";
353
345
  export * from "./components/checkbox";
354
346
  export * from "./components/switch";
@@ -422,9 +414,6 @@ export * from "./components/alert-banner";
422
414
  export * from "./components/toggle-group";
423
415
  // A wrapping set of links/buttons marked `aria-current` — Tabs-like looks, not tabs.
424
416
  export * from "./components/nav-pills";
425
- // 0.49.0 (KIT-6): a section's pages on the phone, one scrolling row of pills. 0.52:
426
- // deprecated, removed in 0.53 (MOD-5; tagged in the module).
427
- export * from "./components/section-nav";
428
417
  // A pill carrying one VALUE — inert, a link, or a toggle, depending on which prop it is
429
418
  // given — and the list field built from it. Distinct from Button on purpose: a row of
430
419
  // buttons reads as "choose an action", a row of chips as "here are the things".
@@ -441,10 +430,15 @@ export * from "./components/full-bleed-dialog";
441
430
  export * from "./components/grouped-picker";
442
431
  export * from "./components/file-dropzone";
443
432
  export * from "./components/mini-calendar";
444
- export * from "./components/calendar-heatmap";
433
+ // Named, not `*`: `heatmapLevel` is CalendarHeatmap's own since 0.53 (API-4).
445
434
  export {
446
- /** @deprecated 0.52 — `CalendarHeatmap`, which steps its cells with it (API-4); removed in 0.53 */
447
- heatmapLevel,
435
+ CalendarHeatmap,
436
+ DEFAULT_CALENDAR_HEATMAP_LABELS,
437
+ heatmapWindowEnd,
438
+ type CalendarHeatmapLabels,
439
+ type CalendarHeatmapDatum,
440
+ type CalendarHeatmapDay,
441
+ type CalendarHeatmapProps,
448
442
  } from "./components/calendar-heatmap";
449
443
  export * from "./components/date-picker";
450
444
  export * from "./components/tree-view";
@@ -465,13 +459,9 @@ export {
465
459
  COMMIT_EXCEPT_BILLING,
466
460
  DEFAULT_WRITE_LOCK_LABELS,
467
461
  } from "./components/write-lock";
468
- // 0.46 (kastlan K4): the offline lock kind, its scope and its source.
469
- export {
470
- /** @deprecated 0.52 — the kind itself, `"offline"`, which `useOfflineWriteLock` holds (API-4); removed in 0.53 */
471
- OFFLINE_LOCK,
472
- COMMIT_EXCEPT_OFFLINE,
473
- useOfflineWriteLock,
474
- } from "./components/write-lock";
462
+ // 0.46 (kastlan K4): the offline lock's scope and its source (the kind, `OFFLINE_LOCK`, left
463
+ // the surface in 0.53: it is the string "offline").
464
+ export { COMMIT_EXCEPT_OFFLINE, useOfflineWriteLock } from "./components/write-lock";
475
465
  export type {
476
466
  WriteLock,
477
467
  WriteLockLabels,
@@ -535,10 +525,12 @@ export * from "./components/error-boundary";
535
525
  export * from "./components/copy-button";
536
526
  export { AuthedImage, DEFAULT_AUTHED_IMAGE_LABELS } from "./components/authed-image";
537
527
  export type { AuthedImageProps, AuthedImageLabels } from "./components/authed-image";
538
- export * from "./components/image-grid";
528
+ // Named, not `*`: `splitFileName` is ImageGrid's own since 0.53 (API-4).
539
529
  export {
540
- /** @deprecated 0.52 — `ImageGrid`, which shortens its file names with it (API-4); removed in 0.53 */
541
- splitFileName,
530
+ ImageGrid,
531
+ DEFAULT_IMAGE_GRID_LABELS,
532
+ type ImageGridLabels,
533
+ type ImageGridProps,
542
534
  } from "./components/image-grid";
543
535
  export * from "./components/lightbox";
544
536
  export * from "./components/button-group";
@@ -567,17 +559,7 @@ export type {
567
559
  // The toast layer over sonner (an OPTIONAL peer, loaded lazily): `toast` mirrors sonner's
568
560
  // API so apps migrate by swapping the import; `<Toaster>` carries the placement, theme,
569
561
  // tones and z-index both apps had wired by hand.
570
- export {
571
- toast,
572
- Toaster,
573
- DEFAULT_TOAST_LABELS,
574
- /** @deprecated 0.52 — `toast()`, which times a toast with an action by it (API-4); removed in 0.53 */
575
- TOAST_ACTION_DURATION,
576
- /** @deprecated 0.52 — `<Toaster>`, whose default `offset` it is (API-4); removed in 0.53 */
577
- TOASTER_OFFSET_TOP,
578
- /** @deprecated 0.52 — `<Toaster>`, whose default `offset` it is (API-4); removed in 0.53 */
579
- TOASTER_OFFSET_BOTTOM,
580
- } from "./components/toast";
562
+ export { toast, Toaster, DEFAULT_TOAST_LABELS } from "./components/toast";
581
563
  export type {
582
564
  ToastId,
583
565
  ToastAction,
@@ -682,8 +664,6 @@ export {
682
664
  // 0.47.0 (keksdose N17): a hand-built table's actions heading and cell.
683
665
  RowActionsHead,
684
666
  ROW_ACTIONS_CELL_CLASS,
685
- /** @deprecated 0.52 — `RowActionsHead`, which draws it (API-4); removed in 0.53 */
686
- ROW_ACTIONS_HEAD_CLASS,
687
667
  } from "./components/row-actions";
688
668
  export type {
689
669
  RowAction,
@@ -704,8 +684,6 @@ export * from "./components/one-time-code-input";
704
684
  export * from "./components/language-select";
705
685
  export * from "./components/tile-radio";
706
686
  export * from "./components/inline-edit-field";
707
- // 0.52: deprecated, removed in 0.53 (MOD-5; tagged in the module).
708
- export * from "./components/sign-chip";
709
687
  export * from "./components/country-select";
710
688
  export * from "./components/iban-input";
711
689
  export * from "./components/phone-input";
@@ -787,7 +765,9 @@ export type { RegisterFormProps, RegisterLabels, RegisterValues } from "./auth/r
787
765
  export { CompleteNameDialog, DEFAULT_COMPLETE_NAME_LABELS } from "./auth/complete-name-dialog";
788
766
  export type { CompleteNameDialogProps, CompleteNameLabels, CompleteNameValues } from "./auth/complete-name-dialog";
789
767
  export { taggedEmail } from "./auth/email-tag";
790
- export { isAuthError, authErrorCode, isBillingError } from "./auth/auth-errors";
768
+ // 0.54 (kastlan reuse audit 2026-11-02, proposal 4): `billingRefusalText`, a billing
769
+ // refusal's words, which every app mapped from the code by hand.
770
+ export { isAuthError, authErrorCode, isBillingError, billingRefusalText } from "./auth/auth-errors";
791
771
  export type { AuthErrorCode } from "./auth/auth-errors";
792
772
  export { ForgotPasswordForm, DEFAULT_FORGOT_PASSWORD_LABELS } from "./auth/forgot-password-form";
793
773
  export type { ForgotPasswordFormProps, ForgotPasswordLabels } from "./auth/forgot-password-form";
@@ -819,7 +799,13 @@ export type { AuthHeadingLevel } from "./auth/status-parts";
819
799
  // 0.30.0: a rate limit and its wait, and the codes the account, billing and demo calls
820
800
  // answer with.
821
801
  export { isRateLimited, retryAfterSeconds } from "./auth/auth-errors";
822
- export type { AccountErrorCode, BillingErrorCode, DemoErrorCode, KitErrorCode } from "./auth/auth-errors";
802
+ export type {
803
+ AccountErrorCode,
804
+ BillingErrorCode,
805
+ BillingRefusalTextOptions,
806
+ DemoErrorCode,
807
+ KitErrorCode,
808
+ } from "./auth/auth-errors";
823
809
 
824
810
  // ── account ──────────────────────────────────────────────────────────────────
825
811
  // In account/ since 0.34, beside the other account cards (MOD-8, refactor audit 2026-10):
@@ -859,13 +845,9 @@ export type { DataExportSettingProps, DataExportLabels, DataExportFile } from ".
859
845
  // 0.30.0 (docs/user-admin-harmonization.md): the admin user list and its actions, and the
860
846
  // invitations panel. Callbacks only; the confirmation level is the server's.
861
847
  export {
862
- /** @deprecated 0.52 — `useUserRosterColumns`, the same columns in the provider's words (API-4); removed in 0.53 */
863
- userRosterColumns,
864
848
  useUserRosterColumns,
865
849
  UserIdentityCell,
866
850
  UserRowActions,
867
- /** @deprecated 0.52 — `useUserRosterColumns`, whose state cell reads it (API-4); removed in 0.53 */
868
- adminUserStates,
869
851
  userRosterSort,
870
852
  USER_ROSTER_SORT_KEYS,
871
853
  DEFAULT_USER_ROSTER_LABELS,
@@ -945,13 +927,7 @@ export { adminSectionOrder } from "./admin/admin-sections";
945
927
  export type { AdminSectionPlace, AdminSectionOrderOptions, AdminKeptSection } from "./admin/admin-sections";
946
928
  export { AdminForbidden } from "./admin/admin-forbidden";
947
929
  export type { AdminForbiddenProps } from "./admin/admin-forbidden";
948
- export {
949
- AdminOverview,
950
- AttentionTile,
951
- adminWaitingCount,
952
- /** @deprecated 0.52 — `AdminOverview`, which links its tiles there unless told otherwise (API-4); removed in 0.53 */
953
- DEFAULT_ADMIN_ATTENTION_HREFS,
954
- } from "./admin/admin-overview";
930
+ export { AdminOverview, AttentionTile, adminWaitingCount } from "./admin/admin-overview";
955
931
  export type {
956
932
  AdminAttention,
957
933
  AdminAttentionTile,
@@ -966,23 +942,10 @@ export type {
966
942
  export { AdminToolLinks } from "./admin/admin-tool-links";
967
943
  export type { AdminToolLink, AdminToolLinksProps } from "./admin/admin-tool-links";
968
944
  // 0.45.0 (docs/admin-harmonization.md §9.2.4): "Send a test mail to me".
969
- export {
970
- MailTestCard,
971
- DEFAULT_MAIL_TEST_CARD_LABELS,
972
- MAIL_TEST_ANCHOR,
973
- /** @deprecated 0.52 — `MailTestCard`, which masks the address itself (API-4); removed in 0.53 */
974
- maskMailAddress,
975
- } from "./admin/mail-test-card";
945
+ export { MailTestCard, DEFAULT_MAIL_TEST_CARD_LABELS, MAIL_TEST_ANCHOR } from "./admin/mail-test-card";
976
946
  export type { MailTestCardProps, MailTestCardLabels, MailTestAnswer } from "./admin/mail-test-card";
977
947
  // 0.33.0: the operator's plan parts (docs/billing-harmonization.md §14.12).
978
- export {
979
- /** @deprecated 0.52 — `usePlanColumn`, the same column in the provider's words (API-4); removed in 0.53 */
980
- planColumn,
981
- usePlanColumn,
982
- PlanChangeConfirm,
983
- usePlanChangeResult,
984
- DEFAULT_PLAN_CHANGE_LABELS,
985
- } from "./admin/plan-change";
948
+ export { usePlanColumn, PlanChangeConfirm, usePlanChangeResult, DEFAULT_PLAN_CHANGE_LABELS } from "./admin/plan-change";
986
949
  export type {
987
950
  PlanChangeLabels,
988
951
  PlanColumnOptions,
@@ -1007,13 +970,7 @@ export type {
1007
970
  PlanPrices,
1008
971
  PlanPriceAnswer,
1009
972
  } from "./billing/plan-price";
1010
- export {
1011
- PlanCard,
1012
- PlanPicker,
1013
- GrantWarningConfirm,
1014
- /** @deprecated 0.52 — `PlanPicker`, which marks each card's relation itself (API-4); removed in 0.53 */
1015
- planRelation,
1016
- } from "./billing/plan-card";
973
+ export { PlanCard, PlanPicker, GrantWarningConfirm } from "./billing/plan-card";
1017
974
  export type {
1018
975
  BillingPlan,
1019
976
  PlanRelation,
@@ -1047,21 +1004,10 @@ export {
1047
1004
  withoutCheckoutReturn,
1048
1005
  checkoutReturnUrl,
1049
1006
  } from "./billing/checkout-return";
1050
- export {
1051
- checkoutFingerprint,
1052
- /** @deprecated 0.52 — `useCheckoutProcessing`, which asks it (API-4); removed in 0.53 */
1053
- checkoutLanded,
1054
- noteCheckoutStarted,
1055
- useCheckoutProcessing,
1056
- } from "./billing/checkout-processing";
1007
+ export { checkoutFingerprint, noteCheckoutStarted, useCheckoutProcessing } from "./billing/checkout-processing";
1057
1008
  export type { CheckoutOverview, CheckoutProcessingOptions } from "./billing/checkout-processing";
1058
1009
  // The link to the pay page and Paddle's locale (§14.4); the page itself ships as dist/pay/.
1059
- export {
1060
- /** @deprecated 0.52 — `payPageUrl`, which writes it (API-4); removed in 0.53 */
1061
- PAY_PAGE_LANG_PARAM,
1062
- paddleLocale,
1063
- payPageUrl,
1064
- } from "./billing/pay-page";
1010
+ export { paddleLocale, payPageUrl } from "./billing/pay-page";
1065
1011
  export type { PaddleEnvironment } from "./billing/pay-page";
1066
1012
  // 0.39 (§15.2.6): the apps every pay page serves, by their `billing_app` tag.
1067
1013
  export { PAY_PAGE_APPS } from "./billing/pay-page";
@@ -1111,11 +1057,7 @@ export type {
1111
1057
  ResolveAccountLanguageInput,
1112
1058
  ResolvedAccountLanguage,
1113
1059
  } from "./settings/use-account-language";
1114
- export {
1115
- useAccountAppearance,
1116
- /** @deprecated 0.52 — `useAccountAppearance`, which resolves the same three values (API-4); removed in 0.53 */
1117
- resolveAccountAppearance,
1118
- } from "./settings/use-account-appearance";
1060
+ export { useAccountAppearance } from "./settings/use-account-appearance";
1119
1061
  export type {
1120
1062
  AppearanceSource,
1121
1063
  AccountAppearanceFields,
@@ -1192,11 +1134,7 @@ export type {
1192
1134
  } from "./notifications/push-worker";
1193
1135
  export { PushDeviceSetting } from "./notifications/push-device-setting";
1194
1136
  export type { PushDeviceSettingProps, PushDeviceAction } from "./notifications/push-device-setting";
1195
- export {
1196
- PushDevicesSetting,
1197
- /** @deprecated 0.52 — `PushDevicesSetting`, which names each device with it (API-4); removed in 0.53 */
1198
- describeUserAgent,
1199
- } from "./notifications/push-devices-setting";
1137
+ export { PushDevicesSetting } from "./notifications/push-devices-setting";
1200
1138
  export type { PushDevicesSettingProps } from "./notifications/push-devices-setting";
1201
1139
  export { UnsubscribePanel, unsubscribeTokenType } from "./notifications/unsubscribe-panel";
1202
1140
  export type { UnsubscribePanelProps } from "./notifications/unsubscribe-panel";
@@ -1243,15 +1181,7 @@ export { DEFAULT_DEMO_LABELS, useDemoLabels } from "./demo/demo-labels";
1243
1181
  export type { DemoLabels } from "./demo/demo-labels";
1244
1182
  export { isDemoSession } from "./demo/demo-session";
1245
1183
  export type { DemoModel, DemoSessionUser } from "./demo/demo-session";
1246
- export {
1247
- useDemoCountdown,
1248
- /** @deprecated 0.52 — `useDemoCountdown`, the same countdown; its `now` option for a test (API-4); removed in 0.53 */
1249
- demoCountdown,
1250
- /** @deprecated 0.52 — `useDemoCountdown`, whose `warning` turns on then (API-4); removed in 0.53 */
1251
- DEMO_WARNING_MINUTES,
1252
- /** @deprecated 0.52 — `useDemoCountdown`, whose `lastHour` turns on then (API-4); removed in 0.53 */
1253
- DEMO_MINUTES_ONLY,
1254
- } from "./demo/demo-countdown";
1184
+ export { useDemoCountdown } from "./demo/demo-countdown";
1255
1185
  export type { DemoCountdown, DemoCountdownOptions, DemoExpiry } from "./demo/demo-countdown";
1256
1186
  export { DemoStart } from "./demo/demo-start";
1257
1187
  export type { DemoStartProps } from "./demo/demo-start";
package/src/lib/dates.ts CHANGED
@@ -24,6 +24,9 @@
24
24
  * all, so sending it the user's local day is also what it wants.
25
25
  */
26
26
 
27
+ // Imports nothing else: `@eifi1/ui-kit/dates` reaches no package (packaging-contract).
28
+ import { dateFormatter } from "./intl-cache";
29
+
27
30
  /** Zero-pad a number to two digits ("3" -> "03"). */
28
31
  export function pad(n: number): string {
29
32
  return String(n).padStart(2, "0");
@@ -124,14 +127,33 @@ export function parseIsoDate(s: string): Date | null {
124
127
  return dt;
125
128
  }
126
129
 
130
+ /** The fields that make an option set a DATE for `toLocaleDateString` (ECMA-402's
131
+ * ToDateTimeOptions, required "date"): with none of them it adds year, month and day. */
132
+ const DATE_FIELDS = ["weekday", "year", "month", "day", "dateStyle"] as const;
133
+ const NUMERIC_DATE: Intl.DateTimeFormatOptions = { year: "numeric", month: "numeric", day: "numeric" };
134
+
135
+ /** `options` as `toLocaleDateString` reads them: with a date in them, whatever else they
136
+ * ask for. `{ hour: "numeric" }` alone is a date and an hour there, never the hour alone
137
+ * that `Intl.DateTimeFormat` would print for it. */
138
+ function asDateOptions(options: Intl.DateTimeFormatOptions | undefined): Intl.DateTimeFormatOptions {
139
+ if (!options) return NUMERIC_DATE;
140
+ return DATE_FIELDS.some((field) => options[field] !== undefined) ? options : { ...options, ...NUMERIC_DATE };
141
+ }
142
+
127
143
  /** Format a "YYYY-MM-DD" string for display in `locale` (empty string when
128
144
  * unparseable). Defaults to the locale's short numeric date; pass `options` to
129
145
  * override (e.g. `{ dateStyle: "medium" }`). `locale` may be `undefined` since 0.34 —
130
146
  * the runtime's default, as `Intl` reads it and as every other kit formatter takes it
131
- * (DUP-11: DatePicker re-wrapped this function only to pass none). */
147
+ * (DUP-11: DatePicker re-wrapped this function only to pass none).
148
+ *
149
+ * Through the kit's formatter cache since 0.54 (keksdose audit follow-up, 2026-11-01):
150
+ * `toLocaleDateString` builds a formatter per call, and this one runs for the date
151
+ * pickers' trigger on every render and for an app's date column once per row. The output
152
+ * is `toLocaleDateString`'s: the same options, with the date it adds when they name none
153
+ * (`asDateOptions`). */
132
154
  export function formatIsoDate(iso: string, locale: string | undefined, options?: Intl.DateTimeFormatOptions): string {
133
155
  const d = parseIsoDate(iso);
134
- return d ? d.toLocaleDateString(locale, options) : "";
156
+ return d ? dateFormatter(locale, asDateOptions(options)).format(d) : "";
135
157
  }
136
158
 
137
159
  /** A "YYYY-MM-DD" string shifted by `days` (negative shifts back), or the input
package/src/lib/format.ts CHANGED
@@ -11,8 +11,8 @@ export type { MinusStyle } from "./minus";
11
11
  export { formatWithMinus } from "./minus";
12
12
 
13
13
  /**
14
- * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,
15
- * for the figures an app prints OUTSIDE a kit component.
14
+ * Number, money, percentage, duration (0.54), date and relative-time formatting — `Intl`,
15
+ * in one place, for the figures an app prints OUTSIDE a kit component.
16
16
  *
17
17
  * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned
18
18
  * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`
@@ -99,6 +99,19 @@ export interface FormatMoneyOptions extends FormatNumberBaseOptions {
99
99
  currencyDisplay?: Intl.NumberFormatOptions["currencyDisplay"];
100
100
  }
101
101
 
102
+ export interface FormatDurationOptions extends BaseOptions {
103
+ /**
104
+ * How the units are written — `Intl.NumberFormat`'s `unitDisplay`, in `locale`:
105
+ * - `"narrow"` (default): "3m 12s", "3 Min. 12 Sek." (de-CH), "3min 12s" (fr) —
106
+ * keksdose's scan readout;
107
+ * - `"short"`: "3 min 12 sec", "3 Min. 12 Sek.", "3 min 12 s";
108
+ * - `"long"`: "3 minutes 12 seconds", "3 Minuten 12 Sekunden", "3 minutes 12 secondes".
109
+ */
110
+ unitDisplay?: "narrow" | "short" | "long";
111
+ /** The minus of a negative duration — {@link FormatNumberBaseOptions.minus}. */
112
+ minus?: MinusStyle;
113
+ }
114
+
102
115
  export interface FormatPercentOptions extends FormatNumberBaseOptions {
103
116
  /**
104
117
  * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read
@@ -162,7 +175,7 @@ function devWarnings(): boolean {
162
175
  const warnedSites = new Set<string>();
163
176
  /** This module's own frames, skipped to find the caller ("Object.formatMoney" is a
164
177
  * `useKitFormat` wrapper). */
165
- const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\b/;
178
+ const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|formatDuration|toFormattable|warnInvalid|callSite)\b/;
166
179
 
167
180
  /** The first stack frame outside the formatters: where the bad value was passed. */
168
181
  function callSite(): string {
@@ -248,6 +261,53 @@ export function formatPercent(value: FormattableNumber, options: FormatPercentOp
248
261
  return formatWithMinus(formatter, ratio, options.minus);
249
262
  }
250
263
 
264
+ /** The units a duration is written in, largest first, with their length in seconds. */
265
+ const DURATION_UNITS = [
266
+ ["hour", 3600],
267
+ ["minute", 60],
268
+ ["second", 1],
269
+ ] as const;
270
+
271
+ /**
272
+ * A number of SECONDS as an elapsed time in `locale`: "42s", "3m 12s", "1h 0m 5s" — and in
273
+ * German "3 Min. 12 Sek.", in French "3min 12s" (keksdose audit follow-up, 2026-11-01:
274
+ * keksdose's `formatElapsed`, the scan card's readout, moves here). Each unit is
275
+ * `Intl.NumberFormat`'s `style: "unit"`, so the digits and the unit's spelling follow
276
+ * `locale`; `unitDisplay` picks the form, narrow by default.
277
+ *
278
+ * - Seconds alone under a minute ("0s", "59s"); minutes and seconds under an hour ("1m 0s",
279
+ * "59m 59s"); from an hour on, hours, minutes and seconds ("1h 0m 0s"). Hours are the
280
+ * largest unit: a day and a bit is "27h 46m 40s". Every unit below the first is printed,
281
+ * zero too, so a ticking readout keeps its shape from one second to the next.
282
+ * - Whole seconds, cut toward zero: a timer reads "59s" until the minute is full.
283
+ * - The parts are joined by a space, as the readout always was — not `Intl.ListFormat`'s
284
+ * "3 Min. und 12 Sek.", which is a sentence rather than a figure.
285
+ * - Negative: the minus on the first unit, as `Intl.DurationFormat` writes one — "−3m 12s",
286
+ * with the kit's minus (`minus`). An elapsed time from a server's clock can come out a
287
+ * second below zero; clamp it before, as keksdose's card does, when that is not news.
288
+ * - `null` / `undefined` print `empty`; `NaN`, ±`Infinity` and a non-numeric string too,
289
+ * with the formatters' development warning. A numeric string ("185") is a number.
290
+ */
291
+ export function formatDuration(seconds: FormattableNumber, options: FormatDurationOptions = {}): string {
292
+ const n = toFormattable("formatDuration", seconds);
293
+ if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;
294
+ const unitDisplay = options.unitDisplay ?? "narrow";
295
+ const negative = n < 0;
296
+ let rest = Math.trunc(Math.abs(n));
297
+ const parts: string[] = [];
298
+ for (const [unit, size] of DURATION_UNITS) {
299
+ const count = Math.floor(rest / size);
300
+ rest -= count * size;
301
+ // Units above the first that has a count are left out; seconds always print.
302
+ if (parts.length === 0 && count === 0 && unit !== "second") continue;
303
+ const formatter = numberFormatter(options.locale, { style: "unit", unit, unitDisplay });
304
+ // `count > 0`: under a second either way is "0s", with no sign on it.
305
+ const signed = negative && parts.length === 0 && count > 0 ? -count : count;
306
+ parts.push(formatWithMinus(formatter, signed, options.minus));
307
+ }
308
+ return parts.join(" ");
309
+ }
310
+
251
311
  /* ── Dates ─────────────────────────────────────────────────────────────────── */
252
312
 
253
313
  /**
@@ -436,6 +496,8 @@ export interface KitFormat {
436
496
  formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;
437
497
  formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;
438
498
  formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;
499
+ /** 0.54: {@link formatDuration}, a number of seconds as an elapsed time. */
500
+ formatDuration: (seconds: FormattableNumber, options?: WithoutLocale<FormatDurationOptions>) => string;
439
501
  formatDate: (
440
502
  value: DateInput,
441
503
  style?: FormatDateStyle | Intl.DateTimeFormatOptions,
@@ -462,6 +524,7 @@ export function useKitFormat(locale?: string): KitFormat {
462
524
  formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),
463
525
  formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),
464
526
  formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),
527
+ formatDuration: (seconds, options) => formatDuration(seconds, { locale: kitLocale, ...options }),
465
528
  formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),
466
529
  formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),
467
530
  }),
@@ -4,10 +4,12 @@
4
4
  * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on
5
5
  * a 270-row register when each cell built its own (dev#632), and kept its own renderer of
6
6
  * the date-format preference because the kit's built one per call (refactor audit
7
- * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one.
7
+ * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one, and
8
+ * since 0.54 `lib/dates`' `formatIsoDate` (keksdose audit follow-up, 2026-11-01).
8
9
  *
9
10
  * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache
10
- * lives apart from it.
11
+ * lives apart from it. It imports nothing, which `@eifi1/ui-kit/dates` (no package at
12
+ * all) relies on.
11
13
  */
12
14
 
13
15
  const dateFormatters = new Map<string, Intl.DateTimeFormat>();
@@ -570,6 +570,58 @@ export function dropReviews<T extends TranslationReviewKey>(
570
570
  return prev.filter((r) => !gone.has(`${r.locale}|${r.key}`));
571
571
  }
572
572
 
573
+ /**
574
+ * How many verdicts one request carries. server-kit refuses more than 2000 in one
575
+ * `PUT /translations/reviews` or `POST /translations/reviews/clear` (`BATCH_MAX`, its
576
+ * translation_review/schemas.py); half of it, as each of the three apps sent. A whole area
577
+ * is rarely more than a few hundred, so most saves are one request; "Approve the
578
+ * unreviewed" over an app's every string is not.
579
+ */
580
+ const REVIEW_CHUNK = 1000;
581
+
582
+ /**
583
+ * A save of any size under server-kit's cap: `writes` in chunks of 1000, one request
584
+ * after the other, in their order, and every verdict the server answered with, in that
585
+ * order — to patch the page's cached list with {@link mergeReviews} once. `save` is the
586
+ * app's own request (`PUT /translations/reviews`), answering with the verdicts it wrote,
587
+ * in either spelling. No writes, no request: the server refuses an empty batch.
588
+ *
589
+ * A chunk that fails rejects with its error, and the chunks after it are not sent. What
590
+ * the chunks before it wrote stays written — the server has it — but is not handed back,
591
+ * so the page leaves its cache as it was and the panel shows the error; a second try
592
+ * writes those verdicts again, which changes nothing but the time on them.
593
+ *
594
+ * All three apps wrote this loop, with a `chunks` helper and `SAVE_CHUNK = 1000` of
595
+ * their own (kastlan reuse audit 2026-11-02, proposal 5):
596
+ *
597
+ * const written = await saveReviewsInChunks(writes.map(toApiWrite), (part) => translationsApi.save(part));
598
+ * patchCache((reviews) => mergeReviews(reviews, written));
599
+ */
600
+ export async function saveReviewsInChunks<W extends TranslationReviewKey, R extends TranslationReviewKey>(
601
+ writes: readonly W[],
602
+ save: (chunk: W[]) => Promise<readonly R[]>,
603
+ ): Promise<R[]> {
604
+ const written: R[] = [];
605
+ for (let at = 0; at < writes.length; at += REVIEW_CHUNK) {
606
+ written.push(...(await save(writes.slice(at, at + REVIEW_CHUNK))));
607
+ }
608
+ return written;
609
+ }
610
+
611
+ /**
612
+ * {@link saveReviewsInChunks} for a reset (`POST /translations/reviews/clear`): `keys` in
613
+ * chunks of 1000, one request after the other, and the keys themselves once every chunk
614
+ * is through — to drop from the cached list with {@link dropReviews} once. A failing
615
+ * chunk rejects as a save's does, and what the chunks before it cleared stays cleared.
616
+ */
617
+ export async function clearReviewsInChunks<K extends TranslationReviewKey>(
618
+ keys: readonly K[],
619
+ clear: (chunk: K[]) => Promise<unknown>,
620
+ ): Promise<K[]> {
621
+ for (let at = 0; at < keys.length; at += REVIEW_CHUNK) await clear(keys.slice(at, at + REVIEW_CHUNK));
622
+ return [...keys];
623
+ }
624
+
573
625
  // ── Swipe bindings ──────────────────────────────────────────────────────────────
574
626
 
575
627
  /**
@@ -899,8 +899,8 @@ export type RhfDateRangePickerProps<
899
899
  *
900
900
  * Without `apply` (the picker's default) the first calendar click is reported on its
901
901
  * own, so the form holds `(from, emptyValue)` until the second; with `apply` nothing
902
- * reaches the form until Apply. `apply` was `commit="apply"` until 0.52 (deprecated, gone
903
- * in 0.53): `commit` is now the write lock's, as on every other binding — with
902
+ * reaches the form until Apply. `apply` was `commit="apply"` until 0.52 (gone in 0.53):
903
+ * `commit` is now the write lock's, as on every other binding — with
904
904
  * `disabledReason`, the picker's own (refactor audit 2026-10-30, API-3).
905
905
  *
906
906
  * The common props (`label`, `hint`, `required`, `disabled`, `excludeWhenDisabled`,
@@ -281,18 +281,3 @@ function capByGroup<E extends SearchEntry>(hits: SearchHit<E>[], groupLimit: num
281
281
  }
282
282
  return out;
283
283
  }
284
-
285
- /**
286
- * One-shot form of {@link createSearchIndex}: the matching entries, best first. For a
287
- * list that changes on every call (a server response, a store selector); build an index
288
- * once when the entries are stable.
289
- */
290
- export function matchEntries<E extends SearchEntry>(
291
- entries: readonly E[],
292
- query: string,
293
- options?: SearchIndexOptions,
294
- ): E[] {
295
- return createSearchIndex(entries, options)
296
- .search(query)
297
- .map((hit) => hit.entry);
298
- }
package/src/search.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  // `@eifi1/ui-kit/search` — the command palette / global search.
2
2
  // A re-slicing of the main barrel, not a new API.
3
3
  export * from "./search/command-palette";
4
- export * from "./search/search-index";
5
- // 0.52: the slice's share of the 55 names no app reads (refactor audit 2026-10-30, API-4).
4
+ // Named, not `*`: 0.53 took `SEARCH_TIER_POINTS` and `normalizeSearchText` off the surface
5
+ // (deprecated in 0.52, refactor audit 2026-10-30, API-4); the index keeps them.
6
6
  export {
7
- /** @deprecated 0.52 — `createSearchIndex`, whose ranking tiers it spaces (API-4); removed in 0.53 */
8
- SEARCH_TIER_POINTS,
9
- /** @deprecated 0.52 — `createSearchIndex(entries).search(query)`, the same match (API-4); removed in 0.53 */
10
- matchEntries,
11
- /** @deprecated 0.52 — `foldForSearch`, the list fold it builds on (API-4); removed in 0.53 */
12
- normalizeSearchText,
7
+ createSearchIndex,
8
+ type SearchEntry,
9
+ type SearchMatchField,
10
+ type SearchHit,
11
+ type SearchIndexOptions,
12
+ type SearchIndex,
13
13
  } from "./search/search-index";
14
14
  export * from "./search/global-search";