@eifi1/ui-kit 0.54.0 → 0.55.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 (847) hide show
  1. package/README.md +90 -76
  2. package/dist/admin/plan-change.js +2 -2
  3. package/dist/admin/plan-change.js.map +1 -1
  4. package/dist/admin/user-roster.d.ts +6 -0
  5. package/dist/admin/user-roster.js +4 -0
  6. package/dist/admin/user-roster.js.map +1 -1
  7. package/dist/billing/billing-labels.d.ts +26 -0
  8. package/dist/billing/billing-labels.js +9 -0
  9. package/dist/billing/billing-labels.js.map +1 -1
  10. package/dist/billing/subscription-standing.d.ts +61 -0
  11. package/dist/billing/subscription-standing.js +51 -0
  12. package/dist/billing/subscription-standing.js.map +1 -0
  13. package/dist/billing/usage-meter.d.ts +52 -0
  14. package/dist/billing/usage-meter.js +52 -0
  15. package/dist/billing/usage-meter.js.map +1 -0
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +4 -0
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/alert-banner.js +2 -3
  20. package/dist/components/alert-banner.js.map +1 -1
  21. package/dist/components/amount-input.js +3 -15
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +2 -2
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/bulk-action-bar.js +2 -1
  26. package/dist/components/bulk-action-bar.js.map +1 -1
  27. package/dist/components/button.d.ts +35 -1
  28. package/dist/components/button.js +5 -9
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar-heatmap.js +2 -2
  31. package/dist/components/calendar-heatmap.js.map +1 -1
  32. package/dist/components/camera-button.d.ts +89 -0
  33. package/dist/components/camera-button.js +122 -0
  34. package/dist/components/camera-button.js.map +1 -0
  35. package/dist/components/camera-capture.d.ts +66 -0
  36. package/dist/components/camera-capture.js +190 -0
  37. package/dist/components/camera-capture.js.map +1 -0
  38. package/dist/components/camera-labels.d.ts +20 -0
  39. package/dist/components/camera-labels.js +15 -0
  40. package/dist/components/camera-labels.js.map +1 -0
  41. package/dist/components/chart-edge.d.ts +9 -0
  42. package/dist/components/chart-edge.js +14 -3
  43. package/dist/components/chart-edge.js.map +1 -1
  44. package/dist/components/chart-zoom-core.js +34 -29
  45. package/dist/components/chart-zoom-core.js.map +1 -1
  46. package/dist/components/checkbox-group.js +1 -1
  47. package/dist/components/checkbox-group.js.map +1 -1
  48. package/dist/components/combobox-core.js +3 -2
  49. package/dist/components/combobox-core.js.map +1 -1
  50. package/dist/components/combobox.js +2 -2
  51. package/dist/components/combobox.js.map +1 -1
  52. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  53. package/dist/components/consent-again-notice-labels.js +15 -0
  54. package/dist/components/consent-again-notice-labels.js.map +1 -0
  55. package/dist/components/consent-again-notice.d.ts +85 -0
  56. package/dist/components/consent-again-notice.js +94 -0
  57. package/dist/components/consent-again-notice.js.map +1 -0
  58. package/dist/components/country-select.js +2 -7
  59. package/dist/components/country-select.js.map +1 -1
  60. package/dist/components/currency-select.js +2 -2
  61. package/dist/components/currency-select.js.map +1 -1
  62. package/dist/components/danger-confirm.js +2 -2
  63. package/dist/components/danger-confirm.js.map +1 -1
  64. package/dist/components/data-table-column-settings.js +2 -2
  65. package/dist/components/data-table-column-settings.js.map +1 -1
  66. package/dist/components/data-table-fit.js.map +1 -1
  67. package/dist/components/data-table-virtual.js +13 -3
  68. package/dist/components/data-table-virtual.js.map +1 -1
  69. package/dist/components/date-field.js +2 -2
  70. package/dist/components/date-field.js.map +1 -1
  71. package/dist/components/date-picker.js +2 -2
  72. package/dist/components/date-picker.js.map +1 -1
  73. package/dist/components/date-range-picker.js +2 -6
  74. package/dist/components/date-range-picker.js.map +1 -1
  75. package/dist/components/dialog-frame.d.ts +3 -1
  76. package/dist/components/dialog-frame.js.map +1 -1
  77. package/dist/components/dropdown.js +7 -3
  78. package/dist/components/dropdown.js.map +1 -1
  79. package/dist/components/entity-combobox.js +2 -2
  80. package/dist/components/entity-combobox.js.map +1 -1
  81. package/dist/components/field-parts.d.ts +108 -6
  82. package/dist/components/field-parts.js +59 -6
  83. package/dist/components/field-parts.js.map +1 -1
  84. package/dist/components/field-suggestion-labels.d.ts +2 -0
  85. package/dist/components/field-suggestion-labels.js +10 -0
  86. package/dist/components/field-suggestion-labels.js.map +1 -0
  87. package/dist/components/field.js +1 -1
  88. package/dist/components/field.js.map +1 -1
  89. package/dist/components/file-button.d.ts +1 -1
  90. package/dist/components/file-button.js.map +1 -1
  91. package/dist/components/floating-panel.d.ts +7 -6
  92. package/dist/components/floating-panel.js +3 -17
  93. package/dist/components/floating-panel.js.map +1 -1
  94. package/dist/components/form-actions.d.ts +3 -2
  95. package/dist/components/form-actions.js.map +1 -1
  96. package/dist/components/hover-menu.js +3 -2
  97. package/dist/components/hover-menu.js.map +1 -1
  98. package/dist/components/icon-button.d.ts +23 -5
  99. package/dist/components/icon-button.js +6 -8
  100. package/dist/components/icon-button.js.map +1 -1
  101. package/dist/components/inline-confirm.js +2 -1
  102. package/dist/components/inline-confirm.js.map +1 -1
  103. package/dist/components/inline-entity-combobox.js +2 -2
  104. package/dist/components/inline-entity-combobox.js.map +1 -1
  105. package/dist/components/input.js +2 -2
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/jump-calendar.js +3 -3
  108. package/dist/components/jump-calendar.js.map +1 -1
  109. package/dist/components/legal.d.ts +13 -1
  110. package/dist/components/legal.js +12 -0
  111. package/dist/components/legal.js.map +1 -1
  112. package/dist/components/legended-series-chart.d.ts +80 -0
  113. package/dist/components/legended-series-chart.js +70 -0
  114. package/dist/components/legended-series-chart.js.map +1 -0
  115. package/dist/components/lightbox.js +2 -1
  116. package/dist/components/lightbox.js.map +1 -1
  117. package/dist/components/mini-calendar.js +13 -5
  118. package/dist/components/mini-calendar.js.map +1 -1
  119. package/dist/components/modal.d.ts +23 -2
  120. package/dist/components/modal.js +24 -6
  121. package/dist/components/modal.js.map +1 -1
  122. package/dist/components/month-picker.d.ts +1 -1
  123. package/dist/components/month-picker.js +6 -6
  124. package/dist/components/month-picker.js.map +1 -1
  125. package/dist/components/multi-entity-combobox.js +2 -2
  126. package/dist/components/multi-entity-combobox.js.map +1 -1
  127. package/dist/components/multi-select.js +2 -6
  128. package/dist/components/multi-select.js.map +1 -1
  129. package/dist/components/number-input.js +2 -2
  130. package/dist/components/number-input.js.map +1 -1
  131. package/dist/components/page-header-layout.d.ts +18 -0
  132. package/dist/components/page-header-layout.js +23 -0
  133. package/dist/components/page-header-layout.js.map +1 -0
  134. package/dist/components/page-header-skeleton.d.ts +41 -0
  135. package/dist/components/page-header-skeleton.js +52 -0
  136. package/dist/components/page-header-skeleton.js.map +1 -0
  137. package/dist/components/page-header.js +4 -17
  138. package/dist/components/page-header.js.map +1 -1
  139. package/dist/components/popover.js +7 -2
  140. package/dist/components/popover.js.map +1 -1
  141. package/dist/components/release-notes-labels.d.ts +16 -0
  142. package/dist/components/release-notes-labels.js +18 -0
  143. package/dist/components/release-notes-labels.js.map +1 -0
  144. package/dist/components/release-notes.d.ts +65 -0
  145. package/dist/components/release-notes.js +65 -0
  146. package/dist/components/release-notes.js.map +1 -0
  147. package/dist/components/select.js +2 -2
  148. package/dist/components/select.js.map +1 -1
  149. package/dist/components/series-chart-labels.d.ts +9 -0
  150. package/dist/components/series-chart-labels.js +3 -1
  151. package/dist/components/series-chart-labels.js.map +1 -1
  152. package/dist/components/series-chart.d.ts +5 -0
  153. package/dist/components/series-chart.js +89 -36
  154. package/dist/components/series-chart.js.map +1 -1
  155. package/dist/components/toggle-group.js +3 -2
  156. package/dist/components/toggle-group.js.map +1 -1
  157. package/dist/components/treemap-layout.js.map +1 -1
  158. package/dist/components/treemap.d.ts +17 -15
  159. package/dist/components/treemap.js +167 -85
  160. package/dist/components/treemap.js.map +1 -1
  161. package/dist/eslint/index.js +1 -1
  162. package/dist/eslint/role-utilities.d.ts +2 -2
  163. package/dist/eslint/role-utilities.js.map +1 -1
  164. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  165. package/dist/feedback/feedback-inbox-page.js +207 -0
  166. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  167. package/dist/feedback/feedback-vocabulary.js +2 -2
  168. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  169. package/dist/feedback.d.ts +1 -0
  170. package/dist/feedback.js +6 -0
  171. package/dist/feedback.js.map +1 -1
  172. package/dist/hooks/use-debounce.d.ts +32 -5
  173. package/dist/hooks/use-debounce.js +6 -5
  174. package/dist/hooks/use-debounce.js.map +1 -1
  175. package/dist/hooks/use-file-picker.d.ts +3 -1
  176. package/dist/hooks/use-file-picker.js.map +1 -1
  177. package/dist/hooks/use-focus-trap.d.ts +19 -3
  178. package/dist/hooks/use-focus-trap.js +15 -5
  179. package/dist/hooks/use-focus-trap.js.map +1 -1
  180. package/dist/hooks/use-has-camera.d.ts +36 -0
  181. package/dist/hooks/use-has-camera.js +53 -0
  182. package/dist/hooks/use-has-camera.js.map +1 -0
  183. package/dist/hooks/use-hotkey.js +1 -10
  184. package/dist/hooks/use-hotkey.js.map +1 -1
  185. package/dist/hooks/use-overlay-history.d.ts +6 -1
  186. package/dist/hooks/use-overlay-history.js +10 -2
  187. package/dist/hooks/use-overlay-history.js.map +1 -1
  188. package/dist/i18n/defaults.js +11 -1
  189. package/dist/i18n/defaults.js.map +1 -1
  190. package/dist/i18n/german.js +75 -3
  191. package/dist/i18n/german.js.map +1 -1
  192. package/dist/i18n/kit-labels.d.ts +20 -0
  193. package/dist/i18n/kit-labels.js.map +1 -1
  194. package/dist/i18n/locales/en.js +92 -4
  195. package/dist/i18n/locales/en.js.map +3 -3
  196. package/dist/i18n/locales/es.js +71 -3
  197. package/dist/i18n/locales/es.js.map +1 -1
  198. package/dist/i18n/locales/fr.js +72 -2
  199. package/dist/i18n/locales/fr.js.map +1 -1
  200. package/dist/i18n/locales/hu.js +74 -3
  201. package/dist/i18n/locales/hu.js.map +1 -1
  202. package/dist/i18n/locales/it.js +74 -3
  203. package/dist/i18n/locales/it.js.map +1 -1
  204. package/dist/i18n/locales/zh.js +71 -3
  205. package/dist/i18n/locales/zh.js.map +1 -1
  206. package/dist/i18n/review.js +22 -1
  207. package/dist/i18n/review.js.map +1 -1
  208. package/dist/index.d.ts +24 -5
  209. package/dist/index.js +35 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/landing/landing-labels.d.ts +8 -0
  212. package/dist/landing/landing-labels.js +2 -1
  213. package/dist/landing/landing-labels.js.map +1 -1
  214. package/dist/lib/changelog.d.ts +130 -0
  215. package/dist/lib/changelog.js +137 -0
  216. package/dist/lib/changelog.js.map +1 -0
  217. package/dist/lib/clipboard-minus.js +2 -2
  218. package/dist/lib/clipboard-minus.js.map +1 -1
  219. package/dist/lib/dates.d.ts +29 -3
  220. package/dist/lib/dates.js +41 -12
  221. package/dist/lib/dates.js.map +1 -1
  222. package/dist/lib/editable-content.d.ts +33 -0
  223. package/dist/lib/editable-content.js +15 -0
  224. package/dist/lib/editable-content.js.map +1 -0
  225. package/dist/lib/focus-left.d.ts +47 -1
  226. package/dist/lib/focus-left.js +11 -2
  227. package/dist/lib/focus-left.js.map +1 -1
  228. package/dist/lib/server-wake.d.ts +49 -9
  229. package/dist/lib/server-wake.js +25 -14
  230. package/dist/lib/server-wake.js.map +1 -1
  231. package/dist/lib/session-refresh.d.ts +53 -0
  232. package/dist/lib/session-refresh.js +27 -1
  233. package/dist/lib/session-refresh.js.map +1 -1
  234. package/dist/rhf/fields.js +3 -3
  235. package/dist/rhf/fields.js.map +1 -1
  236. package/dist/shell/app-shell.d.ts +25 -1
  237. package/dist/shell/app-shell.js +3 -1
  238. package/dist/shell/app-shell.js.map +1 -1
  239. package/dist/shell/dev-menu.d.ts +3 -0
  240. package/dist/shell/dev-menu.js +4 -2
  241. package/dist/shell/dev-menu.js.map +1 -1
  242. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  243. package/dist/shell/held-changes-banner-labels.js +14 -0
  244. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  245. package/dist/shell/held-changes-banner.d.ts +75 -0
  246. package/dist/shell/held-changes-banner.js +106 -0
  247. package/dist/shell/held-changes-banner.js.map +1 -0
  248. package/dist/shell/sync-status-indicator.d.ts +27 -12
  249. package/dist/shell/sync-status-indicator.js +13 -4
  250. package/dist/shell/sync-status-indicator.js.map +1 -1
  251. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  252. package/dist/shell/top-bar-parts-labels.js +3 -1
  253. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  254. package/dist/shell.d.ts +2 -0
  255. package/dist/shell.js +3 -0
  256. package/dist/shell.js.map +1 -1
  257. package/dist/sync/engine.d.ts +11 -2
  258. package/dist/sync/engine.js +13 -5
  259. package/dist/sync/engine.js.map +1 -1
  260. package/dist/sync/hooks.d.ts +3 -2
  261. package/dist/sync/hooks.js +4 -2
  262. package/dist/sync/hooks.js.map +1 -1
  263. package/dist/testing/css-variables.generated.d.ts +2 -2
  264. package/dist/testing/css-variables.generated.js +1 -1
  265. package/dist/testing/css-variables.generated.js.map +1 -1
  266. package/dist/theme/chart-palette.d.ts +18 -0
  267. package/dist/theme/chart-palette.js +17 -1
  268. package/dist/theme/chart-palette.js.map +1 -1
  269. package/dist/theme/contrast-tokens.d.ts +3 -3
  270. package/dist/theme/contrast-tokens.js.map +1 -1
  271. package/dist/theme/contrast.d.ts +4 -4
  272. package/dist/theme/contrast.js.map +1 -1
  273. package/dist/tour/tour-overlay.js +2 -1
  274. package/dist/tour/tour-overlay.js.map +1 -1
  275. package/package.json +5 -9
  276. package/tokens.css +19 -54
  277. package/src/account/account-parts.ts +0 -47
  278. package/src/account/account-settings-labels.ts +0 -240
  279. package/src/account/account-settings.tsx +0 -698
  280. package/src/account/data-export-setting-labels.ts +0 -20
  281. package/src/account/data-export-setting.tsx +0 -200
  282. package/src/account/delete-account-setting-labels.ts +0 -33
  283. package/src/account/delete-account-setting.tsx +0 -342
  284. package/src/account/email-change-setting-labels.ts +0 -31
  285. package/src/account/email-change-setting.tsx +0 -443
  286. package/src/account/passkeys-setting.tsx +0 -445
  287. package/src/account/sessions-setting-labels.ts +0 -24
  288. package/src/account/sessions-setting.tsx +0 -283
  289. package/src/admin/admin-action-confirm-labels.ts +0 -19
  290. package/src/admin/admin-action-confirm.tsx +0 -509
  291. package/src/admin/admin-action-log-labels.ts +0 -83
  292. package/src/admin/admin-action-log.tsx +0 -464
  293. package/src/admin/admin-area-labels.ts +0 -114
  294. package/src/admin/admin-forbidden.tsx +0 -75
  295. package/src/admin/admin-overview.tsx +0 -258
  296. package/src/admin/admin-parts.ts +0 -68
  297. package/src/admin/admin-sections.tsx +0 -208
  298. package/src/admin/admin-tool-links.tsx +0 -90
  299. package/src/admin/invitations-panel-labels.ts +0 -31
  300. package/src/admin/invitations-panel.tsx +0 -516
  301. package/src/admin/mail-test-card-labels.ts +0 -19
  302. package/src/admin/mail-test-card.tsx +0 -189
  303. package/src/admin/plan-change-labels.ts +0 -5
  304. package/src/admin/plan-change.tsx +0 -474
  305. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  306. package/src/admin/reviewer-scope-editor.tsx +0 -224
  307. package/src/admin/role-select-labels.ts +0 -16
  308. package/src/admin/role-select.tsx +0 -282
  309. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  310. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  311. package/src/admin/user-roster-labels.ts +0 -17
  312. package/src/admin/user-roster.tsx +0 -559
  313. package/src/auth/accept-invitation-labels.ts +0 -21
  314. package/src/auth/accept-invitation.tsx +0 -210
  315. package/src/auth/auth-errors-labels.ts +0 -23
  316. package/src/auth/auth-errors.ts +0 -439
  317. package/src/auth/complete-name-dialog-labels.ts +0 -16
  318. package/src/auth/complete-name-dialog.tsx +0 -253
  319. package/src/auth/email-tag.ts +0 -75
  320. package/src/auth/forgot-password-form-labels.ts +0 -20
  321. package/src/auth/forgot-password-form.tsx +0 -221
  322. package/src/auth/form-rules.ts +0 -40
  323. package/src/auth/new-password-fields.tsx +0 -85
  324. package/src/auth/not-found-page-labels.ts +0 -12
  325. package/src/auth/not-found-page.tsx +0 -95
  326. package/src/auth/register-form-labels.ts +0 -31
  327. package/src/auth/register-form.tsx +0 -456
  328. package/src/auth/reset-password-form-labels.ts +0 -27
  329. package/src/auth/reset-password-form.tsx +0 -341
  330. package/src/auth/sign-in-form-labels.ts +0 -44
  331. package/src/auth/sign-in-form.tsx +0 -858
  332. package/src/auth/status-parts.tsx +0 -132
  333. package/src/auth/verify-email-labels.ts +0 -26
  334. package/src/auth/verify-email.tsx +0 -445
  335. package/src/billing/billing-banner.tsx +0 -258
  336. package/src/billing/billing-labels.ts +0 -381
  337. package/src/billing/billing-lock.ts +0 -108
  338. package/src/billing/billing-overview.ts +0 -157
  339. package/src/billing/billing-standing.ts +0 -80
  340. package/src/billing/cancel-request.ts +0 -97
  341. package/src/billing/cancel-subscription.tsx +0 -98
  342. package/src/billing/checkout-processing.ts +0 -394
  343. package/src/billing/checkout-return.ts +0 -90
  344. package/src/billing/pay-page.ts +0 -330
  345. package/src/billing/plan-card.tsx +0 -562
  346. package/src/billing/plan-change-labels.ts +0 -66
  347. package/src/billing/plan-limit-notice.tsx +0 -148
  348. package/src/billing/plan-limit-toast.tsx +0 -90
  349. package/src/billing/plan-limit.ts +0 -52
  350. package/src/billing/plan-price.ts +0 -98
  351. package/src/billing/plan-switch.tsx +0 -216
  352. package/src/billing/provider-page.ts +0 -84
  353. package/src/billing/subscription-actions.tsx +0 -247
  354. package/src/billing/subscription-status-chip.tsx +0 -95
  355. package/src/chart.ts +0 -103
  356. package/src/components/account-chips-labels.ts +0 -16
  357. package/src/components/account-chips.tsx +0 -474
  358. package/src/components/alert-banner.tsx +0 -670
  359. package/src/components/amount-input.tsx +0 -816
  360. package/src/components/appearance-labels.ts +0 -63
  361. package/src/components/authed-image-labels.ts +0 -11
  362. package/src/components/authed-image.tsx +0 -209
  363. package/src/components/autocomplete.tsx +0 -645
  364. package/src/components/breadcrumbs-labels.ts +0 -10
  365. package/src/components/breadcrumbs.tsx +0 -205
  366. package/src/components/bulk-action-bar-labels.ts +0 -11
  367. package/src/components/bulk-action-bar.tsx +0 -443
  368. package/src/components/button-group.tsx +0 -223
  369. package/src/components/button-look.ts +0 -256
  370. package/src/components/button.tsx +0 -339
  371. package/src/components/calculator.tsx +0 -341
  372. package/src/components/calendar-heatmap-labels.ts +0 -15
  373. package/src/components/calendar-heatmap.tsx +0 -600
  374. package/src/components/card.tsx +0 -377
  375. package/src/components/chart-edge.ts +0 -106
  376. package/src/components/chart-figure.tsx +0 -35
  377. package/src/components/chart-height.ts +0 -30
  378. package/src/components/chart-zoom-core.tsx +0 -627
  379. package/src/components/chart-zoom.tsx +0 -22
  380. package/src/components/chart.tsx +0 -554
  381. package/src/components/checkbox-group.tsx +0 -247
  382. package/src/components/checkbox.tsx +0 -346
  383. package/src/components/chip-input.tsx +0 -370
  384. package/src/components/chip-labels.ts +0 -13
  385. package/src/components/chip.tsx +0 -843
  386. package/src/components/choice-card.tsx +0 -544
  387. package/src/components/column-mapper-labels.ts +0 -38
  388. package/src/components/column-mapper.tsx +0 -655
  389. package/src/components/combobox-core.tsx +0 -1321
  390. package/src/components/combobox-suggestions.tsx +0 -131
  391. package/src/components/combobox.tsx +0 -716
  392. package/src/components/confirm-context.ts +0 -7
  393. package/src/components/confirm-dialog-labels.ts +0 -13
  394. package/src/components/confirm-dialog.tsx +0 -341
  395. package/src/components/copy-button-labels.ts +0 -13
  396. package/src/components/copy-button.tsx +0 -192
  397. package/src/components/country-select-labels.ts +0 -11
  398. package/src/components/country-select.tsx +0 -539
  399. package/src/components/currency-option-list.tsx +0 -188
  400. package/src/components/currency-select.tsx +0 -343
  401. package/src/components/danger-confirm-labels.ts +0 -20
  402. package/src/components/danger-confirm.tsx +0 -987
  403. package/src/components/data-table-cells.tsx +0 -118
  404. package/src/components/data-table-column-settings.tsx +0 -198
  405. package/src/components/data-table-filter-popover.tsx +0 -289
  406. package/src/components/data-table-filters.ts +0 -315
  407. package/src/components/data-table-fit.ts +0 -301
  408. package/src/components/data-table-groups.tsx +0 -288
  409. package/src/components/data-table-head.tsx +0 -280
  410. package/src/components/data-table-labels.ts +0 -198
  411. package/src/components/data-table-leading-row.tsx +0 -58
  412. package/src/components/data-table-mobile.tsx +0 -1117
  413. package/src/components/data-table-pagination.tsx +0 -202
  414. package/src/components/data-table-resize.ts +0 -188
  415. package/src/components/data-table-row-actions.tsx +0 -302
  416. package/src/components/data-table-row.tsx +0 -496
  417. package/src/components/data-table-selection.ts +0 -71
  418. package/src/components/data-table-shared.tsx +0 -287
  419. package/src/components/data-table-sort.ts +0 -163
  420. package/src/components/data-table-virtual.ts +0 -576
  421. package/src/components/data-table.tsx +0 -2598
  422. package/src/components/date-field.tsx +0 -684
  423. package/src/components/date-format-setting.tsx +0 -64
  424. package/src/components/date-picker.tsx +0 -404
  425. package/src/components/date-range-picker.tsx +0 -518
  426. package/src/components/description-list-labels.ts +0 -9
  427. package/src/components/description-list.tsx +0 -386
  428. package/src/components/dialog-frame-labels.ts +0 -7
  429. package/src/components/dialog-frame.tsx +0 -284
  430. package/src/components/disclosure.tsx +0 -476
  431. package/src/components/dropdown.tsx +0 -461
  432. package/src/components/empty-state.tsx +0 -136
  433. package/src/components/entity-combobox.tsx +0 -422
  434. package/src/components/error-boundary-labels.ts +0 -20
  435. package/src/components/error-boundary.tsx +0 -675
  436. package/src/components/facing-pair.ts +0 -93
  437. package/src/components/field-base-labels.ts +0 -20
  438. package/src/components/field-base.tsx +0 -656
  439. package/src/components/field-parts.tsx +0 -825
  440. package/src/components/field-strip.tsx +0 -129
  441. package/src/components/field.tsx +0 -206
  442. package/src/components/file-button.tsx +0 -295
  443. package/src/components/file-dropzone.tsx +0 -559
  444. package/src/components/floating-panel-labels.ts +0 -10
  445. package/src/components/floating-panel.tsx +0 -828
  446. package/src/components/focus-ring.ts +0 -16
  447. package/src/components/form-actions-labels.ts +0 -11
  448. package/src/components/form-actions.tsx +0 -578
  449. package/src/components/full-bleed-dialog.tsx +0 -294
  450. package/src/components/grouped-picker.tsx +0 -184
  451. package/src/components/help-popover.tsx +0 -117
  452. package/src/components/hover-menu.tsx +0 -343
  453. package/src/components/iban-input-labels.ts +0 -14
  454. package/src/components/iban-input.tsx +0 -236
  455. package/src/components/icon-button.tsx +0 -959
  456. package/src/components/icon-picker-labels.ts +0 -13
  457. package/src/components/icon-picker.tsx +0 -232
  458. package/src/components/image-grid-labels.ts +0 -11
  459. package/src/components/image-grid.tsx +0 -202
  460. package/src/components/inline-confirm.tsx +0 -116
  461. package/src/components/inline-edit-field-labels.ts +0 -11
  462. package/src/components/inline-edit-field.tsx +0 -552
  463. package/src/components/inline-entity-combobox.tsx +0 -849
  464. package/src/components/input.tsx +0 -394
  465. package/src/components/jump-calendar.tsx +0 -265
  466. package/src/components/language-select.tsx +0 -93
  467. package/src/components/legal-labels.ts +0 -105
  468. package/src/components/legal-page.tsx +0 -711
  469. package/src/components/legal.tsx +0 -668
  470. package/src/components/lightbox-labels.ts +0 -18
  471. package/src/components/lightbox.tsx +0 -399
  472. package/src/components/line-items-labels.ts +0 -14
  473. package/src/components/line-items.tsx +0 -602
  474. package/src/components/list-labels.ts +0 -12
  475. package/src/components/list.tsx +0 -870
  476. package/src/components/loading-state.tsx +0 -78
  477. package/src/components/measured-grid-labels.ts +0 -25
  478. package/src/components/measured-grid.tsx +0 -837
  479. package/src/components/menu-item.tsx +0 -241
  480. package/src/components/mini-calendar-labels.ts +0 -17
  481. package/src/components/mini-calendar.tsx +0 -792
  482. package/src/components/modal.tsx +0 -534
  483. package/src/components/money-draft.ts +0 -83
  484. package/src/components/money-field.tsx +0 -76
  485. package/src/components/month-grid.tsx +0 -229
  486. package/src/components/month-picker-labels.ts +0 -22
  487. package/src/components/month-picker.tsx +0 -1087
  488. package/src/components/multi-entity-combobox.tsx +0 -369
  489. package/src/components/multi-select.tsx +0 -376
  490. package/src/components/nav-pills.tsx +0 -198
  491. package/src/components/number-field.tsx +0 -327
  492. package/src/components/number-input.tsx +0 -559
  493. package/src/components/numeric-field.ts +0 -169
  494. package/src/components/numpad-sheet.tsx +0 -285
  495. package/src/components/one-time-code-input.tsx +0 -270
  496. package/src/components/page-contents-labels.ts +0 -9
  497. package/src/components/page-contents.tsx +0 -282
  498. package/src/components/page-header-context.ts +0 -8
  499. package/src/components/page-header.tsx +0 -298
  500. package/src/components/password-strength-labels.ts +0 -22
  501. package/src/components/password-strength.tsx +0 -241
  502. package/src/components/phone-input-labels.ts +0 -10
  503. package/src/components/phone-input.tsx +0 -282
  504. package/src/components/picker-sheet.tsx +0 -330
  505. package/src/components/pie-chart-labels.ts +0 -40
  506. package/src/components/pie-chart.tsx +0 -480
  507. package/src/components/popover-labels.ts +0 -14
  508. package/src/components/popover.tsx +0 -268
  509. package/src/components/progress-bar-labels.ts +0 -10
  510. package/src/components/progress-bar.tsx +0 -479
  511. package/src/components/qr-code.tsx +0 -76
  512. package/src/components/reauth-dialog-labels.ts +0 -13
  513. package/src/components/reauth-dialog.tsx +0 -265
  514. package/src/components/row-actions-labels.ts +0 -12
  515. package/src/components/row-actions.tsx +0 -795
  516. package/src/components/scroll-area.tsx +0 -123
  517. package/src/components/search-field.tsx +0 -192
  518. package/src/components/select.tsx +0 -294
  519. package/src/components/separator.tsx +0 -33
  520. package/src/components/series-chart-axes.ts +0 -342
  521. package/src/components/series-chart-budget.ts +0 -150
  522. package/src/components/series-chart-labels.ts +0 -33
  523. package/src/components/series-chart-thin.ts +0 -180
  524. package/src/components/series-chart-ticks.ts +0 -194
  525. package/src/components/series-chart.tsx +0 -1906
  526. package/src/components/server-wake-labels.ts +0 -11
  527. package/src/components/server-wake.tsx +0 -129
  528. package/src/components/settings-fields.tsx +0 -237
  529. package/src/components/share-card-labels.ts +0 -30
  530. package/src/components/share-card.tsx +0 -675
  531. package/src/components/signature-pad-labels.ts +0 -24
  532. package/src/components/signature-pad.tsx +0 -747
  533. package/src/components/signed-amount-labels.ts +0 -10
  534. package/src/components/signed-amount.tsx +0 -336
  535. package/src/components/skeleton.tsx +0 -80
  536. package/src/components/slider.tsx +0 -338
  537. package/src/components/sparkline-labels.ts +0 -14
  538. package/src/components/sparkline.tsx +0 -322
  539. package/src/components/spinner.tsx +0 -81
  540. package/src/components/stat-tile-labels.ts +0 -15
  541. package/src/components/stat-tile.tsx +0 -754
  542. package/src/components/status-dot.tsx +0 -170
  543. package/src/components/swatch-picker-labels.ts +0 -10
  544. package/src/components/swatch-picker.tsx +0 -283
  545. package/src/components/swipeable-row.tsx +0 -386
  546. package/src/components/switch.tsx +0 -273
  547. package/src/components/table-skeleton.tsx +0 -136
  548. package/src/components/table.tsx +0 -647
  549. package/src/components/tabs-labels.ts +0 -13
  550. package/src/components/tabs.tsx +0 -693
  551. package/src/components/text-link.tsx +0 -359
  552. package/src/components/text.tsx +0 -150
  553. package/src/components/textarea.tsx +0 -330
  554. package/src/components/tile-radio.tsx +0 -332
  555. package/src/components/time-input.tsx +0 -121
  556. package/src/components/toast-labels.ts +0 -12
  557. package/src/components/toast.tsx +0 -519
  558. package/src/components/toggle-group.tsx +0 -752
  559. package/src/components/toggle-legend.tsx +0 -394
  560. package/src/components/tooltip.tsx +0 -1149
  561. package/src/components/translation-review-editor.tsx +0 -290
  562. package/src/components/translation-review-labels.ts +0 -181
  563. package/src/components/translation-review-parts.tsx +0 -272
  564. package/src/components/translation-review.tsx +0 -1240
  565. package/src/components/tree-view.tsx +0 -589
  566. package/src/components/treemap-labels.ts +0 -47
  567. package/src/components/treemap-layout.ts +0 -210
  568. package/src/components/treemap.tsx +0 -1184
  569. package/src/components/trigger-aria.ts +0 -44
  570. package/src/components/use-anchor-dir.ts +0 -20
  571. package/src/components/use-mobile-reveal.ts +0 -62
  572. package/src/components/use-table-state.ts +0 -753
  573. package/src/components/user-avatar.tsx +0 -116
  574. package/src/components/wizard-stepper.tsx +0 -42
  575. package/src/components/write-lock-labels.ts +0 -10
  576. package/src/components/write-lock.tsx +0 -253
  577. package/src/currencies.ts +0 -90
  578. package/src/data-table.ts +0 -42
  579. package/src/demo/demo-banner.tsx +0 -153
  580. package/src/demo/demo-countdown.ts +0 -136
  581. package/src/demo/demo-ended.tsx +0 -90
  582. package/src/demo/demo-labels.ts +0 -78
  583. package/src/demo/demo-session.ts +0 -32
  584. package/src/demo/demo-start.tsx +0 -156
  585. package/src/eslint/index.ts +0 -161
  586. package/src/eslint/role-utilities.generated.ts +0 -146
  587. package/src/eslint/role-utilities.ts +0 -233
  588. package/src/feedback/feedback-attachment-download.tsx +0 -77
  589. package/src/feedback/feedback-attachment-labels.ts +0 -37
  590. package/src/feedback/feedback-attachment.tsx +0 -1239
  591. package/src/feedback/feedback-capture.ts +0 -287
  592. package/src/feedback/feedback-comment-thread.tsx +0 -353
  593. package/src/feedback/feedback-context-labels.ts +0 -11
  594. package/src/feedback/feedback-context.tsx +0 -245
  595. package/src/feedback/feedback-crash.ts +0 -547
  596. package/src/feedback/feedback-diagnostics.tsx +0 -41
  597. package/src/feedback/feedback-dialog-labels.ts +0 -29
  598. package/src/feedback/feedback-dialog.tsx +0 -452
  599. package/src/feedback/feedback-entry-file.tsx +0 -150
  600. package/src/feedback/feedback-inbox.tsx +0 -552
  601. package/src/feedback/feedback-labels.ts +0 -144
  602. package/src/feedback/feedback-menu-labels.ts +0 -12
  603. package/src/feedback/feedback-menu.tsx +0 -177
  604. package/src/feedback/feedback-mutation.ts +0 -20
  605. package/src/feedback/feedback-page-link.ts +0 -90
  606. package/src/feedback/feedback-public-labels.ts +0 -81
  607. package/src/feedback/feedback-public-list.tsx +0 -524
  608. package/src/feedback/feedback-public-switch.tsx +0 -102
  609. package/src/feedback/feedback-public-types.ts +0 -101
  610. package/src/feedback/feedback-record.ts +0 -428
  611. package/src/feedback/feedback-replies-context.ts +0 -9
  612. package/src/feedback/feedback-replies.tsx +0 -343
  613. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  614. package/src/feedback/feedback-row-detail-labels.ts +0 -31
  615. package/src/feedback/feedback-row-detail.tsx +0 -871
  616. package/src/feedback/feedback-status-undo.ts +0 -109
  617. package/src/feedback/feedback-submit-dialog.tsx +0 -131
  618. package/src/feedback/feedback-submit.tsx +0 -270
  619. package/src/feedback/feedback-swipe.tsx +0 -192
  620. package/src/feedback/feedback-table-labels.ts +0 -27
  621. package/src/feedback/feedback-table.tsx +0 -912
  622. package/src/feedback/feedback-thread-labels.ts +0 -22
  623. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  624. package/src/feedback/feedback-thread.tsx +0 -905
  625. package/src/feedback/feedback-vocabulary.ts +0 -263
  626. package/src/feedback/feedback-vote-button.tsx +0 -124
  627. package/src/feedback.ts +0 -116
  628. package/src/hooks/use-anchored-panel.ts +0 -396
  629. package/src/hooks/use-anchored-rect.ts +0 -77
  630. package/src/hooks/use-announce.ts +0 -85
  631. package/src/hooks/use-authed-src.ts +0 -167
  632. package/src/hooks/use-body-scroll-lock.ts +0 -119
  633. package/src/hooks/use-breakpoint.ts +0 -133
  634. package/src/hooks/use-close-transition.ts +0 -107
  635. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  636. package/src/hooks/use-debounce.ts +0 -114
  637. package/src/hooks/use-dismiss.ts +0 -371
  638. package/src/hooks/use-document-title.ts +0 -33
  639. package/src/hooks/use-file-drop.ts +0 -121
  640. package/src/hooks/use-file-picker-labels.ts +0 -28
  641. package/src/hooks/use-file-picker.tsx +0 -359
  642. package/src/hooks/use-focus-trap.ts +0 -199
  643. package/src/hooks/use-hotkey.ts +0 -212
  644. package/src/hooks/use-large-text.ts +0 -41
  645. package/src/hooks/use-long-press.ts +0 -140
  646. package/src/hooks/use-media-query.ts +0 -39
  647. package/src/hooks/use-mounted.ts +0 -39
  648. package/src/hooks/use-noindex.ts +0 -37
  649. package/src/hooks/use-overlay-history.ts +0 -714
  650. package/src/hooks/use-row-swipe.ts +0 -207
  651. package/src/hooks/use-search-param-state.ts +0 -434
  652. package/src/hooks/use-windowed-rows.ts +0 -225
  653. package/src/i18n/defaults.ts +0 -267
  654. package/src/i18n/german.ts +0 -2059
  655. package/src/i18n/kit-labels.tsx +0 -1003
  656. package/src/i18n/languages.ts +0 -349
  657. package/src/i18n/locales/de-CH.ts +0 -24
  658. package/src/i18n/locales/en.ts +0 -267
  659. package/src/i18n/locales/es.ts +0 -1999
  660. package/src/i18n/locales/fr.ts +0 -2037
  661. package/src/i18n/locales/hu.ts +0 -2004
  662. package/src/i18n/locales/it.ts +0 -2014
  663. package/src/i18n/locales/zh.ts +0 -1912
  664. package/src/i18n/plurals.ts +0 -86
  665. package/src/i18n/review.ts +0 -475
  666. package/src/i18n/shared-labels.ts +0 -138
  667. package/src/i18n/swiss.ts +0 -29
  668. package/src/index.ts +0 -1191
  669. package/src/landing/access.ts +0 -93
  670. package/src/landing/landing-actions.tsx +0 -97
  671. package/src/landing/landing-labels.ts +0 -59
  672. package/src/landing/landing-sections.tsx +0 -341
  673. package/src/landing/page-seo.ts +0 -261
  674. package/src/landing/public-header.tsx +0 -121
  675. package/src/landing/public-page.tsx +0 -106
  676. package/src/landing/routing.tsx +0 -225
  677. package/src/lib/broadcast.ts +0 -52
  678. package/src/lib/calc.ts +0 -323
  679. package/src/lib/chip-edge-snap.ts +0 -212
  680. package/src/lib/clipboard-minus.ts +0 -107
  681. package/src/lib/clipping.ts +0 -34
  682. package/src/lib/cn.ts +0 -28
  683. package/src/lib/column-mapping.ts +0 -234
  684. package/src/lib/countries.ts +0 -91
  685. package/src/lib/data-attributes.ts +0 -14
  686. package/src/lib/date-format.ts +0 -227
  687. package/src/lib/dates.ts +0 -288
  688. package/src/lib/day-format.ts +0 -96
  689. package/src/lib/decimal-marks.ts +0 -108
  690. package/src/lib/describe-failure.ts +0 -18
  691. package/src/lib/device-settings.ts +0 -147
  692. package/src/lib/direction.ts +0 -74
  693. package/src/lib/document-navigation.ts +0 -17
  694. package/src/lib/document-title.ts +0 -41
  695. package/src/lib/download.ts +0 -172
  696. package/src/lib/float-clearance.ts +0 -70
  697. package/src/lib/floating-stack.ts +0 -70
  698. package/src/lib/focus-left.ts +0 -156
  699. package/src/lib/format.ts +0 -585
  700. package/src/lib/http-status.ts +0 -38
  701. package/src/lib/iban.ts +0 -251
  702. package/src/lib/ime.ts +0 -24
  703. package/src/lib/intl-cache.ts +0 -27
  704. package/src/lib/iso-day.ts +0 -13
  705. package/src/lib/lazy-with-fallback.ts +0 -82
  706. package/src/lib/logger.ts +0 -69
  707. package/src/lib/minus.ts +0 -50
  708. package/src/lib/path-pattern.ts +0 -55
  709. package/src/lib/person-name.ts +0 -155
  710. package/src/lib/phone.ts +0 -208
  711. package/src/lib/plain-text.ts +0 -22
  712. package/src/lib/presence.ts +0 -18
  713. package/src/lib/qr-encode.ts +0 -444
  714. package/src/lib/quiet-focus.ts +0 -32
  715. package/src/lib/reduced-motion.ts +0 -22
  716. package/src/lib/refs.ts +0 -22
  717. package/src/lib/round.ts +0 -43
  718. package/src/lib/safe-storage.ts +0 -119
  719. package/src/lib/server-wake.ts +0 -315
  720. package/src/lib/session-refresh.ts +0 -472
  721. package/src/lib/share-format.ts +0 -18
  722. package/src/lib/status-tone.ts +0 -43
  723. package/src/lib/strip-fade.ts +0 -287
  724. package/src/lib/table-text.ts +0 -554
  725. package/src/lib/text-case.ts +0 -19
  726. package/src/lib/text-match.ts +0 -169
  727. package/src/lib/thenable.ts +0 -19
  728. package/src/lib/translation-review.ts +0 -769
  729. package/src/notifications/device-zone-report.ts +0 -67
  730. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  731. package/src/notifications/feedback-notification-types.ts +0 -71
  732. package/src/notifications/notification-bell.tsx +0 -119
  733. package/src/notifications/notification-inbox.tsx +0 -309
  734. package/src/notifications/notification-parts.tsx +0 -81
  735. package/src/notifications/notification-types-setting.tsx +0 -406
  736. package/src/notifications/notification-types.ts +0 -89
  737. package/src/notifications/notifications-labels.ts +0 -287
  738. package/src/notifications/push-device-setting.tsx +0 -230
  739. package/src/notifications/push-devices-setting.tsx +0 -374
  740. package/src/notifications/push-worker.ts +0 -168
  741. package/src/notifications/quiet-hours-setting.tsx +0 -238
  742. package/src/notifications/unsubscribe-panel.tsx +0 -200
  743. package/src/notifications/use-push-device.ts +0 -448
  744. package/src/offline/local-first.ts +0 -100
  745. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  746. package/src/offline/pwa-update-prompt.tsx +0 -78
  747. package/src/offline/query-persistence.ts +0 -391
  748. package/src/offline/service-worker-updates.ts +0 -81
  749. package/src/offline/shared-computer-labels.ts +0 -8
  750. package/src/offline/shared-computer.tsx +0 -87
  751. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  752. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  753. package/src/offline.ts +0 -37
  754. package/src/push-worker.ts +0 -25
  755. package/src/rhf/fields.tsx +0 -2187
  756. package/src/rhf/form-core.tsx +0 -324
  757. package/src/rhf/form.tsx +0 -12
  758. package/src/rhf/line-items.tsx +0 -135
  759. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  760. package/src/rhf.ts +0 -21
  761. package/src/search/command-palette-labels.ts +0 -16
  762. package/src/search/command-palette.tsx +0 -624
  763. package/src/search/global-search-labels.ts +0 -13
  764. package/src/search/global-search.tsx +0 -660
  765. package/src/search/search-index.ts +0 -283
  766. package/src/search/use-command-key.ts +0 -23
  767. package/src/search.ts +0 -14
  768. package/src/settings/device-reset-setting-labels.ts +0 -20
  769. package/src/settings/device-reset-setting.tsx +0 -116
  770. package/src/settings/settings-card-classes.ts +0 -11
  771. package/src/settings/settings-catalogue.ts +0 -150
  772. package/src/settings/settings-context.ts +0 -33
  773. package/src/settings/settings-heading.tsx +0 -67
  774. package/src/settings/settings-labels.ts +0 -87
  775. package/src/settings/settings-layout.tsx +0 -471
  776. package/src/settings/settings-section.tsx +0 -108
  777. package/src/settings/use-account-appearance.ts +0 -148
  778. package/src/settings/use-account-language.ts +0 -146
  779. package/src/settings/use-settings-focus.ts +0 -128
  780. package/src/settings/use-settings-route.ts +0 -258
  781. package/src/shell/account-menu-language.tsx +0 -130
  782. package/src/shell/account-menu-text-size.tsx +0 -80
  783. package/src/shell/admin-menu.tsx +0 -183
  784. package/src/shell/app-shell-labels.ts +0 -11
  785. package/src/shell/app-shell.tsx +0 -1560
  786. package/src/shell/auth-layout.tsx +0 -186
  787. package/src/shell/company-switcher-labels.ts +0 -13
  788. package/src/shell/company-switcher.tsx +0 -136
  789. package/src/shell/dev-menu.tsx +0 -257
  790. package/src/shell/fill-height.tsx +0 -45
  791. package/src/shell/language-code.ts +0 -5
  792. package/src/shell/option-switcher-menu.tsx +0 -84
  793. package/src/shell/sync-status-indicator.tsx +0 -303
  794. package/src/shell/top-bar-actions.tsx +0 -87
  795. package/src/shell/top-bar-brand.tsx +0 -78
  796. package/src/shell/top-bar-parts-labels.ts +0 -148
  797. package/src/shell/top-bar.tsx +0 -40
  798. package/src/shell/topbar-action-menu.tsx +0 -567
  799. package/src/shell/topbar-controls.tsx +0 -269
  800. package/src/shell/tour-menu.tsx +0 -187
  801. package/src/shell.ts +0 -108
  802. package/src/sync/db.ts +0 -129
  803. package/src/sync/engine.ts +0 -1011
  804. package/src/sync/hooks.ts +0 -98
  805. package/src/sync/testing.ts +0 -73
  806. package/src/sync/wire.ts +0 -61
  807. package/src/sync.ts +0 -37
  808. package/src/table-text.ts +0 -8
  809. package/src/testing/css-variables.generated.ts +0 -112
  810. package/src/testing/css-variables.ts +0 -189
  811. package/src/testing/index.ts +0 -29
  812. package/src/testing/react.ts +0 -58
  813. package/src/testing/tour.ts +0 -41
  814. package/src/testing/viewport.ts +0 -357
  815. package/src/theme/chart-palette.ts +0 -232
  816. package/src/theme/color.ts +0 -272
  817. package/src/theme/contrast-tokens.ts +0 -268
  818. package/src/theme/contrast.ts +0 -250
  819. package/src/theme/palette-derive.ts +0 -825
  820. package/src/theme/palette-presets.ts +0 -544
  821. package/src/theme/palette-store.ts +0 -108
  822. package/src/theme/text-size.ts +0 -289
  823. package/src/theme/theme-store.ts +0 -157
  824. package/src/tour/tour-catalogue-labels.ts +0 -75
  825. package/src/tour/tour-catalogue.tsx +0 -335
  826. package/src/tour/tour-dock.ts +0 -134
  827. package/src/tour/tour-engine.ts +0 -37
  828. package/src/tour/tour-gates.ts +0 -218
  829. package/src/tour/tour-guide-button.tsx +0 -64
  830. package/src/tour/tour-guide-labels.ts +0 -15
  831. package/src/tour/tour-info.ts +0 -144
  832. package/src/tour/tour-labels.ts +0 -23
  833. package/src/tour/tour-overlay.tsx +0 -1170
  834. package/src/tour/tour.tsx +0 -750
  835. package/src/tour/use-tour-progress.ts +0 -671
  836. package/src/tour/use-tour-url-sync.ts +0 -266
  837. package/src/tour.ts +0 -21
  838. package/src/vite-env.d.ts +0 -23
  839. package/src/wizard/stepper-nav.tsx +0 -360
  840. package/src/wizard/types.ts +0 -275
  841. package/src/wizard/use-wizard.ts +0 -560
  842. package/src/wizard/validation.ts +0 -39
  843. package/src/wizard/wizard-context.tsx +0 -74
  844. package/src/wizard/wizard-labels.ts +0 -29
  845. package/src/wizard/wizard-step.tsx +0 -76
  846. package/src/wizard/wizard-summary.tsx +0 -84
  847. package/src/wizard.ts +0 -15
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState, useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n// The kit's one \"today\" (0.55): a day moved there is an override on, whatever `active` says.\nimport { subscribeToday, todayOverride } from \"../lib/dates\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n *\n * 0.55: a \"today\" the app moved with `setToday` (`@eifi1/ui-kit/dates`, the dev clock's\n * day) counts by itself — the dot is on while one is set, `active` or not.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n // Server render: no day is moved there.\n const movedToday = useSyncExternalStore(subscribeToday, todayOverride, () => null);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active || movedToday !== null ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `HeldChangesBanner`'s words — the `heldChanges` namespace of `<UiKitProvider labels>`
3
+ * (0.55, docs/top-bar-harmonization.md §2.6.1). The confirm's Cancel is
4
+ * `confirmDialog.cancel`.
5
+ */
6
+ export interface HeldChangesBannerLabels {
7
+ /** The banner's sentence when the app passes none: how many wait, that they stay on the
8
+ * device, and the two ways out. Says nothing of a way back, which the app may not have. */
9
+ message: (count: number) => string;
10
+ /** The way back — keksdose's switch to the budget the changes were made in. */
11
+ switchBack: string;
12
+ /** The button that asks first; the ellipsis says a question follows. */
13
+ discard: string;
14
+ /** The question, with the count that would be lost. */
15
+ confirmTitle: (count: number) => string;
16
+ /** The consequence under it: they are gone, and the device takes the server's state. */
17
+ confirmBody: (count: number) => string;
18
+ /** The button that answers it. */
19
+ confirmAction: string;
20
+ /** A switch back or a discard rejected and the app's `formatError` says nothing better. */
21
+ failed: string;
22
+ }
23
+ export declare const DEFAULT_HELD_CHANGES_BANNER_LABELS: HeldChangesBannerLabels;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ const DEFAULT_HELD_CHANGES_BANNER_LABELS = {
3
+ message: (count) => count === 1 ? "1 change cannot be sent from here. It stays on this device until it can be sent, or until you discard it." : `${count} changes cannot be sent from here. They stay on this device until they can be sent, or until you discard them.`,
4
+ switchBack: "Switch back",
5
+ discard: "Discard\u2026",
6
+ confirmTitle: (count) => count === 1 ? "Discard 1 change that cannot be sent from here?" : `Discard ${count} changes that cannot be sent from here?`,
7
+ confirmBody: (count) => count === 1 ? "It cannot be recovered. This device then syncs with what the server has." : "They cannot be recovered. This device then syncs with what the server has.",
8
+ confirmAction: "Discard",
9
+ failed: "That didn\u2019t work. Please try again."
10
+ };
11
+ export {
12
+ DEFAULT_HELD_CHANGES_BANNER_LABELS
13
+ };
14
+ //# sourceMappingURL=held-changes-banner-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/held-changes-banner-labels.ts"],"sourcesContent":["// The English words of ./held-changes-banner, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component (0.34, KS U8).\n// keksdose's reviewed words (sync.held_*, sync.discard_held_*, sync.switch_back_held) are\n// the canon, without its budgets: the kit cannot know why a change waits, only that it does.\n\n/**\n * `HeldChangesBanner`'s words — the `heldChanges` namespace of `<UiKitProvider labels>`\n * (0.55, docs/top-bar-harmonization.md §2.6.1). The confirm's Cancel is\n * `confirmDialog.cancel`.\n */\nexport interface HeldChangesBannerLabels {\n /** The banner's sentence when the app passes none: how many wait, that they stay on the\n * device, and the two ways out. Says nothing of a way back, which the app may not have. */\n message: (count: number) => string;\n /** The way back — keksdose's switch to the budget the changes were made in. */\n switchBack: string;\n /** The button that asks first; the ellipsis says a question follows. */\n discard: string;\n /** The question, with the count that would be lost. */\n confirmTitle: (count: number) => string;\n /** The consequence under it: they are gone, and the device takes the server's state. */\n confirmBody: (count: number) => string;\n /** The button that answers it. */\n confirmAction: string;\n /** A switch back or a discard rejected and the app's `formatError` says nothing better. */\n failed: string;\n}\n\nexport const DEFAULT_HELD_CHANGES_BANNER_LABELS: HeldChangesBannerLabels = {\n message: (count) =>\n count === 1\n ? \"1 change cannot be sent from here. It stays on this device until it can be sent, or until you discard it.\"\n : `${count} changes cannot be sent from here. They stay on this device until they can be sent, or until you discard them.`,\n switchBack: \"Switch back\",\n discard: \"Discard…\",\n confirmTitle: (count) =>\n count === 1\n ? \"Discard 1 change that cannot be sent from here?\"\n : `Discard ${count} changes that cannot be sent from here?`,\n confirmBody: (count) =>\n count === 1\n ? \"It cannot be recovered. This device then syncs with what the server has.\"\n : \"They cannot be recovered. This device then syncs with what the server has.\",\n confirmAction: \"Discard\",\n failed: \"That didn’t work. Please try again.\",\n};\n"],"mappings":";AA4BO,MAAM,qCAA8D;AAAA,EACzE,SAAS,CAAC,UACR,UAAU,IACN,8GACA,GAAG,KAAK;AAAA,EACd,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,cAAc,CAAC,UACb,UAAU,IACN,oDACA,WAAW,KAAK;AAAA,EACtB,aAAa,CAAC,UACZ,UAAU,IACN,6EACA;AAAA,EACN,eAAe;AAAA,EACf,QAAQ;AACV;","names":[]}
@@ -0,0 +1,75 @@
1
+ import type { ReactNode } from "react";
2
+ import { type LabelOverride } from "../i18n/kit-labels.js";
3
+ import { DEFAULT_HELD_CHANGES_BANNER_LABELS, type HeldChangesBannerLabels } from "./held-changes-banner-labels.js";
4
+ export { DEFAULT_HELD_CHANGES_BANNER_LABELS };
5
+ export type { HeldChangesBannerLabels };
6
+ export interface HeldChangesBannerProps {
7
+ /**
8
+ * Changes kept on this device that cannot be sent from where the user is now — the
9
+ * indicator's `blocked`, or the part of it this banner is about (kastlan: the ones the
10
+ * server refused). 0 renders nothing.
11
+ */
12
+ count: number;
13
+ /**
14
+ * The app's sentence: why they wait and what would send them — keksdose's "2 changes
15
+ * were made in “Holiday”, which is no longer the active budget…". Default
16
+ * `heldChanges.message(count)`, which names no reason and no way back.
17
+ */
18
+ message?: ReactNode;
19
+ /**
20
+ * Room under the sentence — kastlan's refusal reasons, as a list. For the box: a strip
21
+ * holds its message to one line on a phone, and a list behind "More" is a list nobody
22
+ * opens, so a strip with `extra` is not clamped.
23
+ */
24
+ extra?: ReactNode;
25
+ /**
26
+ * The way back, where there is one — keksdose's switch to the budget the changes were
27
+ * made in, which sends them. Without it Discard is the one way out. A returned promise
28
+ * keeps the button busy until it settles; a rejection is shown as Discard's is.
29
+ */
30
+ onSwitchBack?: () => Promise<unknown> | void;
31
+ /**
32
+ * Lets them go, once the user has said yes to the kit's confirm: the app drops what is
33
+ * held and syncs (keksdose `discardWaitingChanges`, kastlan `discardRejected` then
34
+ * `onSync`). Busy until the promise settles. A rejection leaves the banner where it is,
35
+ * with the error under the sentence; the count going to 0 is what removes it.
36
+ */
37
+ onDiscard: () => Promise<unknown> | void;
38
+ /** Turns a rejection into words. Default `heldChanges.failed`. */
39
+ formatError?: (error: unknown) => ReactNode;
40
+ /**
41
+ * `box` (default): a framed callout above a page (kastlan's handover pages); on a phone
42
+ * its actions take a line of their own under the sentence. `strip`: a band in the
43
+ * shell beside the app's other strips (keksdose), its message held to one line on a
44
+ * phone with "More" (`clampMobile`, keksdose dev#611), so the actions keep theirs.
45
+ */
46
+ variant?: "box" | "strip";
47
+ /** Prop > `<UiKitProvider labels={{ heldChanges }}>` > English. */
48
+ labels?: LabelOverride<HeldChangesBannerLabels>;
49
+ className?: string;
50
+ /** `data-*` attributes go on the banner — a tour's anchor (`data-tour="…"`). */
51
+ [key: `data-${string}`]: string | number | boolean | undefined;
52
+ }
53
+ /**
54
+ * Changes this device keeps and cannot send from where the user is now, with the two ways
55
+ * out: back to where they can be sent, or let them go (docs/top-bar-harmonization.md
56
+ * §2.6.1). `SyncStatusIndicator` shows the state (`blocked`, ranked first); this is the
57
+ * room for the action that one control in the top bar does not have.
58
+ *
59
+ * Two apps built it by hand:
60
+ * - **keksdose budget switch** (app/held-changes-banner.tsx): the active budget moved
61
+ * and the queue waits for the budget it was made in. A strip in the shell with "Switch
62
+ * back" and "Discard…", which asks first with the count, then discards and syncs.
63
+ * - **kastlan handover-sync-banner** (:57-68): the changes the server refused, a danger
64
+ * box with their reasons and a Discard that asked nothing.
65
+ *
66
+ * The kit's part is the banner and the question; what is held, how to discard it and the
67
+ * sync after are the app's (`onDiscard`). Warning, not danger: nothing is lost until the
68
+ * user says so, and the indicator in the bar already draws the danger. The safe action
69
+ * comes first, so the destructive one is never the only button somebody in a hurry finds
70
+ * (keksdose); Discard is then the quiet one. The confirm is `useConfirm`'s, in the danger
71
+ * tone (focus starts on Cancel), so the app needs a `ConfirmProvider` — all three mount
72
+ * one. Not behind the write lock: the changes are not the page's, and a viewer of what is
73
+ * on screen may still let go of what is held elsewhere (keksdose).
74
+ */
75
+ export declare function HeldChangesBanner(props: HeldChangesBannerProps): import("react").JSX.Element | null;
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { AlertBanner } from "../components/alert-banner.js";
5
+ import { Button } from "../components/button.js";
6
+ import { useConfirm } from "../components/confirm-dialog.js";
7
+ import { useKitLabels } from "../i18n/kit-labels.js";
8
+ import { hasContent } from "../lib/presence.js";
9
+ import { DEFAULT_HELD_CHANGES_BANNER_LABELS } from "./held-changes-banner-labels.js";
10
+ function HeldChangesBanner(props) {
11
+ if (!(props.count > 0)) return null;
12
+ return /* @__PURE__ */ jsx(HeldChangesBody, { ...props });
13
+ }
14
+ function HeldChangesBody({
15
+ count,
16
+ message,
17
+ extra,
18
+ onSwitchBack,
19
+ onDiscard,
20
+ formatError,
21
+ variant = "box",
22
+ labels: labelsProp,
23
+ className,
24
+ ...rest
25
+ }) {
26
+ const labels = useKitLabels("heldChanges", DEFAULT_HELD_CHANGES_BANNER_LABELS, labelsProp);
27
+ const confirm = useConfirm();
28
+ const [busy, setBusy] = useState(null);
29
+ const [failure, setFailure] = useState(null);
30
+ const mounted = useRef(true);
31
+ useEffect(() => {
32
+ mounted.current = true;
33
+ return () => {
34
+ mounted.current = false;
35
+ };
36
+ }, []);
37
+ const run = async (key, action) => {
38
+ setBusy(key);
39
+ setFailure(null);
40
+ try {
41
+ await action();
42
+ } catch (caught) {
43
+ if (!mounted.current) return;
44
+ const said = formatError?.(caught);
45
+ setFailure(hasContent(said) ? said : labels.failed);
46
+ } finally {
47
+ if (mounted.current) setBusy(null);
48
+ }
49
+ };
50
+ const discard = async () => {
51
+ const yes = await confirm({
52
+ title: labels.confirmTitle(count),
53
+ body: labels.confirmBody(count),
54
+ confirmLabel: labels.confirmAction,
55
+ tone: "danger"
56
+ });
57
+ if (yes && mounted.current) await run("discard", onDiscard);
58
+ };
59
+ const strip = variant === "strip";
60
+ return /* @__PURE__ */ jsxs(
61
+ AlertBanner,
62
+ {
63
+ ...rest,
64
+ tone: "warning",
65
+ variant: strip ? "strip" : "box",
66
+ actionPlacement: "own-line",
67
+ clampMobile: strip && !failure && !hasContent(extra),
68
+ className,
69
+ action: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
70
+ onSwitchBack && /* @__PURE__ */ jsx(
71
+ Button,
72
+ {
73
+ variant: "secondary",
74
+ size: "sm",
75
+ pending: busy === "switchBack",
76
+ disabled: busy === "discard",
77
+ onClick: () => void run("switchBack", onSwitchBack),
78
+ children: labels.switchBack
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsx(
82
+ Button,
83
+ {
84
+ variant: onSwitchBack ? "ghost" : "secondary",
85
+ tone: "danger",
86
+ size: "sm",
87
+ pending: busy === "discard",
88
+ disabled: busy === "switchBack",
89
+ onClick: () => void discard(),
90
+ children: labels.discard
91
+ }
92
+ )
93
+ ] }),
94
+ children: [
95
+ message ?? labels.message(count),
96
+ hasContent(extra) && /* @__PURE__ */ jsx("div", { className: "mt-1", children: extra }),
97
+ hasContent(failure) && /* @__PURE__ */ jsx(AlertBanner, { variant: "inline", tone: "danger", size: "sm", block: true, className: "mt-1", children: failure })
98
+ ]
99
+ }
100
+ );
101
+ }
102
+ export {
103
+ DEFAULT_HELD_CHANGES_BANNER_LABELS,
104
+ HeldChangesBanner
105
+ };
106
+ //# sourceMappingURL=held-changes-banner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shell/held-changes-banner.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { useConfirm } from \"../components/confirm-dialog\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { hasContent } from \"../lib/presence\";\nimport { DEFAULT_HELD_CHANGES_BANNER_LABELS, type HeldChangesBannerLabels } from \"./held-changes-banner-labels\";\n\n// Defined in ./held-changes-banner-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_HELD_CHANGES_BANNER_LABELS };\nexport type { HeldChangesBannerLabels };\n\nexport interface HeldChangesBannerProps {\n /**\n * Changes kept on this device that cannot be sent from where the user is now — the\n * indicator's `blocked`, or the part of it this banner is about (kastlan: the ones the\n * server refused). 0 renders nothing.\n */\n count: number;\n /**\n * The app's sentence: why they wait and what would send them — keksdose's \"2 changes\n * were made in “Holiday”, which is no longer the active budget…\". Default\n * `heldChanges.message(count)`, which names no reason and no way back.\n */\n message?: ReactNode;\n /**\n * Room under the sentence — kastlan's refusal reasons, as a list. For the box: a strip\n * holds its message to one line on a phone, and a list behind \"More\" is a list nobody\n * opens, so a strip with `extra` is not clamped.\n */\n extra?: ReactNode;\n /**\n * The way back, where there is one — keksdose's switch to the budget the changes were\n * made in, which sends them. Without it Discard is the one way out. A returned promise\n * keeps the button busy until it settles; a rejection is shown as Discard's is.\n */\n onSwitchBack?: () => Promise<unknown> | void;\n /**\n * Lets them go, once the user has said yes to the kit's confirm: the app drops what is\n * held and syncs (keksdose `discardWaitingChanges`, kastlan `discardRejected` then\n * `onSync`). Busy until the promise settles. A rejection leaves the banner where it is,\n * with the error under the sentence; the count going to 0 is what removes it.\n */\n onDiscard: () => Promise<unknown> | void;\n /** Turns a rejection into words. Default `heldChanges.failed`. */\n formatError?: (error: unknown) => ReactNode;\n /**\n * `box` (default): a framed callout above a page (kastlan's handover pages); on a phone\n * its actions take a line of their own under the sentence. `strip`: a band in the\n * shell beside the app's other strips (keksdose), its message held to one line on a\n * phone with \"More\" (`clampMobile`, keksdose dev#611), so the actions keep theirs.\n */\n variant?: \"box\" | \"strip\";\n /** Prop > `<UiKitProvider labels={{ heldChanges }}>` > English. */\n labels?: LabelOverride<HeldChangesBannerLabels>;\n className?: string;\n /** `data-*` attributes go on the banner — a tour's anchor (`data-tour=\"…\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/**\n * Changes this device keeps and cannot send from where the user is now, with the two ways\n * out: back to where they can be sent, or let them go (docs/top-bar-harmonization.md\n * §2.6.1). `SyncStatusIndicator` shows the state (`blocked`, ranked first); this is the\n * room for the action that one control in the top bar does not have.\n *\n * Two apps built it by hand:\n * - **keksdose budget switch** (app/held-changes-banner.tsx): the active budget moved\n * and the queue waits for the budget it was made in. A strip in the shell with \"Switch\n * back\" and \"Discard…\", which asks first with the count, then discards and syncs.\n * - **kastlan handover-sync-banner** (:57-68): the changes the server refused, a danger\n * box with their reasons and a Discard that asked nothing.\n *\n * The kit's part is the banner and the question; what is held, how to discard it and the\n * sync after are the app's (`onDiscard`). Warning, not danger: nothing is lost until the\n * user says so, and the indicator in the bar already draws the danger. The safe action\n * comes first, so the destructive one is never the only button somebody in a hurry finds\n * (keksdose); Discard is then the quiet one. The confirm is `useConfirm`'s, in the danger\n * tone (focus starts on Cancel), so the app needs a `ConfirmProvider` — all three mount\n * one. Not behind the write lock: the changes are not the page's, and a viewer of what is\n * on screen may still let go of what is held elsewhere (keksdose).\n */\nexport function HeldChangesBanner(props: HeldChangesBannerProps) {\n // Nothing held: nothing to say. The body's state (a running action, an error) goes with\n // it, so a later hold starts clean.\n if (!(props.count > 0)) return null;\n return <HeldChangesBody {...props} />;\n}\n\nfunction HeldChangesBody({\n count,\n message,\n extra,\n onSwitchBack,\n onDiscard,\n formatError,\n variant = \"box\",\n labels: labelsProp,\n className,\n ...rest\n}: HeldChangesBannerProps) {\n const labels = useKitLabels(\"heldChanges\", DEFAULT_HELD_CHANGES_BANNER_LABELS, labelsProp);\n const confirm = useConfirm();\n // One action at a time: a switch back racing a discard would send what is being dropped.\n const [busy, setBusy] = useState<\"switchBack\" | \"discard\" | null>(null);\n const [failure, setFailure] = useState<ReactNode>(null);\n // A discard that worked usually takes the banner with it (the count reaches 0) before\n // its promise settles here.\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = async (key: \"switchBack\" | \"discard\", action: () => Promise<unknown> | void) => {\n setBusy(key);\n setFailure(null);\n try {\n await action();\n } catch (caught) {\n if (!mounted.current) return;\n const said = formatError?.(caught);\n setFailure(hasContent(said) ? said : labels.failed);\n } finally {\n if (mounted.current) setBusy(null);\n }\n };\n\n const discard = async () => {\n const yes = await confirm({\n title: labels.confirmTitle(count),\n body: labels.confirmBody(count),\n confirmLabel: labels.confirmAction,\n tone: \"danger\",\n });\n if (yes && mounted.current) await run(\"discard\", onDiscard);\n };\n\n const strip = variant === \"strip\";\n return (\n <AlertBanner\n {...rest}\n tone=\"warning\"\n variant={strip ? \"strip\" : \"box\"}\n // A long sentence beside two buttons squeezed into a column on a phone (kastlan 53):\n // the actions get a line of their own under it. AlertBanner's default, said here.\n actionPlacement=\"own-line\"\n // Not while an error shows or `extra` is there: the clamp would hide them behind \"More\".\n clampMobile={strip && !failure && !hasContent(extra)}\n className={className}\n action={\n <div className=\"flex flex-wrap items-center gap-2\">\n {onSwitchBack && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n pending={busy === \"switchBack\"}\n disabled={busy === \"discard\"}\n onClick={() => void run(\"switchBack\", onSwitchBack)}\n >\n {labels.switchBack}\n </Button>\n )}\n <Button\n variant={onSwitchBack ? \"ghost\" : \"secondary\"}\n tone=\"danger\"\n size=\"sm\"\n pending={busy === \"discard\"}\n disabled={busy === \"switchBack\"}\n onClick={() => void discard()}\n >\n {labels.discard}\n </Button>\n </div>\n }\n >\n {message ?? labels.message(count)}\n {hasContent(extra) && <div className=\"mt-1\">{extra}</div>}\n {/* Inline, so it is a live region: the answer to the button just pressed is heard. */}\n {hasContent(failure) && (\n <AlertBanner variant=\"inline\" tone=\"danger\" size=\"sm\" block className=\"mt-1\">\n {failure}\n </AlertBanner>\n )}\n </AlertBanner>\n );\n}\n"],"mappings":";AAuFS,cAmED,YAnEC;AAvFT,SAAS,WAAW,QAAQ,gBAAgB;AAE5C,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,oBAAwC;AACjD,SAAS,kBAAkB;AAC3B,SAAS,0CAAwE;AA4E1E,SAAS,kBAAkB,OAA+B;AAG/D,MAAI,EAAE,MAAM,QAAQ,GAAI,QAAO;AAC/B,SAAO,oBAAC,mBAAiB,GAAG,OAAO;AACrC;AAEA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAA2B;AACzB,QAAM,SAAS,aAAa,eAAe,oCAAoC,UAAU;AACzF,QAAM,UAAU,WAAW;AAE3B,QAAM,CAAC,MAAM,OAAO,IAAI,SAA0C,IAAI;AACtE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoB,IAAI;AAGtD,QAAM,UAAU,OAAO,IAAI;AAC3B,YAAU,MAAM;AACd,YAAQ,UAAU;AAClB,WAAO,MAAM;AACX,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,MAAM,OAAO,KAA+B,WAA0C;AAC1F,YAAQ,GAAG;AACX,eAAW,IAAI;AACf,QAAI;AACF,YAAM,OAAO;AAAA,IACf,SAAS,QAAQ;AACf,UAAI,CAAC,QAAQ,QAAS;AACtB,YAAM,OAAO,cAAc,MAAM;AACjC,iBAAW,WAAW,IAAI,IAAI,OAAO,OAAO,MAAM;AAAA,IACpD,UAAE;AACA,UAAI,QAAQ,QAAS,SAAQ,IAAI;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,UAAU,YAAY;AAC1B,UAAM,MAAM,MAAM,QAAQ;AAAA,MACxB,OAAO,OAAO,aAAa,KAAK;AAAA,MAChC,MAAM,OAAO,YAAY,KAAK;AAAA,MAC9B,cAAc,OAAO;AAAA,MACrB,MAAM;AAAA,IACR,CAAC;AACD,QAAI,OAAO,QAAQ,QAAS,OAAM,IAAI,WAAW,SAAS;AAAA,EAC5D;AAEA,QAAM,QAAQ,YAAY;AAC1B,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,SAAS,QAAQ,UAAU;AAAA,MAG3B,iBAAgB;AAAA,MAEhB,aAAa,SAAS,CAAC,WAAW,CAAC,WAAW,KAAK;AAAA,MACnD;AAAA,MACA,QACE,qBAAC,SAAI,WAAU,qCACZ;AAAA,wBACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,SAAS;AAAA,YAClB,UAAU,SAAS;AAAA,YACnB,SAAS,MAAM,KAAK,IAAI,cAAc,YAAY;AAAA,YAEjD,iBAAO;AAAA;AAAA,QACV;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,eAAe,UAAU;AAAA,YAClC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,SAAS,SAAS;AAAA,YAClB,UAAU,SAAS;AAAA,YACnB,SAAS,MAAM,KAAK,QAAQ;AAAA,YAE3B,iBAAO;AAAA;AAAA,QACV;AAAA,SACF;AAAA,MAGD;AAAA,mBAAW,OAAO,QAAQ,KAAK;AAAA,QAC/B,WAAW,KAAK,KAAK,oBAAC,SAAI,WAAU,QAAQ,iBAAM;AAAA,QAElD,WAAW,OAAO,KACjB,oBAAC,eAAY,SAAQ,UAAS,MAAK,UAAS,MAAK,MAAK,OAAK,MAAC,WAAU,QACnE,mBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -19,15 +19,27 @@ export interface SyncStatus {
19
19
  waitingForPlan?: number;
