@eifi1/ui-kit 0.54.0 → 0.56.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 +6 -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 +7 -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 +83 -0
  113. package/dist/components/legended-series-chart.js +77 -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 +9 -12
  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,1011 +0,0 @@
1
- import { listenOn, postOnce } from "../lib/broadcast";
2
- import { httpStatusOf } from "../lib/http-status";
3
- import { isOfflineError, stampAfter } from "../offline/local-first";
4
- import { isSharedComputer } from "../offline/shared-computer";
5
- import type { OutboxChange, OutboxStatus, SyncDb, SyncedRow } from "./db";
6
- import type { SyncRequestBody, SyncResponseBody, SyncWireChange } from "./wire";
7
-
8
- /*
9
- * The sync engine (docs/local-first-harmonization.md §2.3): kastlan's handover client,
10
- * generalised (D10). Keksdose's SERVER rules and wire shapes; the client policies are ka's —
11
- * a new edit merges into the row's last `pending` upsert, a refused change is held per change
12
- * (`waiting` / `blocked` / `rejected`), a capped backoff on errors, the queue kept at sign-out.
13
- *
14
- * A round, per scope in `scopes()` order (read every round, D11): take the scope's queue and
15
- * mark it `sending`; post `{scope, last_knowledge, changes, refusals, client_now, paged?}`
16
- * with `client_now` read fresh (D2 amendment); apply the answer in ONE transaction, only if
17
- * the generation is unchanged (D15); follow `page_cursor` with pull-only requests, the cursor
18
- * written with the last page only (D12). Then the app's `steps`, then one more pass if a step
19
- * changed something. One round at a time; a request during a round runs once more after it.
20
- *
21
- * Across tabs (0.52; refactor audit 2026-10-30, FLW-1): every tab runs its own engine on the one
22
- * IndexedDB copy, and before 0.52 nothing stood between them — a second tab took the first's
23
- * `sending` changes as a closed tab's and sent them again, its failure laid them back so an edit
24
- * merged into a change already out and was deleted with it, and a sign-out in one tab was pulled
25
- * straight back from 0 by the other's poll. Now a round runs under a Web Lock named after the
26
- * database, so the rounds of all tabs take turns, first come first served; and `forget()`
27
- * writes a new epoch into `meta` with the clear, which every round reads, so another tab's
28
- * round drops its answer and that tab's engine stops.
29
- */
30
-
31
- export interface SyncCounts {
32
- pending: number;
33
- waiting: number;
34
- blocked: number;
35
- rejected: number;
36
- }
37
-
38
- export interface SyncStepContext {
39
- signal: AbortSignal;
40
- /** False once `forget()` bumped the generation — in this tab, or (0.52) in another tab on
41
- * the same database, heard over a BroadcastChannel: the step writes nothing more (D15). */
42
- isCurrent(): boolean;
43
- }
44
-
45
- export interface SyncStepResult {
46
- /** The step met no connection: the round ends offline. */
47
- offline?: boolean;
48
- /** One more push/pull in the same round (kastlan's second pass after its photos). */
49
- changed?: boolean;
50
- /** What the step keeps on the device, added to the engine's counts. */
51
- counts?: Partial<SyncCounts>;
52
- }
53
-
54
- /** Work after the push/pull, such as kastlan's photo upload. `D` is the app's database type
55
- * (`createSyncDb<HandoverDb>`), so a step reaches the app's own tables without a cast. */
56
- export interface SyncStep<D extends SyncDb = SyncDb> {
57
- name: string;
58
- run(db: D, ctx: SyncStepContext): Promise<SyncStepResult>;
59
- /** Its counts read from the device between rounds (a photo taken offline shows at once). */
60
- counts?(db: D): Promise<Partial<SyncCounts>>;
61
- }
62
-
63
- export interface SyncEngineState {
64
- phase: "idle" | "syncing" | "offline" | "error";
65
- online: boolean;
66
- counts: SyncCounts;
67
- /** When a scope's walk last completed (ISO), kept in `meta` (D9). */
68
- lastSyncedAt: string | null;
69
- /** When the next attempt after an error runs (epoch ms), or `null`. */
70
- nextRetryAt: number | null;
71
- }
72
-
73
- export interface SyncRoundResult {
74
- /** `paused`: `paused()` held the round. (A round asked for while another tab's round holds
75
- * the database waits its turn, 0.52.) `dropped`: `forget()` ran during it, here or in
76
- * another tab, its answer unused. */
77
- outcome: "synced" | "offline" | "error" | "paused" | "dropped";
78
- /** Changes this round set `waiting`, `blocked` or `rejected`. */
79
- waiting: number;
80
- blocked: number;
81
- rejected: number;
82
- error?: unknown;
83
- }
84
-
85
- export interface SyncEngineOptions<D extends SyncDb = SyncDb> {
86
- /** The database; its type `D` is the one the `steps` get. */
87
- db: D;
88
- /** The scopes this session's role holds, in round order (kastlan: handover, meetings, meters,
89
- * maintenance, contacts). Read at the start of every round: roles are per company. */
90
- scopes: () => readonly string[];
91
- /** Posts one request (`POST /sync`) and answers its body; throws as the app's client does. */
92
- send: (body: SyncRequestBody, signal: AbortSignal) => Promise<SyncResponseBody>;
93
- /** Default {@link isOfflineError}: no answer, or the session refresh's passing 401. */
94
- isOffline?: (err: unknown) => boolean;
95
- /** A whole request refused for the plan → every sent change `waiting` with this code.
96
- * Default: a 402 whose body names a `code` (or `detail.code`). */
97
- planCode?: (err: unknown) => string | null;
98
- /** `refused.code`s kept as `waiting` and sent again every round; any other code is
99
- * `blocked`, sent again only on `retryBlocked()`. Default `["billing_read_only"]`. */
100
- holdCodes?: readonly string[];
101
- /** Ask for a paged pull and follow its cursor (D12). */
102
- paged?: boolean;
103
- steps?: readonly SyncStep<D>[];
104
- /** No round while this holds (kastlan: a practice tour fakes the server's answers). */
105
- paused?: () => boolean;
106
- /** The poll (30 s) and the debounce after a write (800 ms). */
107
- pollMs?: number;
108
- soonMs?: number;
109
- /** The waits after consecutive errors; the last repeats (D13). */
110
- backoffMs?: readonly number[];
111
- /** Default `isSharedComputer`: on a computer marked shared the engine does not start (D15). */
112
- sharedComputer?: () => boolean;
113
- /**
114
- * 0.52: the backstop on one round, counted from the moment it holds the database's lock
115
- * (a tab waiting behind a stuck round is not cut with it). Past it the round is cut: its
116
- * requests and steps are aborted, anything they answer or write later is dropped, and the
117
- * round ends `error` with a `TimeoutError` (`DOMException`), not `offline`, so the backoff
118
- * starts and the lock goes to the next tab even when a request or a step ignores its
119
- * signal. The app's own request timeouts come first; this only bounds what they miss.
120
- * Default 10 minutes, long on purpose: kastlan's photo backlog can take minutes, and a cut
121
- * round has still made progress (each photo uploaded before the cut is off the queue).
122
- * `0` or `Infinity`: none.
123
- */
124
- roundTimeoutMs?: number;
125
- }
126
-
127
- export interface SyncEngine {
128
- /** Into the copy at once and into the queue, in the scope the caller names (D11). An edit
129
- * merges into the row's last `pending` upsert; deleting a row that never reached the
130
- * server forgets its queue; stamped with `stampAfter`. */
131
- write(
132
- scope: string,
133
- entity: string,
134
- id: string,
135
- op: "upsert" | "delete",
136
- data?: Record<string, unknown>,
137
- ): Promise<void>;
138
- /** Starts the poll and the round on `online` (nothing on a computer marked shared).
139
- * An engine another tab's `forget()` stopped stays stopped through `start()`: only its
140
- * own `forget()` (the app's session end) or a new engine (`releaseSyncEngine`) re-arms
141
- * it. A start cannot tell a new sign-in from a remount in a tab whose session never
142
- * ended, and the latter would pull the signed-out account back into the wiped copy
143
- * (D15; the 0.52 code review asked, and the contract stands). */
144
- start(): void;
145
- /** Stops the poll and the timers, aborts the round in flight and drops its answer (the
146
- * queue is laid back). A new engine's round on the same database waits for it. */
147
- stop(): void;
148
- /** Sync soon (debounced): after a write. */
149
- requestSync(): void;
150
- /** "Sync now": resets the backoff, runs a round of its own after the one under way — another
151
- * tab's too (0.52) — and answers its result. */
152
- syncNow(): Promise<SyncRoundResult>;
153
- /** A round that sends the `blocked` changes too. */
154
- retryBlocked(): Promise<void>;
155
- /** Drop the rejected changes (of one scope) and pull that scope afresh. */
156
- discardRejected(scope?: string): Promise<void>;
157
- /** The rejected changes with their reasons, for the app's banner. */
158
- rejected(): Promise<OutboxChange[]>;
159
- /** Sign-out: bump the generation (a round in flight writes nothing more), then clear the
160
- * rows and `meta` — and the outbox and the app's tables unless `keepQueue` (D15). In every
161
- * tab (0.52): the clear writes a new epoch into `meta`, so a round of another tab's engine on
162
- * this database drops its answer, and that engine stops — the session it ran for has ended.
163
- * Another tab's `forget()` heard while this clear runs is decided by the epoch the copy is
164
- * left with: one that committed first was undone by this clear and stops nothing. */
165
- forget(opts: { keepQueue: boolean }): Promise<void>;
166
- getState(): SyncEngineState;
167
- subscribe(listener: () => void): () => void;
168
- }
169
-
170
- export const DEFAULT_SYNC_BACKOFF_MS: readonly number[] = [5_000, 15_000, 60_000, 300_000];
171
- export const DEFAULT_HOLD_CODES: readonly string[] = ["billing_read_only"];
172
- /** `roundTimeoutMs`'s default (see there). */
173
- const ROUND_TIMEOUT_MS = 10 * 60_000;
174
- /** The longest delay `setTimeout` keeps (2^31 - 1 ms, ~24.8 days); a longer one fires at once. */
175
- const MAX_TIMEOUT_MS = 2_147_483_647;
176
-
177
- const LAST_SYNCED_AT = "lastSyncedAt";
178
- /** The copy's epoch in `meta` (0.52): a new one with every `forget()`, in its clear. */
179
- const EPOCH = "epoch";
180
- const lastKnowledgeKey = (scope: string) => `lastKnowledge:${scope}`;
181
- const UNSENT: readonly OutboxStatus[] = ["pending", "sending", "waiting", "blocked"];
182
- const ZERO: SyncCounts = { pending: 0, waiting: 0, blocked: 0, rejected: 0 };
183
-
184
- /** The started engine of each database: one engine per database (D15). */
185
- const running = new Map<string, SyncEngine>();
186
-
187
- /** The round in flight on each database, of whichever engine: a new engine's round waits
188
- * for it, so two never take the same queue (a company switch A→B→A within one round). */
189
- const flights = new Map<string, Promise<unknown>>();
190
-
191
- /** The engines {@link syncEngineFor} keeps, by database name. */
192
- const kept = new Map<string, { db: SyncDb; engine: SyncEngine }>();
193
-
194
- /** The generation changed under a round: its answer is dropped. */
195
- class Dropped extends Error {}
196
-
197
- /** The copy's epoch moved since this engine started: `forget()` ran in another tab. The
198
- * round's answer is dropped as well. */
199
- class ForgottenElsewhere extends Dropped {}
200
-
201
- /** The browser's Web Locks: a secure context (https, localhost), Safari 15.4+. */
202
- function webLocks(): LockManager | undefined {
203
- return typeof navigator === "undefined" ? undefined : (navigator as { locks?: LockManager }).locks;
204
- }
205
-
206
- /** A `forget()`'s epoch: unique enough to tell two apart, and on plain http too, where
207
- * `crypto.randomUUID` is missing (refactor audit 2026-10-30, DUP-7). */
208
- function freshEpoch(): string {
209
- const uuid = (globalThis.crypto as Partial<Crypto> | undefined)?.randomUUID;
210
- return uuid ? uuid.call(globalThis.crypto) : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
211
- }
212
-
213
- /** A row created on this device and never echoed by the server carries `local: true`. */
214
- type CopyRow = SyncedRow & { local?: boolean };
215
-
216
- function browserOnline(): boolean {
217
- return typeof navigator === "undefined" || navigator.onLine !== false;
218
- }
219
-
220
- function defaultPlanCode(err: unknown): string | null {
221
- if (httpStatusOf(err) !== 402) return null;
222
- const body = (err as { response?: { data?: { code?: unknown; detail?: { code?: unknown } } } }).response?.data;
223
- const code = typeof body?.code === "string" ? body.code : body?.detail?.code;
224
- return typeof code === "string" ? code : null;
225
- }
226
-
227
- /** A refusal's `code` and `detail` from an error's body: `{code, detail}` or FastAPI's
228
- * `{detail: {code, detail}}`. */
229
- function refusalOf(err: unknown): { code?: string; detail?: string } {
230
- const body = (err as { response?: { data?: unknown } }).response?.data as
231
- { code?: unknown; detail?: unknown } | undefined;
232
- const inner =
233
- body?.detail && typeof body.detail === "object" ? (body.detail as { code?: unknown; detail?: unknown }) : body;
234
- const text = (v: unknown) => (typeof v === "string" && v ? v : undefined);
235
- return { code: text(inner?.code), detail: text(inner?.detail) };
236
- }
237
-
238
- function latest(stamps: unknown[]): string | undefined {
239
- let best: string | undefined;
240
- for (const s of stamps) {
241
- if (typeof s !== "string" || !Number.isFinite(Date.parse(s))) continue;
242
- if (best === undefined || Date.parse(s) > Date.parse(best)) best = s;
243
- }
244
- return best;
245
- }
246
-
247
- const toWire = ({ entity, id, op, data, updated_at }: OutboxChange): SyncWireChange =>
248
- op === "delete" ? { entity, id, op, updated_at } : { entity, id, op, data, updated_at };
249
-
250
- /**
251
- * The engine on one database (D15: one engine per database; a company switch builds a new
252
- * engine on the new database and stops the old one). Nothing runs until `start()`
253
- * (`useSyncWorker`), and `start()` does nothing on a computer marked shared.
254
- */
255
- export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineOptions<D>): SyncEngine {
256
- const { db } = options;
257
- const isOffline = options.isOffline ?? isOfflineError;
258
- const planCode = options.planCode ?? defaultPlanCode;
259
- const holdCodes = new Set(options.holdCodes ?? DEFAULT_HOLD_CODES);
260
- const steps = options.steps ?? [];
261
- const pollMs = options.pollMs ?? 30_000;
262
- const soonMs = options.soonMs ?? 800;
263
- const backoffMs = options.backoffMs?.length ? options.backoffMs : DEFAULT_SYNC_BACKOFF_MS;
264
- const sharedComputer = options.sharedComputer ?? isSharedComputer;
265
- const roundTimeoutMs = options.roundTimeoutMs ?? ROUND_TIMEOUT_MS;
266
- /** The rounds the deadline cut, by their signal: their late work lays nothing back. */
267
- const cut = new WeakSet<AbortSignal>();
268
-
269
- let generation = 0;
270
- let state: SyncEngineState = {
271
- phase: "idle",
272
- online: browserOnline(),
273
- counts: ZERO,
274
- lastSyncedAt: null,
275
- nextRetryAt: null,
276
- };
277
- const listeners = new Set<() => void>();
278
- let started = false;
279
- let poll: ReturnType<typeof setInterval> | null = null;
280
- let soon: ReturnType<typeof setTimeout> | null = null;
281
- let retry: ReturnType<typeof setTimeout> | null = null;
282
- let failures = 0;
283
- let current: Promise<SyncRoundResult> | null = null;
284
- let again = false;
285
- let controller: AbortController | null = null;
286
- let stepCounts: Record<string, Partial<SyncCounts>> = {};
287
-
288
- // ── Across tabs (0.52, FLW-1) ───────────────────────────────────────────────
289
- /** The name of this database's Web Lock and BroadcastChannel (one origin's tabs share both,
290
- * as they share the IndexedDB). */
291
- const across = `eifi1-sync:${db.name}`;
292
- /** The copy's epochs this engine runs under: the one read when it started (or at its first
293
- * round), and each its own `forget()` wrote. Any other one means `forget()` ran elsewhere. */
294
- const epochs = new Set<string | null>();
295
- let epochRead: Promise<void> | null = null;
296
- /** `forget()` ran in another tab: this engine runs no round until its own `forget()`. */
297
- let forgotten = false;
298
- /** This engine's own `forget()`s whose clear is under way. */
299
- let forgetting = 0;
300
- let unlisten: (() => void) | null = null;
301
-
302
- function set(next: Partial<SyncEngineState>) {
303
- state = { ...state, ...next };
304
- listeners.forEach((listener) => listener());
305
- }
306
-
307
- /** The counts and `lastSyncedAt` read again from the device. Never throws: a closed or
308
- * deleted database leaves the last counts standing. */
309
- function refreshCounts(): Promise<void> {
310
- return readCounts().catch(() => undefined);
311
- }
312
-
313
- async function readCounts(): Promise<void> {
314
- const gen = generation;
315
- const count = (status: OutboxStatus) => db.outbox.where("status").equals(status).count();
316
- const [pending, sending, waiting, blocked, rejected] = await Promise.all(
317
- (["pending", "sending", "waiting", "blocked", "rejected"] as const).map(count),
318
- );
319
- const fromSteps = { ...stepCounts };
320
- for (const step of steps) if (step.counts) fromSteps[step.name] = await step.counts(db);
321
- const last = (await db.meta.get(LAST_SYNCED_AT))?.value;
322
- if (gen !== generation) return;
323
- if (Object.keys(fromSteps).length) stepCounts = fromSteps;
324
- const counts = { pending: pending + sending, waiting, blocked, rejected };
325
- for (const extra of Object.values(stepCounts)) {
326
- for (const key of Object.keys(counts) as (keyof SyncCounts)[]) counts[key] += extra[key] ?? 0;
327
- }
328
- set({ counts, lastSyncedAt: typeof last === "string" ? last : null });
329
- }
330
-
331
- function readEpoch(): Promise<string | null> {
332
- return db.meta.get(EPOCH).then((entry) => (typeof entry?.value === "string" ? entry.value : null));
333
- }
334
-
335
- /** Read once per engine — at `start()`, before its first round can wait on another tab's —
336
- * so a `forget()` that lands while it waits is seen as another tab's. A failed read is
337
- * tried again by the next round. */
338
- function captureEpoch(): Promise<void> {
339
- if (epochRead) return epochRead;
340
- const read = readEpoch().then((epoch) => void epochs.add(epoch));
341
- epochRead = read;
342
- read.catch(() => {
343
- if (epochRead === read) epochRead = null;
344
- });
345
- return read;
346
- }
347
-
348
- /** While started, hear `forget()` in another tab at once: a step in flight sees
349
- * `isCurrent()` turn false, the request in flight is aborted, and the poll stops. Without
350
- * BroadcastChannel the next round's read of the epoch stops it instead. */
351
- function listen() {
352
- unlisten ??= listenOn(across, (data) => {
353
- const epoch = (data as { epoch?: unknown } | null)?.epoch;
354
- if (typeof epoch === "string" && !epochs.has(epoch)) void forgetHere();
355
- });
356
- }
357
-
358
- /** `forget()` ran in another tab: the session this engine ran for has ended there. Its
359
- * round in flight writes nothing more, and it runs none of its own any more.
360
- *
361
- * Not while its own `forget()` clears the copy: that clear decides, by the epoch it leaves
362
- * in `meta`. 0.52 code review (src/sync/engine.ts): in kastlan's sign-out cascade tab A's
363
- * epoch reached tab B while B's own clear waited behind A's, so B's `forget()` resolved
364
- * with B stopped and forgotten — the forget that re-arms an engine left it dead. */
365
- function forgetHere(): Promise<void> {
366
- if (forgotten || forgetting > 0) return Promise.resolve();
367
- forgotten = true;
368
- if (started) halt();
369
- else drop();
370
- return endSession();
371
- }
372
-
373
- /** The round in flight, or waiting for the lock, writes nothing more: its wait and its
374
- * request are aborted, its answer dropped, the queue laid back. */
375
- function drop() {
376
- generation += 1;
377
- controller?.abort();
378
- }
379
-
380
- /** What the engine shows of a session that ended — by its own `forget()` or another tab's —
381
- * goes with it: the steps' counts, the last sync, an error and its retry. 0.52 code review
382
- * (src/sync/engine.ts): the two paths each reset part of it, and an engine another tab
383
- * stopped showed "error" for a retry that never came. */
384
- function endSession(): Promise<void> {
385
- stepCounts = {};
386
- failures = 0;
387
- clearRetry();
388
- set({ phase: "idle", lastSyncedAt: null, nextRetryAt: null });
389
- return refreshCounts();
390
- }
391
-
392
- /** The round's epoch still stands in `meta`, read in the caller's transaction, so another
393
- * tab's `forget()` commits wholly before it or after it. Else `forget()` ran: in this tab
394
- * (the generation moved too) or in another — then the engine stops. */
395
- async function standing(gen: number, epoch: string | null): Promise<void> {
396
- const now = await readEpoch();
397
- if (gen !== generation) throw new Dropped();
398
- if (now !== epoch) throw epochs.has(now) ? new Dropped() : new ForgottenElsewhere();
399
- }
400
-
401
- // ── The queue ──────────────────────────────────────────────────────────────
402
-
403
- /** Mark the scope's queue as in the request (a `sending` left by a closed tab goes back
404
- * first); answers it with the statuses it had. Under the Web Lock (0.52) a `sending` here
405
- * is a closed tab's, as this assumes: no other tab's round is out. Without Web Locks
406
- * (plain http) it may be another open tab's, as before 0.52.
407
- *
408
- * Only under the round's epoch. 0.52 code review (src/sync/engine.ts): after another tab's
409
- * `forget()` had landed mid-round, every scope left still posted the kept queue under the
410
- * session that had ended and pulled itself from 0, only for `apply` to drop the answer. */
411
- function takeQueue(scope: string, withBlocked: boolean, gen: number, epoch: string | null): Promise<OutboxChange[]> {
412
- return db.transaction("rw", db.outbox, db.meta, async () => {
413
- await standing(gen, epoch);
414
- await db.outbox
415
- .where("scope")
416
- .equals(scope)
417
- .and((c) => c.status === "sending")
418
- .modify({ status: "pending" });
419
- const take: OutboxStatus[] = withBlocked ? ["pending", "waiting", "blocked"] : ["pending", "waiting"];
420
- const queue = (await db.outbox.where("scope").equals(scope).toArray())
421
- .filter((c) => take.includes(c.status))
422
- .sort((a, b) => a.seq! - b.seq!);
423
- await db.outbox.bulkUpdate(queue.map((c) => ({ key: c.seq!, changes: { status: "sending" as const } })));
424
- return queue;
425
- });
426
- }
427
-
428
- /** The request failed or its answer was dropped: each change back to the status it had.
429
- * Not by a round the deadline cut: its late work ends after the next round — this tab's
430
- * or another's — may have taken these changes `sending` again, and laid back `pending`
431
- * under that round an edit would merge into a change already out and be deleted with it
432
- * (FLW-1's lost edit). The next round's `takeQueue` takes them back instead, as a closed
433
- * tab's. */
434
- function layBack(sent: OutboxChange[], signal: AbortSignal) {
435
- if (cut.has(signal)) return;
436
- return db.outbox.bulkUpdate(sent.map((c) => ({ key: c.seq!, changes: { status: c.status } })));
437
- }
438
-
439
- function mark(changes: OutboxChange[], fields: Partial<OutboxChange>) {
440
- return db.outbox.bulkUpdate(changes.map((c) => ({ key: c.seq!, changes: fields })));
441
- }
442
-
443
- /** Put one page's rows in the copy, settle the sent changes, lay the unsent ones back. The
444
- * epoch is read in the same transaction: a `forget()` of another tab either commits before
445
- * it (this answer is dropped) or after it (and clears what it wrote). */
446
- async function apply(
447
- scope: string,
448
- answer: SyncResponseBody,
449
- sent: OutboxChange[],
450
- gen: number,
451
- epoch: string | null,
452
- lastPage: boolean,
453
- ) {
454
- return db.transaction("rw", db.rows, db.outbox, db.meta, async () => {
455
- await standing(gen, epoch);
456
- const refused = answer.refused ?? null;
457
- const refusedIds = new Set(refused?.change_ids ?? []);
458
- const rejections = new Map((answer.rejections ?? []).map((r) => [r.change_id, r]));
459
- const kept = sent.filter((c) => refusedIds.has(c.id));
460
- const rejected = sent.filter((c) => !refusedIds.has(c.id) && rejections.has(c.id));
461
- await db.outbox.bulkDelete(sent.filter((c) => !refusedIds.has(c.id) && !rejections.has(c.id)).map((c) => c.seq!));
462
- const hold = refused != null && holdCodes.has(refused.code);
463
- if (refused)
464
- await mark(kept, { status: hold ? "waiting" : "blocked", code: refused.code, reason: refused.detail });
465
- for (const change of rejected) {
466
- const why = rejections.get(change.id)!;
467
- await db.outbox.update(change.seq!, { status: "rejected", code: why.code, reason: why.detail });
468
- }
469
- const pulled = new Set<string>();
470
- for (const change of answer.changes) {
471
- pulled.add(`${change.entity}\u0000${change.id}`);
472
- if (change.op === "delete") await db.rows.delete([change.entity, change.id]);
473
- else {
474
- const row: CopyRow = {
475
- entity: change.entity,
476
- id: change.id,
477
- pk: change.pk ?? null,
478
- local: false,
479
- data: change.data ?? {},
480
- // The server's stamp rides beside `data`: kept, so the next edit is stamped after it.
481
- updated_at: change.updated_at ?? null,
482
- };
483
- await db.rows.put(row);
484
- }
485
- }
486
- // A pull never undoes what was typed while the request was out.
487
- const unsent = (await db.outbox.where("status").anyOf(UNSENT).toArray())
488
- .filter((c) => pulled.has(`${c.entity}\u0000${c.id}`))
489
- .sort((a, b) => a.seq! - b.seq!);
490
- for (const change of unsent) {
491
- if (change.op === "delete") {
492
- await db.rows.delete([change.entity, change.id]);
493
- continue;
494
- }
495
- const row = (await db.rows.get([change.entity, change.id])) as CopyRow | undefined;
496
- await db.rows.put({
497
- ...row,
498
- entity: change.entity,
499
- id: change.id,
500
- pk: row?.pk ?? null,
501
- data: { ...row?.data, ...change.data },
502
- });
503
- }
504
- if (lastPage) {
505
- await db.meta.put({ key: lastKnowledgeKey(scope), value: answer.server_knowledge });
506
- await db.meta.put({ key: LAST_SYNCED_AT, value: new Date().toISOString() });
507
- }
508
- return {
509
- waiting: hold ? kept.length : 0,
510
- blocked: refused && !hold ? kept.length : 0,
511
- rejected: rejected.length,
512
- };
513
- });
514
- }
515
-
516
- /** A scope the role no longer holds (403): its queue is rejected and it leaves the round.
517
- * The server's `code` and `detail` are kept (a demo's `demo_read_only`); without them,
518
- * `forbidden` and "Not allowed to sync <scope>". */
519
- async function loseScope(scope: string, err: unknown): Promise<number> {
520
- const why = refusalOf(err);
521
- return db.transaction("rw", db.outbox, async () => {
522
- const queue = (await db.outbox.where("scope").equals(scope).toArray()).filter((c) => UNSENT.includes(c.status));
523
- await mark(queue, {
524
- status: "rejected",
525
- code: why.code ?? "forbidden",
526
- reason: why.code && why.detail ? why.detail : `Not allowed to sync ${scope}`,
527
- });
528
- return queue.length;
529
- });
530
- }
531
-
532
- type ScopeResult = { offline: true } | { offline: false; waiting: number; blocked: number; rejected: number };
533
-
534
- async function syncScope(
535
- scope: string,
536
- gen: number,
537
- epoch: string | null,
538
- signal: AbortSignal,
539
- withBlocked: boolean,
540
- ): Promise<ScopeResult> {
541
- const sent = await takeQueue(scope, withBlocked, gen, epoch);
542
- const last = Number((await db.meta.get(lastKnowledgeKey(scope)))?.value ?? 0) || 0;
543
- const body = (changes: SyncWireChange[], page_cursor: SyncRequestBody["page_cursor"]): SyncRequestBody => ({
544
- scope,
545
- last_knowledge: last,
546
- changes,
547
- refusals: true,
548
- client_now: new Date().toISOString(),
549
- ...(options.paged ? { paged: true, page_cursor } : {}),
550
- });
551
- let answer: SyncResponseBody;
552
- try {
553
- answer = await options.send(body(sent.map(toWire), null), signal);
554
- } catch (err) {
555
- await layBack(sent, signal);
556
- if (gen !== generation) throw new Dropped();
557
- if (isOffline(err)) return { offline: true };
558
- const plan = planCode(err);
559
- if (!plan && httpStatusOf(err) !== 403) throw err;
560
- // A refusal is the answer, kept only under the round's epoch as `apply` keeps one. 0.52
561
- // code review (src/sync/engine.ts): a 403 heard after another tab's `forget()` marked
562
- // the kept queue `rejected`, never sent again, for a session that had ended.
563
- return db.transaction("rw", db.outbox, db.meta, async (): Promise<ScopeResult> => {
564
- await standing(gen, epoch);
565
- if (plan) {
566
- // The whole request refused for the plan: every change waits for it.
567
- await mark(sent, { status: "waiting", code: plan, reason: plan });
568
- return { offline: false, waiting: sent.length, blocked: 0, rejected: 0 };
569
- }
570
- return { offline: false, waiting: 0, blocked: 0, rejected: await loseScope(scope, err) };
571
- });
572
- }
573
- let cursor = options.paged ? (answer.page_cursor ?? null) : null;
574
- let tally: ScopeResult;
575
- try {
576
- tally = { offline: false, ...(await apply(scope, answer, sent, gen, epoch, cursor == null)) };
577
- } catch (err) {
578
- if (err instanceof Dropped) await layBack(sent, signal);
579
- throw err;
580
- }
581
- // The rest of a paged pull: pull-only; the cursor is written with the last page alone,
582
- // so an interrupted walk starts again from the old one (D12).
583
- while (cursor) {
584
- let page: SyncResponseBody;
585
- try {
586
- page = await options.send(body([], cursor), signal);
587
- } catch (err) {
588
- if (gen !== generation) throw new Dropped();
589
- if (isOffline(err)) return { offline: true };
590
- throw err;
591
- }
592
- cursor = page.page_cursor ?? null;
593
- await apply(scope, page, [], gen, epoch, cursor == null);
594
- }
595
- return tally;
596
- }
597
-
598
- // ── The round ──────────────────────────────────────────────────────────────
599
-
600
- async function round(withBlocked: boolean, gen: number): Promise<SyncRoundResult> {
601
- const result: SyncRoundResult = { outcome: "synced", waiting: 0, blocked: 0, rejected: 0 };
602
- if (gen !== generation || forgotten) return { ...result, outcome: "dropped" };
603
- // Aborted by stop() and forget(): the wait for the lock, then the round's requests.
604
- const own = new AbortController();
605
- controller = own;
606
- try {
607
- const locks = webLocks();
608
- if (!locks) return await begin(result, withBlocked, gen, own);
609
- let granted = false;
610
- try {
611
- // Queued, first come first served: the rounds of every tab on this database take
612
- // turns, and one asked for meanwhile waits for its turn (`current` and `again` keep
613
- // one per engine). 0.52 code review (src/sync/engine.ts): an `ifAvailable` round
614
- // that found the lock taken answered `paused` at once, so the sign-out dialog's
615
- // "Sync now" closed as if it had synced; its one deferred round lost a
616
- // `retryBlocked()`'s `blocked`; and two tabs' waits refused each other's next try —
617
- // the spec refuses `ifAvailable` while any request is queued. A closed tab's lock
618
- // is released with it.
619
- return await locks.request(across, { signal: own.signal }, () => {
620
- granted = true;
621
- return begin(result, withBlocked, gen, own);
622
- });
623
- } catch (err) {
624
- // `begin` throws only what the app's `paused()` throws. A wait aborted by stop() or
625
- // forget() is dropped there; a lock manager that refuses (an opaque origin, a
626
- // document no longer active) runs the round as before 0.52.
627
- if (granted) return { ...result, outcome: "error", error: err };
628
- return await begin(result, withBlocked, gen, own);
629
- }
630
- } finally {
631
- if (controller === own) controller = null;
632
- }
633
- }
634
-
635
- /** The round begins — under the lock, where there is one, after however long it waited:
636
- * the decision point, and the start of its deadline. */
637
- async function begin(
638
- result: SyncRoundResult,
639
- withBlocked: boolean,
640
- gen: number,
641
- own: AbortController,
642
- ): Promise<SyncRoundResult> {
643
- if (gen !== generation || forgotten) return { ...result, outcome: "dropped" };
644
- if (options.paused?.()) return { ...result, outcome: "paused" };
645
- if (!browserOnline()) return { ...result, outcome: "offline" };
646
- return withDeadline(locked(result, withBlocked, gen, own.signal), result, gen, own);
647
- }
648
-
649
- /** The round, or its cut at `roundTimeoutMs`, whichever comes first. 0.52 code review,
650
- * finding 3 (Marcel: "kit backstop + app timeouts"): a round holds the lock of every tab
651
- * on its database, and one whose request never settles (axios has no default timeout) or
652
- * whose step never does stopped sync in all of them until that tab was closed. The cut
653
- * settles the round by the race, so the lock is released even when the app ignores the
654
- * abort; the generation moves, so what the cut work answers or writes later is dropped. */
655
- function withDeadline(
656
- round: Promise<SyncRoundResult>,
657
- result: SyncRoundResult,
658
- gen: number,
659
- own: AbortController,
660
- ): Promise<SyncRoundResult> {
661
- if (!(roundTimeoutMs > 0 && Number.isFinite(roundTimeoutMs))) return round;
662
- let timer: ReturnType<typeof setTimeout> | undefined;
663
- const deadline = new Promise<SyncRoundResult>((resolve) => {
664
- timer = setTimeout(
665
- () => {
666
- const error = new DOMException(`The sync round ran past ${roundTimeoutMs} ms`, "TimeoutError");
667
- // Settled first: the race ends on the cut, whatever the abort below sets off.
668
- resolve({ ...result, outcome: "error", error });
669
- cut.add(own.signal);
670
- if (gen === generation) generation += 1;
671
- own.abort(error);
672
- },
673
- Math.min(roundTimeoutMs, MAX_TIMEOUT_MS),
674
- );
675
- });
676
- return Promise.race([round, deadline]).finally(() => clearTimeout(timer));
677
- }
678
-
679
- /** The round itself. Never throws. */
680
- async function locked(
681
- result: SyncRoundResult,
682
- withBlocked: boolean,
683
- gen: number,
684
- signal: AbortSignal,
685
- ): Promise<SyncRoundResult> {
686
- try {
687
- await captureEpoch();
688
- const epoch = await readEpoch();
689
- if (gen !== generation) throw new Dropped();
690
- if (!epochs.has(epoch)) throw new ForgottenElsewhere();
691
- set({ phase: "syncing" });
692
- for (let pass = 0; ; pass++) {
693
- for (const scope of options.scopes()) {
694
- if (gen !== generation) throw new Dropped();
695
- const r = await syncScope(scope, gen, epoch, signal, withBlocked);
696
- if (r.offline) return { ...result, outcome: "offline" };
697
- result.waiting += r.waiting;
698
- result.blocked += r.blocked;
699
- result.rejected += r.rejected;
700
- }
701
- if (pass > 0) break;
702
- let changed = false;
703
- for (const step of steps) {
704
- if (gen !== generation) throw new Dropped();
705
- const ran = await step.run(db, { signal, isCurrent: () => gen === generation });
706
- if (gen !== generation) throw new Dropped();
707
- if (ran.counts) stepCounts = { ...stepCounts, [step.name]: ran.counts };
708
- if (ran.offline) return { ...result, outcome: "offline" };
709
- if (ran.changed) changed = true;
710
- }
711
- if (!changed) break;
712
- }
713
- return result;
714
- } catch (err) {
715
- if (err instanceof ForgottenElsewhere) void forgetHere();
716
- if (err instanceof Dropped || gen !== generation) return { ...result, outcome: "dropped" };
717
- return { ...result, outcome: "error", error: err };
718
- }
719
- }
720
-
721
- function clearRetry() {
722
- if (retry) clearTimeout(retry);
723
- retry = null;
724
- }
725
-
726
- function resetBackoff() {
727
- failures = 0;
728
- clearRetry();
729
- if (state.nextRetryAt !== null) set({ nextRetryAt: null });
730
- }
731
-
732
- const inBackoff = () => state.nextRetryAt !== null && Date.now() < state.nextRetryAt;
733
-
734
- function settle(result: SyncRoundResult) {
735
- switch (result.outcome) {
736
- case "synced":
737
- failures = 0;
738
- clearRetry();
739
- set({ phase: "idle", online: true, nextRetryAt: null });
740
- break;
741
- case "offline":
742
- // No retry timer offline: the `online` event or the poll brings the next round (D13).
743
- clearRetry();
744
- set({ phase: "offline", online: false, nextRetryAt: null });
745
- break;
746
- case "error": {
747
- const wait = backoffMs[Math.min(failures, backoffMs.length - 1)];
748
- failures += 1;
749
- clearRetry();
750
- if (started) {
751
- retry = setTimeout(() => {
752
- retry = null;
753
- void run(false);
754
- }, wait);
755
- }
756
- set({ phase: "error", nextRetryAt: started ? Date.now() + wait : null });
757
- break;
758
- }
759
- case "dropped":
760
- set({ phase: "idle" });
761
- break;
762
- case "paused":
763
- break;
764
- }
765
- }
766
-
767
- function run(withBlocked: boolean): Promise<SyncRoundResult> {
768
- if (current) {
769
- again = true;
770
- return current;
771
- }
772
- const before = flights.get(db.name);
773
- // The generation is read before any wait: a stop() while this waits drops the round, so
774
- // it never sends this copy's queue with another account's client nor pulls into it.
775
- const gen = generation;
776
- const flight = (async () => {
777
- // Another engine's round on this database (stopped, still settling) ends first.
778
- if (before) await before.catch(() => undefined);
779
- try {
780
- const result = await round(withBlocked, gen);
781
- settle(result);
782
- await refreshCounts();
783
- return result;
784
- } finally {
785
- current = null;
786
- if (again) {
787
- again = false;
788
- tick();
789
- }
790
- }
791
- })();
792
- current = flight;
793
- flights.set(db.name, flight);
794
- const done = () => {
795
- if (flights.get(db.name) === flight) flights.delete(db.name);
796
- };
797
- void flight.then(done, done);
798
- return flight;
799
- }
800
-
801
- /** A round from the poll, a write or a request during a round: not while backing off. */
802
- function tick() {
803
- if (started && !inBackoff()) void run(false);
804
- }
805
-
806
- /** A round of its own, begun after the one under way — and after one that a request made
807
- * meanwhile started as that one ended — so `retryBlocked()` never joins a round that
808
- * leaves `blocked` out (0.52 code review, src/sync/engine.ts). */
809
- async function runFresh(withBlocked: boolean): Promise<SyncRoundResult> {
810
- resetBackoff();
811
- while (current) {
812
- again = false;
813
- await current.catch(() => undefined);
814
- }
815
- return run(withBlocked);
816
- }
817
-
818
- const onOnline = () => {
819
- set({ online: true });
820
- resetBackoff();
821
- void run(false);
822
- };
823
- const onOffline = () => set({ online: false });
824
-
825
- /** `stop()`, and the end `forgetHere` puts to a started engine. */
826
- function halt() {
827
- if (!started) return;
828
- started = false;
829
- // The next engine's round waits for the dropped one to settle.
830
- drop();
831
- if (running.get(db.name) === engine) running.delete(db.name);
832
- if (poll) clearInterval(poll);
833
- poll = null;
834
- if (soon) clearTimeout(soon);
835
- soon = null;
836
- clearRetry();
837
- if (typeof window !== "undefined") {
838
- window.removeEventListener("online", onOnline);
839
- window.removeEventListener("offline", onOffline);
840
- }
841
- unlisten?.();
842
- unlisten = null;
843
- }
844
-
845
- const engine: SyncEngine = {
846
- async write(scope, entity, id, op, data = {}) {
847
- await db.transaction("rw", db.rows, db.outbox, async () => {
848
- const row = (await db.rows.get([entity, id])) as CopyRow | undefined;
849
- const ofRow = await db.outbox.where("[entity+id]").equals([entity, id]).toArray();
850
- ofRow.sort((a, b) => a.seq! - b.seq!);
851
- const updated_at = stampAfter(
852
- latest([row?.updated_at, row?.data.updated_at, ...ofRow.map((c) => c.updated_at)]),
853
- );
854
- // A new edit to the row moves a rejected change out of the way (D13).
855
- const rejected = ofRow.filter((c) => c.scope === scope && c.status === "rejected");
856
- if (op === "delete") {
857
- await db.rows.delete([entity, id]);
858
- const neverSynced = row != null && (row.local ?? row.pk == null);
859
- if (neverSynced && !ofRow.some((c) => c.status === "sending")) {
860
- // Never reached the server: forgetting its queue is enough — a waiting change
861
- // included, or it would create the row once the plan is back.
862
- await db.outbox.bulkDelete(ofRow.map((c) => c.seq!));
863
- return;
864
- }
865
- await db.outbox.bulkDelete(rejected.map((c) => c.seq!));
866
- await db.outbox.add({ scope, entity, id, op, data: {}, updated_at, status: "pending" });
867
- return;
868
- }
869
- const next: CopyRow = { ...row, entity, id, pk: row?.pk ?? null, data: { ...row?.data, ...data } };
870
- if (!row) next.local = true;
871
- await db.rows.put(next);
872
- const carried = Object.assign({}, ...rejected.filter((c) => c.op === "upsert").map((c) => c.data)) as Record<
873
- string,
874
- unknown
875
- >;
876
- await db.outbox.bulkDelete(rejected.map((c) => c.seq!));
877
- const last = ofRow.filter((c) => c.scope === scope && c.status === "pending").at(-1);
878
- if (last && last.op === "upsert") {
879
- // Several edits before a round go up as one change, with the last one's time.
880
- await db.outbox.update(last.seq!, { data: { ...carried, ...last.data, ...data }, updated_at });
881
- } else {
882
- await db.outbox.add({ scope, entity, id, op, data: { ...carried, ...data }, updated_at, status: "pending" });
883
- }
884
- });
885
- engine.requestSync();
886
- },
887
-
888
- start() {
889
- if (started || forgotten || sharedComputer()) return;
890
- const other = running.get(db.name);
891
- if (other && other !== engine) other.stop();
892
- running.set(db.name, engine);
893
- started = true;
894
- captureEpoch().catch(() => undefined);
895
- listen();
896
- if (typeof window !== "undefined") {
897
- window.addEventListener("online", onOnline);
898
- window.addEventListener("offline", onOffline);
899
- }
900
- poll = setInterval(tick, pollMs);
901
- void refreshCounts();
902
- void run(false);
903
- },
904
-
905
- stop: halt,
906
-
907
- requestSync() {
908
- void refreshCounts();
909
- if (!started) return;
910
- if (soon) clearTimeout(soon);
911
- soon = setTimeout(() => {
912
- soon = null;
913
- tick();
914
- }, soonMs);
915
- },
916
-
917
- syncNow: () => runFresh(false),
918
-
919
- async retryBlocked() {
920
- await runFresh(true);
921
- },
922
-
923
- async discardRejected(scope) {
924
- const gone = (await db.outbox.where("status").equals("rejected").toArray()).filter(
925
- (c) => !scope || c.scope === scope,
926
- );
927
- if (gone.length === 0) return;
928
- await db.transaction("rw", db.rows, db.outbox, db.meta, async () => {
929
- await db.outbox.bulkDelete(gone.map((c) => c.seq!));
930
- for (const change of gone) {
931
- const others = await db.outbox.where("[entity+id]").equals([change.entity, change.id]).count();
932
- if (others === 0) await db.rows.delete([change.entity, change.id]);
933
- }
934
- // The scope pulls afresh, so the copy shows what the server holds.
935
- for (const s of new Set(gone.map((c) => c.scope))) await db.meta.put({ key: lastKnowledgeKey(s), value: 0 });
936
- });
937
- await refreshCounts();
938
- engine.requestSync();
939
- },
940
-
941
- async rejected() {
942
- return db.outbox.where("status").equals("rejected").sortBy("seq");
943
- },
944
-
945
- async forget({ keepQueue }) {
946
- drop();
947
- // Its own forget() starts this engine's next session: it runs again where another
948
- // tab's forget() had ended it.
949
- forgotten = false;
950
- const epoch = freshEpoch();
951
- epochs.add(epoch);
952
- const tables = keepQueue ? [db.rows, db.meta] : db.tables;
953
- forgetting += 1;
954
- try {
955
- await db.transaction("rw", tables, async () => {
956
- for (const table of tables) await table.clear();
957
- await db.meta.put({ key: EPOCH, value: epoch });
958
- });
959
- } finally {
960
- forgetting -= 1;
961
- }
962
- // Best-effort: the epoch in `meta` still stops another tab's next round. This engine's
963
- // own channel hears it too, and keeps running: the epoch is already one of its own.
964
- postOnce(across, { epoch });
965
- // Another tab's forget() met while this clear ran was left to the copy: one committed
966
- // before this clear was undone by it; one committed after it ended this session too.
967
- // (Its message is posted after its commit, so that commit is in the epoch read here.)
968
- const now = await readEpoch().catch(() => epoch);
969
- await (epochs.has(now) ? endSession() : forgetHere());
970
- },
971
-
972
- getState: () => state,
973
-
974
- subscribe(listener) {
975
- listeners.add(listener);
976
- return () => listeners.delete(listener);
977
- },
978
- };
979
- return engine;
980
- }
981
-
982
- /**
983
- * The engine kept for `options.db` (by its name), built on first use: one engine per
984
- * database for the session (D15), so a company switch A→B→A finds A's engine again instead
985
- * of building a second one beside a round in flight. The first call's options stand: read
986
- * changing values (scopes, the client) through functions. A database object opened afresh
987
- * under the same name replaces the kept engine (the old one is stopped).
988
- */
989
- export function syncEngineFor<D extends SyncDb = SyncDb>(options: SyncEngineOptions<D>): SyncEngine {
990
- const name = options.db.name;
991
- const found = kept.get(name);
992
- if (found && found.db === options.db) return found.engine;
993
- found?.engine.stop();
994
- const engine = createSyncEngine(options);
995
- kept.set(name, { db: options.db, engine });
996
- return engine;
997
- }
998
-
999
- /**
1000
- * Let go of the engine {@link syncEngineFor} keeps for the database `name` (0.46): it is
1001
- * stopped, its database closed and forgotten, so a later `syncEngineFor` builds afresh. Call it
1002
- * on sign-out (and for a company left for good): every database visited in a tab otherwise
1003
- * keeps its engine and an open IndexedDB connection until a reload. Nothing kept: a no-op.
1004
- */
1005
- export function releaseSyncEngine(name: string): void {
1006
- const found = kept.get(name);
1007
- if (!found) return;
1008
- kept.delete(name);
1009
- found.engine.stop();
1010
- found.db.close();
1011
- }