@purposeinplay/payload-ai-translate 0.1.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 (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,97 @@
1
+ import type React from 'react';
2
+ import { type EditorErrorContext } from '../../lib/error-messages.js';
3
+ /**
4
+ * Plain-English error render component. Every editor-facing error site
5
+ * (AlertBanner, UsageTable expanded row, BulkRunsHub DrillDown failed-unit
6
+ * detail, FailureDrawer rows, action toasts) routes through this rather
7
+ * than dumping raw `err.message` text or internal codes like
8
+ * `permanent.config`.
9
+ *
10
+ * What it gives the editor:
11
+ * - A friendly title + body (from `editorMessageFor(code, context)`).
12
+ * - An optional action CTA — what to actually do next.
13
+ * - When `details` is non-empty: a collapsible "Show technical details"
14
+ * `<details>` element holding the raw provider / validator string.
15
+ * Collapsed by default; engineering can expand to debug.
16
+ * - When the code is a permanent failure (`bulk.permanent.*`,
17
+ * `cost-guard.*`): an inline expandable "Why won't this work?" block
18
+ * explaining the failure mode, and (if `onRetry` is supplied) a
19
+ * disabled retry button with the same explanation as its tooltip.
20
+ *
21
+ * What it explicitly avoids:
22
+ * - Showing the raw `code` string anywhere visible.
23
+ * - Showing the raw `details` string at the top level.
24
+ * - Encouraging a retry click on a permanent failure (which would just
25
+ * fail again with the same root cause).
26
+ */
27
+ type EditorErrorTone = 'error' | 'warning' | 'info' | 'muted';
28
+ export type EditorErrorProps = {
29
+ /**
30
+ * Editor-facing code from `lib/error-messages.ts`. Renders the catalogue
31
+ * message for it. When omitted or unknown the catalogue falls back to a
32
+ * generic "Something went wrong" message — never undefined/blank.
33
+ */
34
+ code: string | null | undefined;
35
+ /**
36
+ * Context for templating the code's message (count, locale, $spent,
37
+ * etc.). Each formatter in the catalogue uses what it needs and ignores
38
+ * the rest, so over-supplying is safe.
39
+ */
40
+ context?: EditorErrorContext;
41
+ /**
42
+ * Raw provider / validator / exception text. When non-empty, surfaces
43
+ * as a collapsible "Show technical details" disclosure under the body.
44
+ * Never rendered at the top level — engineering-only detail.
45
+ */
46
+ details?: string | null;
47
+ /**
48
+ * Visual severity. Default is inferred from the code: permanent
49
+ * failures + persistent alerts render as `error`, cost-guards and
50
+ * soft-skips as `warning`, markers (cancel/reset) as `muted`.
51
+ */
52
+ tone?: EditorErrorTone;
53
+ /**
54
+ * Compact layout for in-table / inline contexts (no banner background,
55
+ * no border, tighter spacing). The action CTA still renders. Use this
56
+ * inside an already-styled card (e.g. UsageTable expanded row).
57
+ */
58
+ compact?: boolean;
59
+ /**
60
+ * Hide the action CTA (e.g. when the surrounding component already
61
+ * provides its own Retry / Open Document button and the CTA copy
62
+ * would duplicate it).
63
+ */
64
+ hideAction?: boolean;
65
+ /**
66
+ * Optional retry callback. When supplied:
67
+ * - For non-permanent codes: renders an enabled "Try again" button
68
+ * that calls this callback.
69
+ * - For permanent codes: renders the button disabled with the
70
+ * "Why won't this work?" explanation as tooltip + inline block.
71
+ * When omitted, no retry surface renders.
72
+ */
73
+ onRetry?: () => void;
74
+ /** Loading state for the retry button. */
75
+ retryPending?: boolean;
76
+ /**
77
+ * Inline `style` overrides for the outer container. Used by callers
78
+ * that need to slot the component into a specific layout.
79
+ */
80
+ style?: React.CSSProperties;
81
+ };
82
+ export declare const EditorError: React.FC<EditorErrorProps>;
83
+ /**
84
+ * Lightweight one-liner for in-table / chip / status-cell contexts.
85
+ * Renders ONLY the title from `editorMessageFor`. When you need more
86
+ * (body, action, details disclosure), use `<EditorError>` instead.
87
+ *
88
+ * Useful for spaces where the row already has its own row-level affordance
89
+ * and we just want the friendly text in place of `failureCode` or
90
+ * `reason` strings.
91
+ */
92
+ export declare const EditorErrorChip: React.FC<{
93
+ code: string | null | undefined;
94
+ context?: EditorErrorContext;
95
+ title?: string;
96
+ }>;
97
+ export {};
@@ -0,0 +1,205 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { editorMessageFor, isPermanentFailure } from '../../lib/error-messages.js';
5
+ const TONE_COLORS = {
6
+ error: {
7
+ bg: 'var(--theme-error-100, #fee2e2)',
8
+ border: 'var(--theme-error-500, #b91c1c)',
9
+ fg: 'var(--theme-error-500, #b91c1c)'
10
+ },
11
+ warning: {
12
+ bg: 'var(--theme-warning-100, #fef3c7)',
13
+ border: 'var(--theme-warning-500, #d97706)',
14
+ fg: 'var(--theme-warning-500, #d97706)'
15
+ },
16
+ info: {
17
+ bg: 'var(--theme-elevation-50, #f1f5f9)',
18
+ border: 'var(--theme-elevation-200, #cbd5e1)',
19
+ fg: 'var(--theme-elevation-800, #1e293b)'
20
+ },
21
+ muted: {
22
+ bg: 'transparent',
23
+ border: 'var(--theme-elevation-100, #e5e7eb)',
24
+ fg: 'var(--theme-elevation-700, #475569)'
25
+ }
26
+ };
27
+ /**
28
+ * Default tone derived from the code prefix. Callers can override via
29
+ * the `tone` prop when context warrants (e.g. an error-tone banner for
30
+ * a soft-skip when many fields failed).
31
+ */ function defaultToneForCode(code) {
32
+ if (!code) return 'error';
33
+ if (code.startsWith('marker.')) return 'muted';
34
+ if (code.startsWith('soft-skip.')) return 'warning';
35
+ if (code.startsWith('cost-guard.')) return 'warning';
36
+ if (code === 'alert.cost-guard-abort') return 'warning';
37
+ if (code === 'bulk.transient.rate_limited') return 'warning';
38
+ if (code === 'bulk.transient.provider') return 'warning';
39
+ if (code === 'bulk.permanent.deleted') return 'info';
40
+ return 'error';
41
+ }
42
+ export const EditorError = ({ code, context, details, tone, compact = false, hideAction = false, onRetry, retryPending = false, style })=>{
43
+ const message = editorMessageFor(code ?? undefined, context ?? {});
44
+ const resolvedTone = tone ?? defaultToneForCode(code);
45
+ const colors = TONE_COLORS[resolvedTone];
46
+ const permanent = isPermanentFailure(code);
47
+ const [explanationOpen, setExplanationOpen] = useState(false);
48
+ const containerStyle = compact ? {
49
+ display: 'flex',
50
+ flexDirection: 'column',
51
+ gap: '0.35rem',
52
+ padding: 0,
53
+ background: 'transparent',
54
+ border: 'none',
55
+ borderRadius: 0,
56
+ ...style
57
+ } : {
58
+ display: 'flex',
59
+ flexDirection: 'column',
60
+ gap: '0.5rem',
61
+ padding: '0.75rem 1rem',
62
+ background: colors.bg,
63
+ border: `1px solid ${colors.border}`,
64
+ borderRadius: '6px',
65
+ ...style
66
+ };
67
+ return /*#__PURE__*/ _jsxs("div", {
68
+ style: containerStyle,
69
+ children: [
70
+ /*#__PURE__*/ _jsx("div", {
71
+ style: {
72
+ fontSize: compact ? '0.8rem' : '0.875rem',
73
+ fontWeight: 600,
74
+ color: colors.fg,
75
+ lineHeight: 1.3
76
+ },
77
+ children: message.title
78
+ }),
79
+ /*#__PURE__*/ _jsx("div", {
80
+ style: {
81
+ fontSize: compact ? '0.75rem' : '0.8rem',
82
+ color: 'var(--theme-elevation-900)',
83
+ lineHeight: 1.5
84
+ },
85
+ children: message.body
86
+ }),
87
+ !hideAction && message.action && /*#__PURE__*/ _jsx("div", {
88
+ style: {
89
+ fontSize: compact ? '0.7rem' : '0.75rem',
90
+ color: 'var(--theme-elevation-700)',
91
+ fontStyle: 'italic'
92
+ },
93
+ children: message.action
94
+ }),
95
+ permanent && message.explanation && /*#__PURE__*/ _jsxs("div", {
96
+ style: {
97
+ marginTop: '0.15rem'
98
+ },
99
+ children: [
100
+ /*#__PURE__*/ _jsx("button", {
101
+ "aria-expanded": explanationOpen,
102
+ onClick: ()=>setExplanationOpen((v)=>!v),
103
+ style: {
104
+ background: 'transparent',
105
+ border: 'none',
106
+ padding: 0,
107
+ cursor: 'pointer',
108
+ color: colors.fg,
109
+ fontSize: '0.75rem',
110
+ fontWeight: 500,
111
+ textDecoration: 'underline'
112
+ },
113
+ type: "button",
114
+ children: explanationOpen ? 'Hide explanation' : 'Why won’t this work?'
115
+ }),
116
+ explanationOpen && /*#__PURE__*/ _jsx("div", {
117
+ style: {
118
+ fontSize: '0.75rem',
119
+ color: 'var(--theme-elevation-800)',
120
+ lineHeight: 1.5,
121
+ marginTop: '0.35rem',
122
+ padding: '0.5rem 0.65rem',
123
+ background: 'var(--theme-elevation-50, #f8fafc)',
124
+ borderLeft: `2px solid ${colors.border}`,
125
+ borderRadius: '3px'
126
+ },
127
+ children: message.explanation
128
+ })
129
+ ]
130
+ }),
131
+ onRetry && /*#__PURE__*/ _jsx("div", {
132
+ style: {
133
+ marginTop: '0.25rem'
134
+ },
135
+ children: /*#__PURE__*/ _jsx("button", {
136
+ disabled: permanent || retryPending,
137
+ onClick: onRetry,
138
+ style: {
139
+ padding: '0.3rem 0.7rem',
140
+ background: permanent ? 'var(--theme-elevation-100, #e5e7eb)' : colors.fg,
141
+ color: permanent ? 'var(--theme-elevation-600, #64748b)' : '#fff',
142
+ border: 'none',
143
+ borderRadius: '4px',
144
+ fontSize: '0.75rem',
145
+ fontWeight: 500,
146
+ cursor: permanent || retryPending ? 'not-allowed' : 'pointer',
147
+ opacity: retryPending ? 0.6 : 1
148
+ },
149
+ title: permanent ? message.explanation ?? 'This error won’t be fixed by retrying.' : undefined,
150
+ type: "button",
151
+ children: retryPending ? 'Retrying…' : 'Try again'
152
+ })
153
+ }),
154
+ details && /*#__PURE__*/ _jsxs("details", {
155
+ style: {
156
+ marginTop: '0.25rem',
157
+ fontSize: '0.7rem',
158
+ color: 'var(--theme-elevation-600, #64748b)'
159
+ },
160
+ children: [
161
+ /*#__PURE__*/ _jsx("summary", {
162
+ style: {
163
+ cursor: 'pointer',
164
+ userSelect: 'none',
165
+ padding: '0.1rem 0'
166
+ },
167
+ children: "Show technical details"
168
+ }),
169
+ /*#__PURE__*/ _jsx("pre", {
170
+ style: {
171
+ marginTop: '0.35rem',
172
+ padding: '0.5rem 0.65rem',
173
+ background: 'var(--theme-elevation-50, #f8fafc)',
174
+ border: '1px solid var(--theme-elevation-100, #e5e7eb)',
175
+ borderRadius: '3px',
176
+ fontSize: '0.7rem',
177
+ fontFamily: 'ui-monospace, SFMono-Regular, monospace',
178
+ whiteSpace: 'pre-wrap',
179
+ wordBreak: 'break-word',
180
+ maxHeight: '12rem',
181
+ overflowY: 'auto',
182
+ margin: 0
183
+ },
184
+ children: details
185
+ })
186
+ ]
187
+ })
188
+ ]
189
+ });
190
+ };
191
+ /**
192
+ * Lightweight one-liner for in-table / chip / status-cell contexts.
193
+ * Renders ONLY the title from `editorMessageFor`. When you need more
194
+ * (body, action, details disclosure), use `<EditorError>` instead.
195
+ *
196
+ * Useful for spaces where the row already has its own row-level affordance
197
+ * and we just want the friendly text in place of `failureCode` or
198
+ * `reason` strings.
199
+ */ export const EditorErrorChip = ({ code, context, title })=>{
200
+ const message = editorMessageFor(code ?? undefined, context ?? {});
201
+ return /*#__PURE__*/ _jsx("span", {
202
+ title: title ?? message.body,
203
+ children: message.title
204
+ });
205
+ };
@@ -0,0 +1,18 @@
1
+ import type React from 'react';
2
+ /**
3
+ * Renders a usage-row `model` value in a table cell. Real OpenRouter
4
+ * model ids (`provider/model`) render verbatim; falsy / provider-only
5
+ * fallbacks (`'openrouter'`, `null`, `''`) collapse to a friendly
6
+ * italic "Failed before model selection" — the run never reached a
7
+ * specific model.
8
+ *
9
+ * Used by:
10
+ * - `TranslationHub/UsageTable.tsx` (Overview → Recent translations)
11
+ * - `TranslationHub/AuditPanel.tsx` (Audit & Cost → Recent failures)
12
+ *
13
+ * Kept in sync via this single component so both surfaces agree on the
14
+ * same copy / styling. See ROUND4-1.
15
+ */
16
+ export declare const ModelCell: React.FC<{
17
+ model: string | null | undefined;
18
+ }>;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { isRealModelId } from '../TranslationHub/AuditPanel.helpers.js';
3
+ /**
4
+ * Renders a usage-row `model` value in a table cell. Real OpenRouter
5
+ * model ids (`provider/model`) render verbatim; falsy / provider-only
6
+ * fallbacks (`'openrouter'`, `null`, `''`) collapse to a friendly
7
+ * italic "Failed before model selection" — the run never reached a
8
+ * specific model.
9
+ *
10
+ * Used by:
11
+ * - `TranslationHub/UsageTable.tsx` (Overview → Recent translations)
12
+ * - `TranslationHub/AuditPanel.tsx` (Audit & Cost → Recent failures)
13
+ *
14
+ * Kept in sync via this single component so both surfaces agree on the
15
+ * same copy / styling. See ROUND4-1.
16
+ */ export const ModelCell = ({ model })=>{
17
+ if (isRealModelId(model)) {
18
+ return /*#__PURE__*/ _jsx(_Fragment, {
19
+ children: model
20
+ });
21
+ }
22
+ return /*#__PURE__*/ _jsx("span", {
23
+ style: {
24
+ color: 'var(--theme-elevation-500)',
25
+ fontStyle: 'italic',
26
+ fontFamily: 'inherit'
27
+ },
28
+ title: "The run failed before a specific model was selected — typically a configuration or provider error.",
29
+ children: "Failed before model selection"
30
+ });
31
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Build a Payload admin URL for a single document, optionally pinned
3
+ * to a target locale via `?locale=`. Used by every Hub surface that
4
+ * links into a translated document so the user lands on the locale
5
+ * they want to verify (not the source locale).
6
+ *
7
+ * Centralising avoids the 3 drifted copies previously living in
8
+ * UsageTable / AuditPanel / BatchRow / FailureDrawer (none of which
9
+ * appended `?locale=`).
10
+ */
11
+ export declare function docHref(basePath: string, collectionSlug: string, documentId: string | number, locale?: string | null): string;
12
+ /**
13
+ * Build a Payload admin URL for a global. Globals are addressed by slug
14
+ * only; the locale param works the same way.
15
+ */
16
+ export declare function globalHref(basePath: string, globalSlug: string, locale?: string | null): string;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Build a Payload admin URL for a single document, optionally pinned
3
+ * to a target locale via `?locale=`. Used by every Hub surface that
4
+ * links into a translated document so the user lands on the locale
5
+ * they want to verify (not the source locale).
6
+ *
7
+ * Centralising avoids the 3 drifted copies previously living in
8
+ * UsageTable / AuditPanel / BatchRow / FailureDrawer (none of which
9
+ * appended `?locale=`).
10
+ */ export function docHref(basePath, collectionSlug, documentId, locale) {
11
+ const base = `${basePath}/admin/collections/${encodeURIComponent(collectionSlug)}/${encodeURIComponent(String(documentId))}`;
12
+ if (locale && locale.length > 0) {
13
+ return `${base}?locale=${encodeURIComponent(locale)}`;
14
+ }
15
+ return base;
16
+ }
17
+ /**
18
+ * Build a Payload admin URL for a global. Globals are addressed by slug
19
+ * only; the locale param works the same way.
20
+ */ export function globalHref(basePath, globalSlug, locale) {
21
+ const base = `${basePath}/admin/globals/${encodeURIComponent(globalSlug)}`;
22
+ if (locale && locale.length > 0) {
23
+ return `${base}?locale=${encodeURIComponent(locale)}`;
24
+ }
25
+ return base;
26
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Read a structured error body from a non-OK Response. The plugin's
3
+ * endpoint helper (`endpoints/translation-hub/_helpers.ts:errorResponse`)
4
+ * returns `{ error: { code, message } }` on failure. This returns just
5
+ * the editor-facing `message`; callers wanting the `code` for catalogue
6
+ * rendering (`editorMessageFor(code)`) should use `readResponseErrorBody`.
7
+ *
8
+ * Falls back to a generic friendly message when the body isn't structured —
9
+ * never throws.
10
+ *
11
+ * 1.2.5 change: stopped appending `(${code})` to the message. The code
12
+ * was leaking engineering jargon to editors; callers that need it now
13
+ * pull it via the structured `readResponseErrorBody` helper.
14
+ */
15
+ export declare function readResponseError(res: Response): Promise<string>;
16
+ /**
17
+ * Structured variant. Returns both `code` and `message` so callers can
18
+ * render via the editor-facing catalogue (`editorMessageFor(code)`)
19
+ * with the raw `message` as a disclosure fallback. Use this at every
20
+ * Hub UI fetch site that surfaces the error inside an `<EditorError>`.
21
+ */
22
+ export declare function readResponseErrorBody(res: Response): Promise<{
23
+ code: string | undefined;
24
+ message: string;
25
+ }>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Read a structured error body from a non-OK Response. The plugin's
3
+ * endpoint helper (`endpoints/translation-hub/_helpers.ts:errorResponse`)
4
+ * returns `{ error: { code, message } }` on failure. This returns just
5
+ * the editor-facing `message`; callers wanting the `code` for catalogue
6
+ * rendering (`editorMessageFor(code)`) should use `readResponseErrorBody`.
7
+ *
8
+ * Falls back to a generic friendly message when the body isn't structured —
9
+ * never throws.
10
+ *
11
+ * 1.2.5 change: stopped appending `(${code})` to the message. The code
12
+ * was leaking engineering jargon to editors; callers that need it now
13
+ * pull it via the structured `readResponseErrorBody` helper.
14
+ */ export async function readResponseError(res) {
15
+ const parsed = await readResponseErrorBody(res);
16
+ return parsed.message;
17
+ }
18
+ /**
19
+ * Structured variant. Returns both `code` and `message` so callers can
20
+ * render via the editor-facing catalogue (`editorMessageFor(code)`)
21
+ * with the raw `message` as a disclosure fallback. Use this at every
22
+ * Hub UI fetch site that surfaces the error inside an `<EditorError>`.
23
+ */ export async function readResponseErrorBody(res) {
24
+ try {
25
+ const body = await res.json();
26
+ if (body && typeof body === 'object') {
27
+ const err = body.error;
28
+ if (err && typeof err.message === 'string' && err.message.length > 0) {
29
+ return {
30
+ code: err.code,
31
+ message: err.message
32
+ };
33
+ }
34
+ }
35
+ } catch {
36
+ // Body wasn't JSON / stream consumed elsewhere. Fall through.
37
+ }
38
+ return {
39
+ code: undefined,
40
+ message: res.status >= 500 ? 'The server returned an error. Try again, or contact engineering if it keeps happening.' : 'The request couldn’t be completed. Try again, or refresh the page.'
41
+ };
42
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared filter-pill style helper.
3
+ *
4
+ * Used by every filter / range / status pill row in the Translation Hub
5
+ * and BulkRunsHub surfaces (FilterBar, BatchRow drill-down chips,
6
+ * SummaryStrip, StatusStrip, AuditPanel). Extracted to fix v1.2.6
7
+ * BugIndex BR-5 + NEW-21:
8
+ *
9
+ * - Before: active pill bg = `elevation-150` / `-200`, sitting beside
10
+ * inactive pills whose border is also `elevation-150` / `-200`. The
11
+ * adjacent UI non-text contrast was 1.0:1 — active state was only
12
+ * signalled by font-weight. WCAG 1.4.11 requires ≥ 3:1 for
13
+ * state-bearing UI.
14
+ * - After: active pill bg = `elevation-1000` (near-black) + text
15
+ * `elevation-50` (near-white) on light theme. The state contrast vs
16
+ * inactive pill borders is ≥ 7:1, well above the AA non-text bar,
17
+ * and the active pill reads as a distinct chip at a glance even
18
+ * without font-weight signalling.
19
+ *
20
+ * Keep the helper small. Each pill row sets its own padding / font-size
21
+ * to match the surrounding surface — only the colour tokens are unified.
22
+ */
23
+ import type React from 'react';
24
+ export type FilterPillColors = {
25
+ background: React.CSSProperties['background'];
26
+ color: React.CSSProperties['color'];
27
+ border: React.CSSProperties['border'];
28
+ };
29
+ /**
30
+ * Resolve a pill's background / text / border colours from its active
31
+ * state. The active state uses `--theme-elevation-1000` (near-black on
32
+ * light theme, near-white on dark) so it never blends with the inactive
33
+ * pills' borders — fixes BR-5 and NEW-21.
34
+ */
35
+ export declare function filterPillColors(isActive: boolean): FilterPillColors;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared filter-pill style helper.
3
+ *
4
+ * Used by every filter / range / status pill row in the Translation Hub
5
+ * and BulkRunsHub surfaces (FilterBar, BatchRow drill-down chips,
6
+ * SummaryStrip, StatusStrip, AuditPanel). Extracted to fix v1.2.6
7
+ * BugIndex BR-5 + NEW-21:
8
+ *
9
+ * - Before: active pill bg = `elevation-150` / `-200`, sitting beside
10
+ * inactive pills whose border is also `elevation-150` / `-200`. The
11
+ * adjacent UI non-text contrast was 1.0:1 — active state was only
12
+ * signalled by font-weight. WCAG 1.4.11 requires ≥ 3:1 for
13
+ * state-bearing UI.
14
+ * - After: active pill bg = `elevation-1000` (near-black) + text
15
+ * `elevation-50` (near-white) on light theme. The state contrast vs
16
+ * inactive pill borders is ≥ 7:1, well above the AA non-text bar,
17
+ * and the active pill reads as a distinct chip at a glance even
18
+ * without font-weight signalling.
19
+ *
20
+ * Keep the helper small. Each pill row sets its own padding / font-size
21
+ * to match the surrounding surface — only the colour tokens are unified.
22
+ */ /**
23
+ * Resolve a pill's background / text / border colours from its active
24
+ * state. The active state uses `--theme-elevation-1000` (near-black on
25
+ * light theme, near-white on dark) so it never blends with the inactive
26
+ * pills' borders — fixes BR-5 and NEW-21.
27
+ */ export function filterPillColors(isActive) {
28
+ if (isActive) {
29
+ return {
30
+ background: 'var(--theme-elevation-1000)',
31
+ color: 'var(--theme-elevation-50)',
32
+ border: '1px solid var(--theme-elevation-1000)'
33
+ };
34
+ }
35
+ return {
36
+ background: 'transparent',
37
+ color: 'var(--theme-elevation-700)',
38
+ border: '1px solid var(--theme-elevation-200)'
39
+ };
40
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Shared cost / duration formatters for Translation Hub + Bulk Translate
3
+ * Runs surfaces. v1.2.6 BugIndex NEW-17 + NEW-18 + NEW-10.
4
+ *
5
+ * Before extracting these, every surface (Hub Overview, Audit, Runs,
6
+ * batch rows, drill-down) rolled its own threshold logic. The result:
7
+ * - Hub Overview "Estimated cost" showed `$0.54`
8
+ * - Audit "Total spend" showed `53.6¢`
9
+ * - Runs "Last 7 days cost" showed `$0.51`
10
+ * - Batch row Cost column showed `12.4¢`
11
+ * - Drill-down unit-detail showed `2.1¢`
12
+ * - Bucket-row duration showed `964m 6s` with no hours carryover
13
+ *
14
+ * Same underlying value (cents, ms), five different formats.
15
+ *
16
+ * The contract here is the only contract going forward. Every cost /
17
+ * duration render site MUST import `formatCost` / `formatDuration`. Do
18
+ * NOT roll new threshold rules at the call site.
19
+ *
20
+ * `formatCost` rule (1.2.8):
21
+ * - All values render as `$X.YY[YY]` (USD, dollar sign always present).
22
+ * - Sub-cent values use up to 4 decimals so editors can tell a
23
+ * 0.46¢ run from a 0.88¢ run — but the unit is consistently $,
24
+ * never the ¢ glyph (it was being mis-read as `$X.YY¢` on dense
25
+ * tables and made per-doc costs look 100× higher than they were).
26
+ * - Values ≥ $0.01 stay at the standard 2-decimal $X.YY form.
27
+ *
28
+ * Wait — that was the OLD rule. The Group F brief locks this in:
29
+ * `$X.XX` with 2 decimals; switch to `Y.Y¢` ONLY when value < $0.01
30
+ * (or, equivalently, < 1¢).
31
+ *
32
+ * So: < $0.01 → `Y.YY¢`, else `$X.XX`.
33
+ *
34
+ * `formatDuration` rule:
35
+ * ms → s → m → h → d, carrying everywhere. Show two units of
36
+ * precision when the next-down unit would otherwise be lost
37
+ * (e.g. `16h 38m`, `5m 16s`, `42s`).
38
+ */
39
+ /**
40
+ * Render a USD value with consistent rules.
41
+ *
42
+ * @param usd Dollars (e.g. 0.124 means 12.4 cents). `null` / `undefined`
43
+ * / non-finite → `'—'`. Negative → coerced to `0`.
44
+ * @returns
45
+ * - `'—'` when missing or non-finite
46
+ * - `'$0.00'` when exactly zero
47
+ * - `'$0.YYYY'` (USD, four decimals) when 0 < value < $0.01
48
+ * - `'$X.XX'` (USD, two decimals) otherwise
49
+ *
50
+ * Tests pin the boundaries — see `__tests__/format.test.ts`.
51
+ */
52
+ export declare function formatCost(usd: number | null | undefined): string;
53
+ /**
54
+ * Render a duration in ms with full carry-over from ms → s → m → h → d.
55
+ *
56
+ * @param ms Duration in milliseconds. `null` / `undefined` / non-finite
57
+ * → `'—'`. Zero → `'—'` by convention (matches the legacy `fmtUsd`
58
+ * behaviour the call sites relied on). Negative → coerced to `0` →
59
+ * `'—'`.
60
+ * @returns
61
+ * - `'—'` when missing / non-finite / non-positive
62
+ * - `'Nms'` when < 1s
63
+ * - `'N.Ns'` when < 10s (one decimal, e.g. `3.4s`)
64
+ * - `'Ns'` when < 60s
65
+ * - `'Nm Ss'` when < 1h (e.g. `5m 16s`)
66
+ * - `'Nh Mm'` when < 24h (e.g. `16h 38m`)
67
+ * - `'Nd Hh'` when ≥ 24h (e.g. `2d 4h`)
68
+ *
69
+ * The rule is the same one BatchRow.tsx implemented for batch-level
70
+ * durations; lifting it here so BucketRow stops capping at minutes and
71
+ * rendering `964m 6s`. See NEW-18.
72
+ *
73
+ * Tests pin the boundaries — see `__tests__/format.test.ts`.
74
+ */
75
+ export declare function formatDuration(ms: number | null | undefined): string;