20
20
  /** When the last good round ended — epoch ms, an ISO string or a `Date`. */
21
21
  lastSyncedAt?: number | string | Date | null;
22
+ /**
23
+ * 0.55 (held back from 0.53): a round of this tab waits for another tab's on the same
24
+ * device copy — the sync engine's Web Lock (0.52) lets one tab's round run at a time — so
25
+ * nothing goes out from here until that one ends. `useSyncStatus` fills it from the
26
+ * engine's `waitingForTab`.
27
+ */
28
+ waitingForTab?: boolean;
22
29
  }
23
- /** The seven states, in their ranking: the first that holds wins. */
24
- export type SyncState = "blocked" | "waitingForPlan" | "offline" | "error" | "syncing" | "pending" | "synced";
30
+ /** The eight states, in their ranking: the first that holds wins. */
31
+ export type SyncState = "blocked" | "waitingForPlan" | "offline" | "waitingForTab" | "error" | "syncing" | "pending" | "synced";
25
32
  /**
26
- * Which of the seven states a {@link SyncStatus} is in — keksdose's ranking
33
+ * Which of the eight states a {@link SyncStatus} is in — keksdose's ranking
27
34
  * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,
28
35
  * because waiting cannot help it; then one waiting for a plan; then offline; then a refused
29
36
  * round; then a round under way; then writes waiting; else in sync. `syncing` also holds
30
37
  * while a tap's own round runs (`refreshing`), before the engine publishes its phase.
38
+ *
39
+ * 0.55 (held back from 0.53): a round waiting for another tab's (`waitingForTab`) ranks
40
+ * after offline, which it cannot help either, and before the rest: it is the start of a
41
+ * round, which replaces an error as `syncing` does once it runs, and the engine never says
42
+ * it beside `syncing` — so it is what a tap's own round shows while it waits its turn.
31
43
  */
32
44
  export declare function syncState(status: SyncStatus, refreshing?: boolean): SyncState;
33
45
  /** The scope words of an indicator that covers only part of the app (§6.10). */
@@ -46,7 +58,8 @@ export interface SyncStatusIndicatorProps {
46
58
  * Run a round now (push, then pull, then re-read what is on screen). With it the
47
59
  * indicator is a "sync now" button, disabled offline and mid-round but keeping its
48
60
  * colour; a returned promise keeps the spin until it settles. Without it, a status only
49
- * (§6.9).
61
+ * (§6.9). Disabled too while a round waits for another tab's (0.55): a tap would only
62
+ * queue one more behind it.
50
63
  */
51
64
  onSync?: () => void | Promise<unknown>;
52
65
  /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,
@@ -59,11 +72,12 @@ export interface SyncStatusIndicatorProps {
59
72
  * offline-first app (keksdose, kastlan's handovers) — offline means "kept here, sent
60
73
  * later". `"none"`: an online-only app that keeps nothing on the device (Kurvenschmiede,
61
74
  * build review B1) — offline reads "changes can't be saved until you reconnect", a
62
- * round reads "Saving…", and `pending`, `blocked` and `waitingForPlan` are ignored,
63
- * since there is nowhere for a change to wait. "Up to date" when settled.
75
+ * round reads "Saving…", and `pending`, `blocked`, `waitingForPlan` and `waitingForTab`
76
+ * are ignored, since there is nowhere for a change to wait. "Up to date" when settled.
64
77
  */
65
78
  storage?: "device" | "none";
66
- /** Short words beside the cloud ("Offline", "Syncing…", "3 waiting") while not in sync. */
79
+ /** Short words beside the cloud ("Offline", "Syncing…", "3 waiting", "Waiting for tab")
80
+ * while not in sync. */
67
81
  showLabel?: boolean;
68
82
  /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */
69
83
  labels?: LabelOverride<SyncStatusLabels>;
@@ -75,12 +89,13 @@ export interface SyncStatusIndicatorProps {
75
89
  }
76
90
  /**
77
91
  * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's
78
- * `SyncStatusIndicator`: the seven states and their ranking ({@link syncState}), one cloud
79
- * per state, a ✓ badge when in sync and a spin while syncing. With `onSync` it is also the
80
- * "sync now" button, and its name says both what it is and what a tap does.
92
+ * `SyncStatusIndicator`: its seven states and their ranking ({@link syncState}; 0.55 adds an
93
+ * eighth, "Waiting for another tab", an hourglass), one cloud per state, a ✓ badge when in
94
+ * sync and a spin while syncing. With `onSync` it is also the "sync now" button, and its
95
+ * name says both what it is and what a tap does.
81
96
  *
82
97
  * Colours are the kit's roles — danger for a write that cannot be sent, warning for
83
- * anything waiting, info while syncing, success when in sync — where keksdose had raw
84
- * rose/amber/sky/emerald pairs.
98
+ * anything waiting, info while syncing (or waiting for another tab's round), success when
99
+ * in sync — where keksdose had raw rose/amber/sky/emerald pairs.
85
100
  */
86
101
  export declare function SyncStatusIndicator({ status, onSync, hideWhenIdle, scope, showLabel, labels: labelsProp, locale: localeProp, className, storage, ...rest }: SyncStatusIndicatorProps): import("react").JSX.Element | null;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
- import { Check, Cloud, CloudAlert, CloudOff, RefreshCw, UploadCloud } from "lucide-react";
4
+ import { Check, Cloud, CloudAlert, CloudOff, Hourglass, RefreshCw, UploadCloud } from "lucide-react";
5
5
  import { cn } from "../lib/cn.js";
6
6
  import { IconButton } from "../components/icon-button.js";
7
7
  import { Tooltip } from "../components/tooltip.js";
@@ -12,6 +12,7 @@ function syncState(status, refreshing = false) {
12
12
  if ((status.blocked ?? 0) > 0) return "blocked";
13
13
  if ((status.waitingForPlan ?? 0) > 0) return "waitingForPlan";
14
14
  if (!status.online || status.phase === "offline") return "offline";
15
+ if (status.waitingForTab) return "waitingForTab";
15
16
  if (status.phase === "error") return "error";
16
17
  if (status.phase === "syncing" || refreshing) return "syncing";
17
18
  if (status.pending > 0) return "pending";
@@ -23,6 +24,8 @@ const LOOK = {
23
24
  // Safe, and a payment sends them: amber, the kit's warning.
24
25
  waitingForPlan: { Icon: CloudAlert, tone: "warning" },
25
26
  offline: { Icon: CloudOff, tone: "warning" },
27
+ // A round under way, in another tab: info, as syncing, without the spin.
28
+ waitingForTab: { Icon: Hourglass, tone: "info" },
26
29
  // Usually resolves by itself (a deploy, a read-only view): amber, not danger.
27
30
  error: { Icon: CloudAlert, tone: "warning" },
28
31
  syncing: { Icon: RefreshCw, tone: "info" },
@@ -62,7 +65,10 @@ function SyncStatusIndicator({
62
65
  };
63
66
  }, []);
64
67
  const onlineOnly = storage === "none";
65
- const state = syncState(onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0 } : status, refreshing);
68
+ const state = syncState(
69
+ onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0, waitingForTab: false } : status,
70
+ refreshing
71
+ );
66
72
  const data = dataAttributes(rest);
67
73
  if (hideWhenIdle && state === "synced") return null;
68
74
  const { Icon, tone } = LOOK[state];
@@ -78,6 +84,9 @@ function SyncStatusIndicator({
78
84
  case "offline":
79
85
  text = onlineOnly ? words.offlineUnsaved : words.offline;
80
86
  break;
87
+ case "waitingForTab":
88
+ text = words.waitingForTab;
89
+ break;
81
90
  case "error":
82
91
  text = words.refused;
83
92
  break;
@@ -96,11 +105,11 @@ function SyncStatusIndicator({
96
105
  if (scope) {
97
106
  text = state === "offline" && scope.offline ? scope.offline : words.scoped(scope.name, text);
98
107
  }
99
- const short = state === "offline" ? words.offlineShort : state === "syncing" ? onlineOnly ? words.saving : words.syncingShort : state === "error" ? words.refusedShort : state === "pending" || state === "blocked" || state === "waitingForPlan" ? words.waitingShort(
108
+ const short = state === "offline" ? words.offlineShort : state === "syncing" ? onlineOnly ? words.saving : words.syncingShort : state === "error" ? words.refusedShort : state === "waitingForTab" ? words.waitingForTabShort : state === "pending" || state === "blocked" || state === "waitingForPlan" ? words.waitingShort(
100
109
  state === "pending" ? status.pending : state === "blocked" ? status.blocked ?? 0 : status.waitingForPlan ?? 0
101
110
  ) : null;
102
111
  const offline = state === "offline";
103
- const canSync = !!onSync && status.online && status.phase !== "offline" && !spin;
112
+ const canSync = !!onSync && status.online && status.phase !== "offline" && !spin && state !== "waitingForTab";
104
113
  const name = onSync ? canSync ? words.withHint(text, words.syncHint) : offline ? words.withHint(text, words.offlineHint) : text : text;
105
114
  const badge = state === "synced" ? /* @__PURE__ */ jsx(Check, { className: "stroke-[3]" }) : void 0;
106
115
  const glyph = /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: spin ? "animate-spin" : void 0 });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/sync-status-indicator.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ComponentType } from \"react\";\nimport { Check, Cloud, CloudAlert, CloudOff, RefreshCw, UploadCloud } from \"lucide-react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"../components/icon-button\";\nimport { Tooltip } from \"../components/tooltip\";\nimport { useBillingLabels } from \"../billing/billing-labels\";\nimport { useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useTopBarPartsLabels, type SyncStatusLabels } from \"./top-bar-parts-labels\";\n\n/**\n * What the indicator shows, fed by the app's sync engine (docs/top-bar-harmonization.md\n * §2.6). Small on purpose: keksdose's sync-status store, kastlan's handover controller and\n * Kurvenschmiede's online state + `useIsMutating` all fill it.\n */\nexport interface SyncStatus {\n /** What the engine is doing. `offline` is the engine's own verdict; `online: false` is\n * the browser's — either shows the offline state. */\n phase: \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** The browser's `navigator.onLine` (or the app's better guess). */\n online: boolean;\n /** Writes kept on this device and not yet sent. */\n pending: number;\n /** Writes this device cannot send at all (keksdose: no key for the budget). Ranked first. */\n blocked?: number;\n /** Writes the server refused as read-only and keeps until a plan (billing §12.5). */\n waitingForPlan?: number;\n /** When the last good round ended — epoch ms, an ISO string or a `Date`. */\n lastSyncedAt?: number | string | Date | null;\n}\n\n/** The seven states, in their ranking: the first that holds wins. */\nexport type SyncState = \"blocked\" | \"waitingForPlan\" | \"offline\" | \"error\" | \"syncing\" | \"pending\" | \"synced\";\n\n/**\n * Which of the seven states a {@link SyncStatus} is in — keksdose's ranking\n * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,\n * because waiting cannot help it; then one waiting for a plan; then offline; then a refused\n * round; then a round under way; then writes waiting; else in sync. `syncing` also holds\n * while a tap's own round runs (`refreshing`), before the engine publishes its phase.\n */\nexport function syncState(status: SyncStatus, refreshing = false): SyncState {\n if ((status.blocked ?? 0) > 0) return \"blocked\";\n if ((status.waitingForPlan ?? 0) > 0) return \"waitingForPlan\";\n if (!status.online || status.phase === \"offline\") return \"offline\";\n if (status.phase === \"error\") return \"error\";\n if (status.phase === \"syncing\" || refreshing) return \"syncing\";\n if (status.pending > 0) return \"pending\";\n return \"synced\";\n}\n\ntype Tone = \"danger\" | \"warning\" | \"info\" | \"success\";\n\nconst LOOK: Record<SyncState, { Icon: ComponentType<LucideProps>; tone: Tone }> = {\n // The one the user must act on: the kit's danger, where keksdose had rose.\n blocked: { Icon: CloudAlert, tone: \"danger\" },\n // Safe, and a payment sends them: amber, the kit's warning.\n waitingForPlan: { Icon: CloudAlert, tone: \"warning\" },\n offline: { Icon: CloudOff, tone: \"warning\" },\n // Usually resolves by itself (a deploy, a read-only view): amber, not danger.\n error: { Icon: CloudAlert, tone: \"warning\" },\n syncing: { Icon: RefreshCw, tone: \"info\" },\n pending: { Icon: UploadCloud, tone: \"warning\" },\n synced: { Icon: Cloud, tone: \"success\" },\n};\n\nconst TEXT_TONE: Record<Tone, string> = {\n danger: \"text-danger\",\n warning: \"text-warning\",\n info: \"text-info\",\n success: \"text-success\",\n};\n\n/** The scope words of an indicator that covers only part of the app (§6.10). */\nexport interface SyncStatusScope {\n /** Put before every state in the tooltip: \"Handovers on this device\" →\n * \"Handovers on this device: 3 changes waiting to sync\". */\n name: string;\n /** The whole offline sentence instead of the kit's, when offline means something\n * narrower: \"Offline: handovers keep working on this device; everything else needs a\n * connection\". */\n offline?: string;\n}\n\nexport interface SyncStatusIndicatorProps {\n status: SyncStatus;\n /**\n * Run a round now (push, then pull, then re-read what is on screen). With it the\n * indicator is a \"sync now\" button, disabled offline and mid-round but keeping its\n * colour; a returned promise keeps the spin until it settles. Without it, a status only\n * (§6.9).\n */\n onSync?: () => void | Promise<unknown>;\n /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,\n * syncing or with changes waiting, held or refused (§2.6). */\n hideWhenIdle?: boolean;\n /** The app's words for its part of the app (§6.10). */\n scope?: SyncStatusScope;\n /**\n * Where the app keeps a change before the server has it. `\"device\"` (the default): an\n * offline-first app (keksdose, kastlan's handovers) — offline means \"kept here, sent\n * later\". `\"none\"`: an online-only app that keeps nothing on the device (Kurvenschmiede,\n * build review B1) — offline reads \"changes can't be saved until you reconnect\", a\n * round reads \"Saving…\", and `pending`, `blocked` and `waitingForPlan` are ignored,\n * since there is nowhere for a change to wait. \"Up to date\" when settled.\n */\n storage?: \"device\" | \"none\";\n /** Short words beside the cloud (\"Offline\", \"Syncing…\", \"3 waiting\") while not in sync. */\n showLabel?: boolean;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */\n labels?: LabelOverride<SyncStatusLabels>;\n /** The clock's language for \"Synced at 14:05\". Default: the provider's locale. */\n locale?: string;\n className?: string;\n /** `data-*` attributes go on the trigger — a tour's anchor (`data-tour=\"sync-status\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** The `data-*` attributes of a rest object, for a trigger. */\nfunction dataAttributes(rest: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith(\"data-\")));\n}\n\n/**\n * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's\n * `SyncStatusIndicator`: the seven states and their ranking ({@link syncState}), one cloud\n * per state, a ✓ badge when in sync and a spin while syncing. With `onSync` it is also the\n * \"sync now\" button, and its name says both what it is and what a tap does.\n *\n * Colours are the kit's roles — danger for a write that cannot be sent, warning for\n * anything waiting, info while syncing, success when in sync — where keksdose had raw\n * rose/amber/sky/emerald pairs.\n */\nexport function SyncStatusIndicator({\n status,\n onSync,\n hideWhenIdle = false,\n scope,\n showLabel = false,\n labels: labelsProp,\n locale: localeProp,\n className,\n storage = \"device\",\n ...rest\n}: SyncStatusIndicatorProps) {\n const words = useTopBarPartsLabels(labelsProp ? { sync: labelsProp } : undefined).sync;\n const billing = useBillingLabels();\n const locale = useKitLocale(localeProp);\n // The tap's own round, local rather than in the app's store: the store's phase belongs\n // to the sync engine, and a second writer would race the round itself.\n const [refreshing, setRefreshing] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const onlineOnly = storage === \"none\";\n // Online-only: nothing waits on the device, so only offline, error, saving and settled.\n const state = syncState(onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0 } : status, refreshing);\n const data = dataAttributes(rest);\n if (hideWhenIdle && state === \"synced\") return null;\n\n const { Icon, tone } = LOOK[state];\n const spin = state === \"syncing\";\n let text: string;\n switch (state) {\n case \"blocked\":\n text = words.blocked(status.blocked ?? 0);\n break;\n case \"waitingForPlan\":\n text = billing.waitingChanges(status.waitingForPlan ?? 0);\n break;\n case \"offline\":\n text = onlineOnly ? words.offlineUnsaved : words.offline;\n break;\n case \"error\":\n text = words.refused;\n break;\n case \"syncing\":\n text = onlineOnly ? words.saving : words.syncing;\n break;\n case \"pending\":\n text = words.pending(status.pending);\n break;\n default:\n text =\n !onlineOnly && status.lastSyncedAt\n ? words.syncedAt(\n // A clock time in the UI's language, never the browser's (keksdose live #246).\n new Date(status.lastSyncedAt).toLocaleTimeString(locale, { hour: \"2-digit\", minute: \"2-digit\" }),\n )\n : words.idle;\n }\n if (scope) {\n text = state === \"offline\" && scope.offline ? scope.offline : words.scoped(scope.name, text);\n }\n\n const short =\n state === \"offline\"\n ? words.offlineShort\n : state === \"syncing\"\n ? onlineOnly\n ? words.saving\n : words.syncingShort\n : state === \"error\"\n ? words.refusedShort\n : state === \"pending\" || state === \"blocked\" || state === \"waitingForPlan\"\n ? words.waitingShort(\n state === \"pending\"\n ? status.pending\n : state === \"blocked\"\n ? (status.blocked ?? 0)\n : (status.waitingForPlan ?? 0),\n )\n : null;\n\n const offline = state === \"offline\";\n const canSync = !!onSync && status.online && status.phase !== \"offline\" && !spin;\n const name = onSync\n ? canSync\n ? words.withHint(text, words.syncHint)\n : offline\n ? words.withHint(text, words.offlineHint)\n : text\n : text;\n const badge = state === \"synced\" ? <Check className=\"stroke-[3]\" /> : undefined;\n const glyph = <Icon aria-hidden className={spin ? \"animate-spin\" : undefined} />;\n\n const sync = async () => {\n if (!canSync || !onSync) return;\n setRefreshing(true);\n try {\n await onSync();\n } finally {\n if (mounted.current) setRefreshing(false);\n }\n };\n\n const control = onSync ? (\n <IconButton\n {...data}\n data-sync-state={state}\n size=\"2xs\"\n glyphSize={20}\n shape=\"round\"\n // A status that is also an action: disabled (offline, mid-round) is exactly when\n // the state matters most, so it keeps its colour.\n disabledStyle=\"keep\"\n tone={tone}\n quiet={false}\n label={name}\n labelVisible={false}\n // Below: the bar is at the top edge, where a bubble above has no room.\n tooltipSide=\"bottom\"\n onClick={() => void sync()}\n disabled={!canSync}\n // It announces itself as a status: a reader hears the state change.\n aria-live=\"polite\"\n badge={badge}\n >\n {glyph}\n </IconButton>\n ) : (\n <Tooltip label={name} side=\"bottom\">\n <span\n {...data}\n role=\"status\"\n data-sync-state={state}\n className={cn(\n \"relative inline-flex size-9 shrink-0 items-center justify-center [&>svg]:size-5\",\n TEXT_TONE[tone],\n )}\n >\n {glyph}\n <span className=\"sr-only\">{name}</span>\n {badge && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute bottom-1.5 end-1.5 inline-flex rounded-full bg-surface [&_svg]:size-3\"\n >\n {badge}\n </span>\n )}\n </span>\n </Tooltip>\n );\n\n if (!showLabel || !short)\n return className ? <span className={cn(\"inline-flex\", className)}>{control}</span> : control;\n return (\n <span className={cn(\"inline-flex items-center gap-1\", className)}>\n {control}\n {/* The eye's short words; the name above already says it in full. */}\n <span aria-hidden className={cn(\"whitespace-nowrap text-xs font-medium\", TEXT_TONE[tone])}>\n {short}\n </span>\n </span>\n );\n}\n"],"mappings":";AAqOqC,cAuC/B,YAvC+B;AArOrC,SAAS,WAAW,QAAQ,gBAAgB;AAE5C,SAAS,OAAO,OAAO,YAAY,UAAU,WAAW,mBAAmB;AAE3E,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,oBAAwC;AACjD,SAAS,4BAAmD;AAiCrD,SAAS,UAAU,QAAoB,aAAa,OAAkB;AAC3E,OAAK,OAAO,WAAW,KAAK,EAAG,QAAO;AACtC,OAAK,OAAO,kBAAkB,KAAK,EAAG,QAAO;AAC7C,MAAI,CAAC,OAAO,UAAU,OAAO,UAAU,UAAW,QAAO;AACzD,MAAI,OAAO,UAAU,QAAS,QAAO;AACrC,MAAI,OAAO,UAAU,aAAa,WAAY,QAAO;AACrD,MAAI,OAAO,UAAU,EAAG,QAAO;AAC/B,SAAO;AACT;AAIA,MAAM,OAA4E;AAAA;AAAA,EAEhF,SAAS,EAAE,MAAM,YAAY,MAAM,SAAS;AAAA;AAAA,EAE5C,gBAAgB,EAAE,MAAM,YAAY,MAAM,UAAU;AAAA,EACpD,SAAS,EAAE,MAAM,UAAU,MAAM,UAAU;AAAA;AAAA,EAE3C,OAAO,EAAE,MAAM,YAAY,MAAM,UAAU;AAAA,EAC3C,SAAS,EAAE,MAAM,WAAW,MAAM,OAAO;AAAA,EACzC,SAAS,EAAE,MAAM,aAAa,MAAM,UAAU;AAAA,EAC9C,QAAQ,EAAE,MAAM,OAAO,MAAM,UAAU;AACzC;AAEA,MAAM,YAAkC;AAAA,EACtC,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AACX;AAgDA,SAAS,eAAe,MAAwD;AAC9E,SAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC,CAAC;AACvF;AAYO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAA6B;AAC3B,QAAM,QAAQ,qBAAqB,aAAa,EAAE,MAAM,WAAW,IAAI,MAAS,EAAE;AAClF,QAAM,UAAU,iBAAiB;AACjC,QAAM,SAAS,aAAa,UAAU;AAGtC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,UAAU,OAAO,IAAI;AAC3B,YAAU,MAAM;AACd,YAAQ,UAAU;AAClB,WAAO,MAAM;AACX,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,YAAY;AAE/B,QAAM,QAAQ,UAAU,aAAa,EAAE,GAAG,QAAQ,SAAS,GAAG,SAAS,GAAG,gBAAgB,EAAE,IAAI,QAAQ,UAAU;AAClH,QAAM,OAAO,eAAe,IAAI;AAChC,MAAI,gBAAgB,UAAU,SAAU,QAAO;AAE/C,QAAM,EAAE,MAAM,KAAK,IAAI,KAAK,KAAK;AACjC,QAAM,OAAO,UAAU;AACvB,MAAI;AACJ,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,MAAM,QAAQ,OAAO,WAAW,CAAC;AACxC;AAAA,IACF,KAAK;AACH,aAAO,QAAQ,eAAe,OAAO,kBAAkB,CAAC;AACxD;AAAA,IACF,KAAK;AACH,aAAO,aAAa,MAAM,iBAAiB,MAAM;AACjD;AAAA,IACF,KAAK;AACH,aAAO,MAAM;AACb;AAAA,IACF,KAAK;AACH,aAAO,aAAa,MAAM,SAAS,MAAM;AACzC;AAAA,IACF,KAAK;AACH,aAAO,MAAM,QAAQ,OAAO,OAAO;AACnC;AAAA,IACF;AACE,aACE,CAAC,cAAc,OAAO,eAClB,MAAM;AAAA;AAAA,QAEJ,IAAI,KAAK,OAAO,YAAY,EAAE,mBAAmB,QAAQ,EAAE,MAAM,WAAW,QAAQ,UAAU,CAAC;AAAA,MACjG,IACA,MAAM;AAAA,EAChB;AACA,MAAI,OAAO;AACT,WAAO,UAAU,aAAa,MAAM,UAAU,MAAM,UAAU,MAAM,OAAO,MAAM,MAAM,IAAI;AAAA,EAC7F;AAEA,QAAM,QACJ,UAAU,YACN,MAAM,eACN,UAAU,YACR,aACE,MAAM,SACN,MAAM,eACR,UAAU,UACR,MAAM,eACN,UAAU,aAAa,UAAU,aAAa,UAAU,mBACtD,MAAM;AAAA,IACJ,UAAU,YACN,OAAO,UACP,UAAU,YACP,OAAO,WAAW,IAClB,OAAO,kBAAkB;AAAA,EAClC,IACA;AAEZ,QAAM,UAAU,UAAU;AAC1B,QAAM,UAAU,CAAC,CAAC,UAAU,OAAO,UAAU,OAAO,UAAU,aAAa,CAAC;AAC5E,QAAM,OAAO,SACT,UACE,MAAM,SAAS,MAAM,MAAM,QAAQ,IACnC,UACE,MAAM,SAAS,MAAM,MAAM,WAAW,IACtC,OACJ;AACJ,QAAM,QAAQ,UAAU,WAAW,oBAAC,SAAM,WAAU,cAAa,IAAK;AACtE,QAAM,QAAQ,oBAAC,QAAK,eAAW,MAAC,WAAW,OAAO,iBAAiB,QAAW;AAE9E,QAAM,OAAO,YAAY;AACvB,QAAI,CAAC,WAAW,CAAC,OAAQ;AACzB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,OAAO;AAAA,IACf,UAAE;AACA,UAAI,QAAQ,QAAS,eAAc,KAAK;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,UAAU,SACd;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,mBAAiB;AAAA,MACjB,MAAK;AAAA,MACL,WAAW;AAAA,MACX,OAAM;AAAA,MAGN,eAAc;AAAA,MACd;AAAA,MACA,OAAO;AAAA,MACP,OAAO;AAAA,MACP,cAAc;AAAA,MAEd,aAAY;AAAA,MACZ,SAAS,MAAM,KAAK,KAAK;AAAA,MACzB,UAAU,CAAC;AAAA,MAEX,aAAU;AAAA,MACV;AAAA,MAEC;AAAA;AAAA,EACH,IAEA,oBAAC,WAAQ,OAAO,MAAM,MAAK,UACzB;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA,UAAU,IAAI;AAAA,MAChB;AAAA,MAEC;AAAA;AAAA,QACD,oBAAC,UAAK,WAAU,WAAW,gBAAK;AAAA,QAC/B,SACC;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ,GACF;AAGF,MAAI,CAAC,aAAa,CAAC;AACjB,WAAO,YAAY,oBAAC,UAAK,WAAW,GAAG,eAAe,SAAS,GAAI,mBAAQ,IAAU;AACvF,SACE,qBAAC,UAAK,WAAW,GAAG,kCAAkC,SAAS,GAC5D;AAAA;AAAA,IAED,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,yCAAyC,UAAU,IAAI,CAAC,GACrF,iBACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/sync-status-indicator.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { ComponentType } from \"react\";\nimport { Check, Cloud, CloudAlert, CloudOff, Hourglass, RefreshCw, UploadCloud } from \"lucide-react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { IconButton } from \"../components/icon-button\";\nimport { Tooltip } from \"../components/tooltip\";\nimport { useBillingLabels } from \"../billing/billing-labels\";\nimport { useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useTopBarPartsLabels, type SyncStatusLabels } from \"./top-bar-parts-labels\";\n\n/**\n * What the indicator shows, fed by the app's sync engine (docs/top-bar-harmonization.md\n * §2.6). Small on purpose: keksdose's sync-status store, kastlan's handover controller and\n * Kurvenschmiede's online state + `useIsMutating` all fill it.\n */\nexport interface SyncStatus {\n /** What the engine is doing. `offline` is the engine's own verdict; `online: false` is\n * the browser's — either shows the offline state. */\n phase: \"idle\" | \"syncing\" | \"offline\" | \"error\";\n /** The browser's `navigator.onLine` (or the app's better guess). */\n online: boolean;\n /** Writes kept on this device and not yet sent. */\n pending: number;\n /** Writes this device cannot send at all (keksdose: no key for the budget). Ranked first. */\n blocked?: number;\n /** Writes the server refused as read-only and keeps until a plan (billing §12.5). */\n waitingForPlan?: number;\n /** When the last good round ended — epoch ms, an ISO string or a `Date`. */\n lastSyncedAt?: number | string | Date | null;\n /**\n * 0.55 (held back from 0.53): a round of this tab waits for another tab's on the same\n * device copy — the sync engine's Web Lock (0.52) lets one tab's round run at a time — so\n * nothing goes out from here until that one ends. `useSyncStatus` fills it from the\n * engine's `waitingForTab`.\n */\n waitingForTab?: boolean;\n}\n\n/** The eight states, in their ranking: the first that holds wins. */\nexport type SyncState =\n \"blocked\" | \"waitingForPlan\" | \"offline\" | \"waitingForTab\" | \"error\" | \"syncing\" | \"pending\" | \"synced\";\n\n/**\n * Which of the eight states a {@link SyncStatus} is in — keksdose's ranking\n * (sync-status-indicator.tsx): a write that cannot be sent at all ranks above everything,\n * because waiting cannot help it; then one waiting for a plan; then offline; then a refused\n * round; then a round under way; then writes waiting; else in sync. `syncing` also holds\n * while a tap's own round runs (`refreshing`), before the engine publishes its phase.\n *\n * 0.55 (held back from 0.53): a round waiting for another tab's (`waitingForTab`) ranks\n * after offline, which it cannot help either, and before the rest: it is the start of a\n * round, which replaces an error as `syncing` does once it runs, and the engine never says\n * it beside `syncing` — so it is what a tap's own round shows while it waits its turn.\n */\nexport function syncState(status: SyncStatus, refreshing = false): SyncState {\n if ((status.blocked ?? 0) > 0) return \"blocked\";\n if ((status.waitingForPlan ?? 0) > 0) return \"waitingForPlan\";\n if (!status.online || status.phase === \"offline\") return \"offline\";\n if (status.waitingForTab) return \"waitingForTab\";\n if (status.phase === \"error\") return \"error\";\n if (status.phase === \"syncing\" || refreshing) return \"syncing\";\n if (status.pending > 0) return \"pending\";\n return \"synced\";\n}\n\ntype Tone = \"danger\" | \"warning\" | \"info\" | \"success\";\n\nconst LOOK: Record<SyncState, { Icon: ComponentType<LucideProps>; tone: Tone }> = {\n // The one the user must act on: the kit's danger, where keksdose had rose.\n blocked: { Icon: CloudAlert, tone: \"danger\" },\n // Safe, and a payment sends them: amber, the kit's warning.\n waitingForPlan: { Icon: CloudAlert, tone: \"warning\" },\n offline: { Icon: CloudOff, tone: \"warning\" },\n // A round under way, in another tab: info, as syncing, without the spin.\n waitingForTab: { Icon: Hourglass, tone: \"info\" },\n // Usually resolves by itself (a deploy, a read-only view): amber, not danger.\n error: { Icon: CloudAlert, tone: \"warning\" },\n syncing: { Icon: RefreshCw, tone: \"info\" },\n pending: { Icon: UploadCloud, tone: \"warning\" },\n synced: { Icon: Cloud, tone: \"success\" },\n};\n\nconst TEXT_TONE: Record<Tone, string> = {\n danger: \"text-danger\",\n warning: \"text-warning\",\n info: \"text-info\",\n success: \"text-success\",\n};\n\n/** The scope words of an indicator that covers only part of the app (§6.10). */\nexport interface SyncStatusScope {\n /** Put before every state in the tooltip: \"Handovers on this device\" →\n * \"Handovers on this device: 3 changes waiting to sync\". */\n name: string;\n /** The whole offline sentence instead of the kit's, when offline means something\n * narrower: \"Offline: handovers keep working on this device; everything else needs a\n * connection\". */\n offline?: string;\n}\n\nexport interface SyncStatusIndicatorProps {\n status: SyncStatus;\n /**\n * Run a round now (push, then pull, then re-read what is on screen). With it the\n * indicator is a \"sync now\" button, disabled offline and mid-round but keeping its\n * colour; a returned promise keeps the spin until it settles. Without it, a status only\n * (§6.9). Disabled too while a round waits for another tab's (0.55): a tap would only\n * queue one more behind it.\n */\n onSync?: () => void | Promise<unknown>;\n /** Render nothing while in sync — kastlan and Kurvenschmiede show it only while offline,\n * syncing or with changes waiting, held or refused (§2.6). */\n hideWhenIdle?: boolean;\n /** The app's words for its part of the app (§6.10). */\n scope?: SyncStatusScope;\n /**\n * Where the app keeps a change before the server has it. `\"device\"` (the default): an\n * offline-first app (keksdose, kastlan's handovers) — offline means \"kept here, sent\n * later\". `\"none\"`: an online-only app that keeps nothing on the device (Kurvenschmiede,\n * build review B1) — offline reads \"changes can't be saved until you reconnect\", a\n * round reads \"Saving…\", and `pending`, `blocked`, `waitingForPlan` and `waitingForTab`\n * are ignored, since there is nowhere for a change to wait. \"Up to date\" when settled.\n */\n storage?: \"device\" | \"none\";\n /** Short words beside the cloud (\"Offline\", \"Syncing…\", \"3 waiting\", \"Waiting for tab\")\n * while not in sync. */\n showLabel?: boolean;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { sync } }}>` > English. */\n labels?: LabelOverride<SyncStatusLabels>;\n /** The clock's language for \"Synced at 14:05\". Default: the provider's locale. */\n locale?: string;\n className?: string;\n /** `data-*` attributes go on the trigger — a tour's anchor (`data-tour=\"sync-status\"`). */\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** The `data-*` attributes of a rest object, for a trigger. */\nfunction dataAttributes(rest: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith(\"data-\")));\n}\n\n/**\n * The top bar's sync status (docs/top-bar-harmonization.md §2.6), lifted from keksdose's\n * `SyncStatusIndicator`: its seven states and their ranking ({@link syncState}; 0.55 adds an\n * eighth, \"Waiting for another tab\", an hourglass), one cloud per state, a ✓ badge when in\n * sync and a spin while syncing. With `onSync` it is also the \"sync now\" button, and its\n * name says both what it is and what a tap does.\n *\n * Colours are the kit's roles — danger for a write that cannot be sent, warning for\n * anything waiting, info while syncing (or waiting for another tab's round), success when\n * in sync — where keksdose had raw rose/amber/sky/emerald pairs.\n */\nexport function SyncStatusIndicator({\n status,\n onSync,\n hideWhenIdle = false,\n scope,\n showLabel = false,\n labels: labelsProp,\n locale: localeProp,\n className,\n storage = \"device\",\n ...rest\n}: SyncStatusIndicatorProps) {\n const words = useTopBarPartsLabels(labelsProp ? { sync: labelsProp } : undefined).sync;\n const billing = useBillingLabels();\n const locale = useKitLocale(localeProp);\n // The tap's own round, local rather than in the app's store: the store's phase belongs\n // to the sync engine, and a second writer would race the round itself.\n const [refreshing, setRefreshing] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const onlineOnly = storage === \"none\";\n // Online-only: nothing waits on the device, so only offline, error, saving and settled.\n const state = syncState(\n onlineOnly ? { ...status, pending: 0, blocked: 0, waitingForPlan: 0, waitingForTab: false } : status,\n refreshing,\n );\n const data = dataAttributes(rest);\n if (hideWhenIdle && state === \"synced\") return null;\n\n const { Icon, tone } = LOOK[state];\n const spin = state === \"syncing\";\n let text: string;\n switch (state) {\n case \"blocked\":\n text = words.blocked(status.blocked ?? 0);\n break;\n case \"waitingForPlan\":\n text = billing.waitingChanges(status.waitingForPlan ?? 0);\n break;\n case \"offline\":\n text = onlineOnly ? words.offlineUnsaved : words.offline;\n break;\n case \"waitingForTab\":\n text = words.waitingForTab;\n break;\n case \"error\":\n text = words.refused;\n break;\n case \"syncing\":\n text = onlineOnly ? words.saving : words.syncing;\n break;\n case \"pending\":\n text = words.pending(status.pending);\n break;\n default:\n text =\n !onlineOnly && status.lastSyncedAt\n ? words.syncedAt(\n // A clock time in the UI's language, never the browser's (keksdose live #246).\n new Date(status.lastSyncedAt).toLocaleTimeString(locale, { hour: \"2-digit\", minute: \"2-digit\" }),\n )\n : words.idle;\n }\n if (scope) {\n text = state === \"offline\" && scope.offline ? scope.offline : words.scoped(scope.name, text);\n }\n\n const short =\n state === \"offline\"\n ? words.offlineShort\n : state === \"syncing\"\n ? onlineOnly\n ? words.saving\n : words.syncingShort\n : state === \"error\"\n ? words.refusedShort\n : state === \"waitingForTab\"\n ? words.waitingForTabShort\n : state === \"pending\" || state === \"blocked\" || state === \"waitingForPlan\"\n ? words.waitingShort(\n state === \"pending\"\n ? status.pending\n : state === \"blocked\"\n ? (status.blocked ?? 0)\n : (status.waitingForPlan ?? 0),\n )\n : null;\n\n const offline = state === \"offline\";\n // Not while a round waits for another tab's either: a tap would queue one more behind it.\n const canSync = !!onSync && status.online && status.phase !== \"offline\" && !spin && state !== \"waitingForTab\";\n const name = onSync\n ? canSync\n ? words.withHint(text, words.syncHint)\n : offline\n ? words.withHint(text, words.offlineHint)\n : text\n : text;\n const badge = state === \"synced\" ? <Check className=\"stroke-[3]\" /> : undefined;\n const glyph = <Icon aria-hidden className={spin ? \"animate-spin\" : undefined} />;\n\n const sync = async () => {\n if (!canSync || !onSync) return;\n setRefreshing(true);\n try {\n await onSync();\n } finally {\n if (mounted.current) setRefreshing(false);\n }\n };\n\n const control = onSync ? (\n <IconButton\n {...data}\n data-sync-state={state}\n size=\"2xs\"\n glyphSize={20}\n shape=\"round\"\n // A status that is also an action: disabled (offline, mid-round) is exactly when\n // the state matters most, so it keeps its colour.\n disabledStyle=\"keep\"\n tone={tone}\n quiet={false}\n label={name}\n labelVisible={false}\n // Below: the bar is at the top edge, where a bubble above has no room.\n tooltipSide=\"bottom\"\n onClick={() => void sync()}\n disabled={!canSync}\n // It announces itself as a status: a reader hears the state change.\n aria-live=\"polite\"\n badge={badge}\n >\n {glyph}\n </IconButton>\n ) : (\n <Tooltip label={name} side=\"bottom\">\n <span\n {...data}\n role=\"status\"\n data-sync-state={state}\n className={cn(\n \"relative inline-flex size-9 shrink-0 items-center justify-center [&>svg]:size-5\",\n TEXT_TONE[tone],\n )}\n >\n {glyph}\n <span className=\"sr-only\">{name}</span>\n {badge && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute bottom-1.5 end-1.5 inline-flex rounded-full bg-surface [&_svg]:size-3\"\n >\n {badge}\n </span>\n )}\n </span>\n </Tooltip>\n );\n\n if (!showLabel || !short)\n return className ? <span className={cn(\"inline-flex\", className)}>{control}</span> : control;\n return (\n <span className={cn(\"inline-flex items-center gap-1\", className)}>\n {control}\n {/* The eye's short words; the name above already says it in full. */}\n <span aria-hidden className={cn(\"whitespace-nowrap text-xs font-medium\", TEXT_TONE[tone])}>\n {short}\n </span>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -65,6 +65,11 @@ export interface SyncStatusLabels {
65
65
  /** A round under way in an online-only app — it saves, it doesn't sync. Also its short
66
66
  * words beside the cloud. */
67
67
  saving: string;
68
+ /** 0.55 (held back from 0.53): a round waits for another tab's on the same device copy
69
+ * (the sync engine's Web Lock lets one tab sync at a time). */
70
+ waitingForTab: string;
71
+ /** Its short words beside the cloud. */
72
+ waitingForTabShort: string;
68
73
  }
69
74
  /**
70
75
  * `DevMenu`'s words (§2.7, §2.7.1, §6.2, §6.3). Still named for 0.42's `DevSwitchers`: the
@@ -30,7 +30,9 @@ const DEFAULT_TOP_BAR_PARTS_LABELS = {
30
30
  waitingShort: (count) => `${count} waiting`,
31
31
  refusedShort: "Not synced",
32
32
  offlineUnsaved: "Offline \u2014 changes can't be saved until you reconnect",
33
- saving: "Saving\u2026"
33
+ saving: "Saving\u2026",
34
+ waitingForTab: "Waiting for another tab to finish syncing",
35
+ waitingForTabShort: "Waiting for tab"
34
36
  },
35
37
  dev: {
36
38
  menu: "Dev tools",