@eifi1/ui-kit 0.51.7 → 0.53.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.
- package/README.md +178 -67
- package/dist/account/account-parts.d.ts +10 -8
- package/dist/account/account-parts.js +1 -18
- package/dist/account/account-parts.js.map +1 -1
- package/dist/account/data-export-setting-labels.js +1 -1
- package/dist/account/data-export-setting-labels.js.map +1 -1
- package/dist/account/delete-account-setting-labels.js +1 -1
- package/dist/account/delete-account-setting-labels.js.map +1 -1
- package/dist/account/email-change-setting-labels.js +1 -1
- package/dist/account/email-change-setting-labels.js.map +1 -1
- package/dist/account/passkeys-setting.d.ts +9 -2
- package/dist/account/passkeys-setting.js +41 -29
- package/dist/account/passkeys-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +3 -2
- package/dist/account/sessions-setting.js +10 -8
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.js +1 -3
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/admin-parts.d.ts +6 -3
- package/dist/admin/admin-parts.js +6 -21
- package/dist/admin/admin-parts.js.map +1 -1
- package/dist/admin/mail-test-card-labels.js +1 -1
- package/dist/admin/mail-test-card-labels.js.map +1 -1
- package/dist/admin/plan-change-labels.d.ts +2 -2
- package/dist/admin/plan-change-labels.js +1 -17
- package/dist/admin/plan-change-labels.js.map +1 -1
- package/dist/admin/plan-change.d.ts +3 -42
- package/dist/admin/plan-change.js +1 -1
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.js +3 -3
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/auth/auth-errors-labels.d.ts +11 -0
- package/dist/auth/auth-errors-labels.js +13 -0
- package/dist/auth/auth-errors-labels.js.map +1 -0
- package/dist/auth/auth-errors.d.ts +7 -23
- package/dist/auth/auth-errors.js +2 -15
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/forgot-password-form-labels.js +1 -1
- package/dist/auth/forgot-password-form-labels.js.map +1 -1
- package/dist/auth/forgot-password-form.js +7 -2
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form-labels.js +1 -1
- package/dist/auth/register-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form-labels.js +1 -1
- package/dist/auth/reset-password-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form.js +5 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form-labels.js +1 -1
- package/dist/auth/sign-in-form-labels.js.map +1 -1
- package/dist/auth/status-parts.d.ts +0 -3
- package/dist/auth/status-parts.js +0 -4
- package/dist/auth/status-parts.js.map +1 -1
- package/dist/auth/verify-email-labels.js +1 -1
- package/dist/auth/verify-email-labels.js.map +1 -1
- package/dist/auth/verify-email.js +7 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +10 -7
- package/dist/billing/cancel-subscription.js +6 -6
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/plan-card.d.ts +5 -3
- package/dist/billing/plan-card.js +7 -8
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-change-labels.d.ts +42 -0
- package/dist/billing/plan-change-labels.js +22 -0
- package/dist/billing/plan-change-labels.js.map +1 -0
- package/dist/billing/plan-switch.d.ts +9 -6
- package/dist/billing/plan-switch.js +7 -5
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +4 -3
- package/dist/chart.js +72 -4
- package/dist/chart.js.map +1 -1
- package/dist/components/account-chips.d.ts +5 -5
- package/dist/components/account-chips.js +11 -13
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.d.ts +16 -5
- package/dist/components/alert-banner.js +46 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -0
- package/dist/components/amount-input.js +1 -0
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +57 -65
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +12 -2
- package/dist/components/bulk-action-bar.js +65 -21
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-look.d.ts +5 -5
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/chart-edge.d.ts +16 -0
- package/dist/components/chart-edge.js +14 -1
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-figure.d.ts +13 -0
- package/dist/components/chart-figure.js +15 -0
- package/dist/components/chart-figure.js.map +1 -0
- package/dist/components/chart-zoom-core.js +1 -4
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/chart.d.ts +8 -1
- package/dist/components/chart.js +19 -10
- package/dist/components/chart.js.map +1 -1
- package/dist/components/combobox-core.d.ts +111 -2
- package/dist/components/combobox-core.js +148 -42
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +141 -130
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +6 -5
- package/dist/components/copy-button.js +5 -3
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.js +2 -2
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +1 -1
- package/dist/components/data-table-column-settings.js +33 -7
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +4 -2
- package/dist/components/data-table-filters.js +2 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +6 -4
- package/dist/components/data-table-fit.js +4 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.d.ts +1 -1
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.d.ts +1 -1
- package/dist/components/data-table-head.js +15 -12
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +12 -7
- package/dist/components/data-table-mobile.js +43 -30
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-pagination.js +11 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-resize.d.ts +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +6 -2
- package/dist/components/data-table-row.js +17 -10
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table-selection.d.ts +1 -1
- package/dist/components/data-table-selection.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +10 -2
- package/dist/components/data-table-shared.js +18 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +21 -2
- package/dist/components/data-table-sort.js +24 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table-virtual.d.ts +1 -1
- package/dist/components/data-table-virtual.js +18 -6
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/data-table.d.ts +13 -4
- package/dist/components/data-table.js +139 -36
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-field.d.ts +25 -3
- package/dist/components/date-field.js +33 -8
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +16 -3
- package/dist/components/date-picker.js +55 -26
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +38 -24
- package/dist/components/date-range-picker.js +23 -7
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/error-boundary.js +37 -30
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
- package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
- package/dist/components/field-base-labels.js.map +1 -0
- package/dist/components/field-base.d.ts +4 -3
- package/dist/components/field-base.js +3 -3
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/form-actions.js +11 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +5 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +14 -9
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.d.ts +43 -0
- package/dist/components/inline-confirm.js +67 -0
- package/dist/components/inline-confirm.js.map +1 -0
- package/dist/components/inline-entity-combobox.d.ts +4 -3
- package/dist/components/inline-entity-combobox.js +148 -140
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +28 -112
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal-page.d.ts +3 -1
- package/dist/components/legal-page.js +3 -2
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.d.ts +4 -2
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.d.ts +2 -2
- package/dist/components/lightbox.js +17 -6
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/list.d.ts +6 -2
- package/dist/components/list.js +3 -2
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.d.ts +1 -1
- package/dist/components/measured-grid.js +2 -1
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/mini-calendar.js +3 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.js +3 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-grid.d.ts +55 -0
- package/dist/components/month-grid.js +148 -0
- package/dist/components/month-grid.js.map +1 -0
- package/dist/components/month-picker.d.ts +36 -4
- package/dist/components/month-picker.js +123 -146
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +11 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/nav-pills.d.ts +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +7 -0
- package/dist/components/pie-chart-labels.js +1 -0
- package/dist/components/pie-chart-labels.js.map +1 -1
- package/dist/components/pie-chart.d.ts +5 -3
- package/dist/components/pie-chart.js +7 -22
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +3 -0
- package/dist/components/popover.js +7 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.js +2 -1
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +4 -4
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +7 -4
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +92 -23
- package/dist/components/series-chart.js +74 -30
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +3 -3
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/tabs-labels.d.ts +2 -0
- package/dist/components/tabs-labels.js +11 -0
- package/dist/components/tabs-labels.js.map +1 -0
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +2 -2
- package/dist/components/toggle-legend.js +6 -6
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -2
- package/dist/components/translation-review-editor.js +2 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.d.ts +2 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +31 -0
- package/dist/components/treemap-labels.js +10 -0
- package/dist/components/treemap-labels.js.map +1 -0
- package/dist/components/treemap-layout.d.ts +62 -0
- package/dist/components/treemap-layout.js +126 -0
- package/dist/components/treemap-layout.js.map +1 -0
- package/dist/components/treemap.d.ts +167 -19
- package/dist/components/treemap.js +340 -100
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.d.ts +5 -5
- package/dist/components/use-table-state.js +4 -1
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +1 -4
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-countdown.d.ts +0 -5
- package/dist/demo/demo-countdown.js +0 -4
- package/dist/demo/demo-countdown.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +36 -7
- package/dist/eslint/role-utilities.js +42 -9
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +25 -16
- package/dist/feedback/feedback-dialog.js +6 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-mutation.d.ts +18 -0
- package/dist/feedback/feedback-mutation.js +8 -0
- package/dist/feedback/feedback-mutation.js.map +1 -0
- package/dist/feedback/feedback-page-link.d.ts +53 -0
- package/dist/feedback/feedback-page-link.js +37 -0
- package/dist/feedback/feedback-page-link.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +8 -4
- package/dist/feedback/feedback-public-list.js +2 -2
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-replies.d.ts +3 -1
- package/dist/feedback/feedback-replies.js +5 -4
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -3
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +33 -44
- package/dist/feedback/feedback-row-detail.js +28 -34
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +17 -14
- package/dist/feedback/feedback-status-undo.js +10 -15
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
- package/dist/feedback/feedback-submit-dialog.js +1 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.js +9 -3
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +21 -9
- package/dist/feedback/feedback-table.js +45 -25
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread-labels.d.ts +2 -2
- package/dist/feedback/feedback-thread-labels.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +15 -23
- package/dist/feedback/feedback-thread.js +2 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +6 -6
- package/dist/feedback.js +90 -6
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +35 -0
- package/dist/hooks/use-focus-trap.js +6 -4
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +5 -2
- package/dist/hooks/use-hotkey.js +5 -4
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-long-press.d.ts +7 -4
- package/dist/hooks/use-long-press.js +3 -0
- package/dist/hooks/use-long-press.js.map +1 -1
- package/dist/hooks/use-media-query.js +12 -4
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-mounted.d.ts +22 -0
- package/dist/hooks/use-mounted.js +24 -0
- package/dist/hooks/use-mounted.js.map +1 -0
- package/dist/hooks/use-overlay-history.js +19 -3
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +4 -9
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +7 -18
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +9 -11
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +15 -64
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +7 -18
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -27
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +9 -18
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +7 -19
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +8 -18
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +12 -2
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +0 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +21 -23
- package/dist/index.js +50 -65
- package/dist/index.js.map +1 -1
- package/dist/landing/routing.d.ts +3 -1
- package/dist/landing/routing.js +4 -7
- package/dist/landing/routing.js.map +1 -1
- package/dist/lib/broadcast.d.ts +17 -0
- package/dist/lib/broadcast.js +30 -0
- package/dist/lib/broadcast.js.map +1 -0
- package/dist/lib/date-format.d.ts +37 -2
- package/dist/lib/date-format.js +34 -8
- package/dist/lib/date-format.js.map +1 -1
- package/dist/lib/day-format.d.ts +46 -0
- package/dist/lib/day-format.js +46 -0
- package/dist/lib/day-format.js.map +1 -0
- package/dist/lib/describe-failure.d.ts +14 -0
- package/dist/lib/describe-failure.js +9 -0
- package/dist/lib/describe-failure.js.map +1 -0
- package/dist/lib/direction.d.ts +16 -0
- package/dist/lib/direction.js +9 -1
- package/dist/lib/direction.js.map +1 -1
- package/dist/lib/focus-left.d.ts +63 -0
- package/dist/lib/focus-left.js +67 -0
- package/dist/lib/focus-left.js.map +1 -0
- package/dist/lib/format.js +5 -14
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/http-status.d.ts +24 -0
- package/dist/lib/http-status.js +14 -0
- package/dist/lib/http-status.js.map +1 -0
- package/dist/lib/intl-cache.d.ts +14 -0
- package/dist/lib/intl-cache.js +15 -0
- package/dist/lib/intl-cache.js.map +1 -0
- package/dist/lib/iso-day.d.ts +11 -0
- package/dist/lib/iso-day.js +8 -0
- package/dist/lib/iso-day.js.map +1 -0
- package/dist/lib/lazy-with-fallback.d.ts +31 -0
- package/dist/lib/lazy-with-fallback.js +40 -0
- package/dist/lib/lazy-with-fallback.js.map +1 -0
- package/dist/lib/path-pattern.d.ts +34 -0
- package/dist/lib/path-pattern.js +24 -0
- package/dist/lib/path-pattern.js.map +1 -0
- package/dist/lib/reduced-motion.d.ts +2 -1
- package/dist/lib/reduced-motion.js.map +1 -1
- package/dist/lib/round.d.ts +2 -1
- package/dist/lib/round.js +2 -1
- package/dist/lib/round.js.map +1 -1
- package/dist/lib/safe-storage.d.ts +24 -5
- package/dist/lib/safe-storage.js +27 -2
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/lib/session-refresh.js +1 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/share-format.d.ts +13 -0
- package/dist/lib/share-format.js +10 -0
- package/dist/lib/share-format.js.map +1 -0
- package/dist/lib/text-case.d.ts +17 -0
- package/dist/lib/text-case.js +8 -0
- package/dist/lib/text-case.js.map +1 -0
- package/dist/lib/text-match.d.ts +29 -0
- package/dist/lib/text-match.js +20 -2
- package/dist/lib/text-match.js.map +1 -1
- package/dist/lib/thenable.d.ts +13 -0
- package/dist/lib/thenable.js +8 -0
- package/dist/lib/thenable.js.map +1 -0
- package/dist/lib/translation-review.d.ts +7 -2
- package/dist/lib/translation-review.js +4 -4
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/notifications/device-zone-report.js +1 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +2 -1
- package/dist/notifications/notification-bell.js +3 -3
- package/dist/notifications/notification-bell.js.map +1 -1
- package/dist/notifications/notification-inbox.d.ts +10 -6
- package/dist/notifications/notification-inbox.js +26 -48
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +3 -2
- package/dist/notifications/notification-parts.js +1 -3
- package/dist/notifications/notification-parts.js.map +1 -1
- package/dist/notifications/notification-types-setting.js +1 -1
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notifications-labels.d.ts +6 -0
- package/dist/notifications/notifications-labels.js +1 -0
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-device-setting.js +1 -1
- package/dist/notifications/push-device-setting.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +2 -1
- package/dist/notifications/push-devices-setting.js +27 -49
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +10 -3
- package/dist/notifications/quiet-hours-setting.js +65 -26
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/unsubscribe-panel.js +4 -7
- package/dist/notifications/unsubscribe-panel.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +39 -1
- package/dist/notifications/use-push-device.js +28 -7
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/query-persistence.d.ts +3 -1
- package/dist/offline/query-persistence.js +19 -2
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/rhf/fields.d.ts +11 -4
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/command-palette.js +1 -1
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.js +19 -9
- package/dist/search/global-search.js.map +1 -1
- package/dist/search/search-index.d.ts +0 -6
- package/dist/search/search-index.js +0 -4
- package/dist/search/search-index.js.map +1 -1
- package/dist/search/use-command-key.d.ts +12 -1
- package/dist/search/use-command-key.js +3 -12
- package/dist/search/use-command-key.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.js +6 -1
- package/dist/search.js.map +1 -1
- package/dist/settings/use-settings-focus.js +1 -0
- package/dist/settings/use-settings-focus.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +5 -3
- package/dist/shell/account-menu-language.js +2 -2
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +9 -6
- package/dist/shell/account-menu-text-size.js +2 -2
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/app-shell.d.ts +0 -10
- package/dist/shell/app-shell.js +1 -7
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/company-switcher.d.ts +1 -1
- package/dist/shell/company-switcher.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +1 -1
- package/dist/shell/dev-menu.js +5 -18
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +1 -1
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +1 -3
- package/dist/shell/top-bar-parts-labels.js +0 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +7 -10
- package/dist/shell.js +14 -23
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +30 -4
- package/dist/sync/engine.js +172 -52
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +2 -10
- package/dist/sync.js.map +1 -1
- package/dist/testing/css-variables.js +3 -8
- package/dist/testing/css-variables.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +0 -5
- package/dist/theme/chart-palette.js +0 -2
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -1
- package/dist/theme/contrast.js +3 -8
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-store.js +6 -6
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +3 -8
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +3 -6
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour-catalogue.d.ts +7 -4
- package/dist/tour/tour-catalogue.js +12 -10
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +0 -2
- package/dist/tour/tour-dock.js +0 -2
- package/dist/tour/tour-dock.js.map +1 -1
- package/dist/tour/tour-gates.d.ts +4 -0
- package/dist/tour/tour-gates.js +14 -0
- package/dist/tour/tour-gates.js.map +1 -1
- package/dist/tour/tour-overlay.d.ts +27 -0
- package/dist/tour/tour-overlay.js +741 -0
- package/dist/tour/tour-overlay.js.map +1 -0
- package/dist/tour/tour.d.ts +13 -3
- package/dist/tour/tour.js +59 -781
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.js +1 -1
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +5 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +6 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.d.ts +3 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.d.ts +3 -1
- package/dist/wizard/validation.js.map +1 -1
- package/package.json +10 -6
- package/src/account/account-parts.ts +5 -25
- package/src/account/data-export-setting-labels.ts +1 -1
- package/src/account/delete-account-setting-labels.ts +1 -1
- package/src/account/email-change-setting-labels.ts +1 -1
- package/src/account/passkeys-setting.tsx +60 -38
- package/src/account/sessions-setting.tsx +18 -12
- package/src/admin/admin-action-confirm.tsx +1 -5
- package/src/admin/admin-parts.ts +11 -25
- package/src/admin/mail-test-card-labels.ts +1 -1
- package/src/admin/plan-change-labels.ts +5 -23
- package/src/admin/plan-change.tsx +7 -44
- package/src/admin/reviewer-scope-editor.tsx +3 -3
- package/src/auth/auth-errors-labels.ts +23 -0
- package/src/auth/auth-errors.ts +11 -38
- package/src/auth/complete-name-dialog.tsx +4 -1
- package/src/auth/forgot-password-form-labels.ts +1 -1
- package/src/auth/forgot-password-form.tsx +11 -6
- package/src/auth/register-form-labels.ts +1 -1
- package/src/auth/reset-password-form-labels.ts +1 -1
- package/src/auth/reset-password-form.tsx +8 -4
- package/src/auth/sign-in-form-labels.ts +1 -1
- package/src/auth/status-parts.tsx +0 -6
- package/src/auth/verify-email-labels.ts +1 -1
- package/src/auth/verify-email.tsx +11 -6
- package/src/billing/cancel-subscription.tsx +15 -12
- package/src/billing/plan-card.tsx +18 -10
- package/src/billing/plan-change-labels.ts +66 -0
- package/src/billing/plan-switch.tsx +15 -12
- package/src/chart.ts +73 -6
- package/src/components/account-chips.tsx +20 -23
- package/src/components/alert-banner.tsx +82 -5
- package/src/components/amount-input.tsx +18 -7
- package/src/components/autocomplete.tsx +64 -70
- package/src/components/bulk-action-bar.tsx +70 -2
- package/src/components/button-look.ts +5 -5
- package/src/components/button.tsx +2 -2
- package/src/components/chart-edge.ts +34 -1
- package/src/components/chart-figure.tsx +35 -0
- package/src/components/chart-zoom-core.tsx +6 -5
- package/src/components/chart.tsx +26 -5
- package/src/components/combobox-core.tsx +293 -62
- package/src/components/combobox.tsx +183 -153
- package/src/components/copy-button.tsx +34 -23
- package/src/components/country-select.tsx +16 -11
- package/src/components/data-table-column-settings.tsx +52 -10
- package/src/components/data-table-filters.ts +6 -4
- package/src/components/data-table-fit.ts +19 -5
- package/src/components/data-table-groups.tsx +1 -1
- package/src/components/data-table-head.tsx +32 -22
- package/src/components/data-table-mobile.tsx +61 -26
- package/src/components/data-table-pagination.tsx +21 -6
- package/src/components/data-table-resize.ts +1 -1
- package/src/components/data-table-row-actions.tsx +3 -3
- package/src/components/data-table-row.tsx +25 -14
- package/src/components/data-table-selection.ts +1 -1
- package/src/components/data-table-shared.tsx +33 -1
- package/src/components/data-table-sort.ts +57 -2
- package/src/components/data-table-virtual.ts +32 -8
- package/src/components/data-table.tsx +239 -68
- package/src/components/date-field.tsx +66 -9
- package/src/components/date-picker.tsx +125 -49
- package/src/components/date-range-picker.tsx +71 -36
- package/src/components/error-boundary.tsx +53 -39
- package/src/components/field-base-labels.ts +20 -0
- package/src/components/field-base.tsx +13 -7
- package/src/components/form-actions.tsx +20 -5
- package/src/components/hover-menu.tsx +18 -8
- package/src/components/icon-button.tsx +14 -9
- package/src/components/inline-confirm.tsx +116 -0
- package/src/components/inline-entity-combobox.tsx +197 -153
- package/src/components/input.tsx +1 -1
- package/src/components/jump-calendar.tsx +30 -146
- package/src/components/legal-page.tsx +10 -7
- package/src/components/legal.tsx +4 -2
- package/src/components/lightbox.tsx +21 -8
- package/src/components/list.tsx +10 -4
- package/src/components/measured-grid.tsx +8 -4
- package/src/components/mini-calendar.tsx +6 -5
- package/src/components/modal.tsx +6 -1
- package/src/components/month-grid.tsx +229 -0
- package/src/components/month-picker.tsx +192 -200
- package/src/components/multi-select.tsx +21 -9
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/picker-sheet.tsx +12 -9
- package/src/components/pie-chart-labels.ts +8 -0
- package/src/components/pie-chart.tsx +22 -28
- package/src/components/popover.tsx +18 -1
- package/src/components/row-actions.tsx +2 -1
- package/src/components/series-chart-axes.ts +9 -4
- package/src/components/series-chart-labels.ts +7 -4
- package/src/components/series-chart.tsx +253 -87
- package/src/components/share-card.tsx +6 -6
- package/src/components/tabs-labels.ts +13 -0
- package/src/components/tabs.tsx +2 -2
- package/src/components/toggle-legend.tsx +16 -11
- package/src/components/translation-review-editor.tsx +6 -5
- package/src/components/translation-review.tsx +4 -3
- package/src/components/treemap-labels.ts +47 -0
- package/src/components/treemap-layout.ts +210 -0
- package/src/components/treemap.tsx +780 -138
- package/src/components/use-table-state.ts +12 -9
- package/src/data-table.ts +1 -4
- package/src/demo/demo-countdown.ts +3 -8
- package/src/eslint/role-utilities.ts +97 -19
- package/src/feedback/feedback-dialog.tsx +30 -19
- package/src/feedback/feedback-mutation.ts +20 -0
- package/src/feedback/feedback-page-link.ts +90 -0
- package/src/feedback/feedback-public-list.tsx +13 -7
- package/src/feedback/feedback-replies.tsx +13 -6
- package/src/feedback/feedback-row-detail-labels.ts +0 -3
- package/src/feedback/feedback-row-detail.tsx +70 -84
- package/src/feedback/feedback-status-undo.ts +28 -28
- package/src/feedback/feedback-submit-dialog.tsx +2 -2
- package/src/feedback/feedback-submit.tsx +21 -7
- package/src/feedback/feedback-swipe.tsx +1 -1
- package/src/feedback/feedback-table.tsx +86 -38
- package/src/feedback/feedback-thread-labels.ts +2 -2
- package/src/feedback/feedback-thread.tsx +18 -26
- package/src/feedback.ts +89 -6
- package/src/hooks/use-focus-trap.ts +8 -24
- package/src/hooks/use-hotkey.ts +24 -7
- package/src/hooks/use-long-press.ts +16 -2
- package/src/hooks/use-media-query.ts +20 -4
- package/src/hooks/use-mounted.ts +39 -0
- package/src/hooks/use-overlay-history.ts +32 -3
- package/src/i18n/defaults.ts +4 -9
- package/src/i18n/german.ts +8 -19
- package/src/i18n/kit-labels.tsx +9 -11
- package/src/i18n/locales/es.ts +8 -19
- package/src/i18n/locales/fr.ts +21 -30
- package/src/i18n/locales/hu.ts +10 -19
- package/src/i18n/locales/it.ts +8 -20
- package/src/i18n/locales/zh.ts +9 -19
- package/src/i18n/review.ts +12 -2
- package/src/i18n/shared-labels.ts +0 -1
- package/src/index.ts +57 -49
- package/src/landing/routing.tsx +14 -10
- package/src/lib/broadcast.ts +52 -0
- package/src/lib/date-format.ts +89 -13
- package/src/lib/day-format.ts +96 -0
- package/src/lib/describe-failure.ts +18 -0
- package/src/lib/direction.ts +39 -0
- package/src/lib/focus-left.ts +156 -0
- package/src/lib/format.ts +5 -17
- package/src/lib/http-status.ts +38 -0
- package/src/lib/intl-cache.ts +25 -0
- package/src/lib/iso-day.ts +13 -0
- package/src/lib/lazy-with-fallback.ts +82 -0
- package/src/lib/path-pattern.ts +55 -0
- package/src/lib/reduced-motion.ts +2 -1
- package/src/lib/round.ts +7 -2
- package/src/lib/safe-storage.ts +48 -6
- package/src/lib/session-refresh.ts +1 -1
- package/src/lib/share-format.ts +18 -0
- package/src/lib/text-case.ts +19 -0
- package/src/lib/text-match.ts +57 -2
- package/src/lib/thenable.ts +19 -0
- package/src/lib/translation-review.ts +13 -5
- package/src/notifications/device-zone-report.ts +1 -1
- package/src/notifications/notification-bell.tsx +7 -4
- package/src/notifications/notification-inbox.tsx +40 -58
- package/src/notifications/notification-parts.tsx +3 -4
- package/src/notifications/notification-types-setting.tsx +1 -1
- package/src/notifications/notifications-labels.ts +7 -0
- package/src/notifications/push-device-setting.tsx +1 -1
- package/src/notifications/push-devices-setting.tsx +30 -55
- package/src/notifications/quiet-hours-setting.tsx +93 -31
- package/src/notifications/unsubscribe-panel.tsx +4 -7
- package/src/notifications/use-push-device.ts +76 -10
- package/src/offline/query-persistence.ts +35 -3
- package/src/rhf/fields.tsx +11 -4
- package/src/search/command-palette.tsx +2 -4
- package/src/search/global-search.tsx +36 -15
- package/src/search/search-index.ts +0 -15
- package/src/search/use-command-key.ts +13 -12
- package/src/search.ts +10 -1
- package/src/settings/use-settings-focus.ts +3 -0
- package/src/shell/account-menu-language.tsx +6 -4
- package/src/shell/account-menu-text-size.tsx +10 -7
- package/src/shell/app-shell.tsx +0 -13
- package/src/shell/company-switcher.tsx +1 -1
- package/src/shell/dev-menu.tsx +8 -22
- package/src/shell/top-bar-actions.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +1 -4
- package/src/shell.ts +21 -17
- package/src/sync/engine.ts +339 -61
- package/src/sync.ts +2 -8
- package/src/testing/css-variables.ts +8 -9
- package/src/theme/chart-palette.ts +0 -2
- package/src/theme/contrast.ts +8 -9
- package/src/theme/palette-store.ts +9 -9
- package/src/theme/text-size.ts +6 -8
- package/src/theme/theme-store.ts +7 -9
- package/src/tour/tour-catalogue.tsx +26 -15
- package/src/tour/tour-dock.ts +0 -2
- package/src/tour/tour-gates.ts +23 -0
- package/src/tour/tour-overlay.tsx +1170 -0
- package/src/tour/tour.tsx +108 -1202
- package/src/tour/use-tour-progress.ts +1 -1
- package/src/wizard/stepper-nav.tsx +3 -1
- package/src/wizard/types.ts +11 -2
- package/src/wizard/use-wizard.ts +2 -1
- package/src/wizard/validation.ts +8 -2
- package/tokens.css +6 -5
- package/dist/components/field-sync-labels.d.ts +0 -2
- package/dist/components/field-sync-labels.js +0 -12
- package/dist/components/field-sync-labels.js.map +0 -1
- package/dist/components/field-sync.d.ts +0 -164
- package/dist/components/field-sync.js +0 -269
- package/dist/components/field-sync.js.map +0 -1
- package/dist/components/section-nav.d.ts +0 -37
- package/dist/components/section-nav.js +0 -33
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/sign-chip-labels.d.ts +0 -2
- package/dist/components/sign-chip-labels.js +0 -11
- package/dist/components/sign-chip-labels.js.map +0 -1
- package/dist/components/sign-chip.d.ts +0 -68
- package/dist/components/sign-chip.js +0 -47
- package/dist/components/sign-chip.js.map +0 -1
- package/dist/components/ui-labels.js.map +0 -1
- package/dist/lib/isin.d.ts +0 -28
- package/dist/lib/isin.js +0 -36
- package/dist/lib/isin.js.map +0 -1
- package/dist/shell/dev-switchers.d.ts +0 -6
- package/dist/shell/dev-switchers.js +0 -12
- package/dist/shell/dev-switchers.js.map +0 -1
- package/dist/shell/role-switcher.d.ts +0 -21
- package/dist/shell/role-switcher.js +0 -31
- package/dist/shell/role-switcher.js.map +0 -1
- package/src/components/field-sync-labels.ts +0 -13
- package/src/components/field-sync.tsx +0 -482
- package/src/components/section-nav.tsx +0 -65
- package/src/components/sign-chip-labels.ts +0 -12
- package/src/components/sign-chip.tsx +0 -124
- package/src/components/ui-labels.ts +0 -24
- package/src/lib/isin.ts +0 -62
- package/src/shell/dev-switchers.tsx +0 -18
- package/src/shell/role-switcher.tsx +0 -46
package/src/sync/engine.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listenOn, postOnce } from "../lib/broadcast";
|
|
2
|
+
import { httpStatusOf } from "../lib/http-status";
|
|
2
3
|
import { isOfflineError, stampAfter } from "../offline/local-first";
|
|
3
4
|
import { isSharedComputer } from "../offline/shared-computer";
|
|
4
5
|
import type { OutboxChange, OutboxStatus, SyncDb, SyncedRow } from "./db";
|
|
@@ -16,6 +17,15 @@ import type { SyncRequestBody, SyncResponseBody, SyncWireChange } from "./wire";
|
|
|
16
17
|
* the generation is unchanged (D15); follow `page_cursor` with pull-only requests, the cursor
|
|
17
18
|
* written with the last page only (D12). Then the app's `steps`, then one more pass if a step
|
|
18
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.
|
|
19
29
|
*/
|
|
20
30
|
|
|
21
31
|
export interface SyncCounts {
|
|
@@ -27,7 +37,8 @@ export interface SyncCounts {
|
|
|
27
37
|
|
|
28
38
|
export interface SyncStepContext {
|
|
29
39
|
signal: AbortSignal;
|
|
30
|
-
/** False once `forget()` bumped the generation
|
|
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). */
|
|
31
42
|
isCurrent(): boolean;
|
|
32
43
|
}
|
|
33
44
|
|
|
@@ -60,7 +71,9 @@ export interface SyncEngineState {
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
export interface SyncRoundResult {
|
|
63
|
-
/** `paused`: `paused()` held the round.
|
|
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. */
|
|
64
77
|
outcome: "synced" | "offline" | "error" | "paused" | "dropped";
|
|
65
78
|
/** Changes this round set `waiting`, `blocked` or `rejected`. */
|
|
66
79
|
waiting: number;
|
|
@@ -97,6 +110,18 @@ export interface SyncEngineOptions<D extends SyncDb = SyncDb> {
|
|
|
97
110
|
backoffMs?: readonly number[];
|
|
98
111
|
/** Default `isSharedComputer`: on a computer marked shared the engine does not start (D15). */
|
|
99
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;
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
export interface SyncEngine {
|
|
@@ -110,13 +135,20 @@ export interface SyncEngine {
|
|
|
110
135
|
op: "upsert" | "delete",
|
|
111
136
|
data?: Record<string, unknown>,
|
|
112
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). */
|
|
113
144
|
start(): void;
|
|
114
145
|
/** Stops the poll and the timers, aborts the round in flight and drops its answer (the
|
|
115
146
|
* queue is laid back). A new engine's round on the same database waits for it. */
|
|
116
147
|
stop(): void;
|
|
117
148
|
/** Sync soon (debounced): after a write. */
|
|
118
149
|
requestSync(): void;
|
|
119
|
-
/** "Sync now": resets the backoff, runs a round
|
|
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. */
|
|
120
152
|
syncNow(): Promise<SyncRoundResult>;
|
|
121
153
|
/** A round that sends the `blocked` changes too. */
|
|
122
154
|
retryBlocked(): Promise<void>;
|
|
@@ -125,7 +157,11 @@ export interface SyncEngine {
|
|
|
125
157
|
/** The rejected changes with their reasons, for the app's banner. */
|
|
126
158
|
rejected(): Promise<OutboxChange[]>;
|
|
127
159
|
/** Sign-out: bump the generation (a round in flight writes nothing more), then clear the
|
|
128
|
-
* rows and `meta` — and the outbox and the app's tables unless `keepQueue` (D15).
|
|
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. */
|
|
129
165
|
forget(opts: { keepQueue: boolean }): Promise<void>;
|
|
130
166
|
getState(): SyncEngineState;
|
|
131
167
|
subscribe(listener: () => void): () => void;
|
|
@@ -133,8 +169,14 @@ export interface SyncEngine {
|
|
|
133
169
|
|
|
134
170
|
export const DEFAULT_SYNC_BACKOFF_MS: readonly number[] = [5_000, 15_000, 60_000, 300_000];
|
|
135
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;
|
|
136
176
|
|
|
137
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";
|
|
138
180
|
const lastKnowledgeKey = (scope: string) => `lastKnowledge:${scope}`;
|
|
139
181
|
const UNSENT: readonly OutboxStatus[] = ["pending", "sending", "waiting", "blocked"];
|
|
140
182
|
const ZERO: SyncCounts = { pending: 0, waiting: 0, blocked: 0, rejected: 0 };
|
|
@@ -152,6 +194,22 @@ const kept = new Map<string, { db: SyncDb; engine: SyncEngine }>();
|
|
|
152
194
|
/** The generation changed under a round: its answer is dropped. */
|
|
153
195
|
class Dropped extends Error {}
|
|
154
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
|
+
|
|
155
213
|
/** A row created on this device and never echoed by the server carries `local: true`. */
|
|
156
214
|
type CopyRow = SyncedRow & { local?: boolean };
|
|
157
215
|
|
|
@@ -204,6 +262,9 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
204
262
|
const soonMs = options.soonMs ?? 800;
|
|
205
263
|
const backoffMs = options.backoffMs?.length ? options.backoffMs : DEFAULT_SYNC_BACKOFF_MS;
|
|
206
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>();
|
|
207
268
|
|
|
208
269
|
let generation = 0;
|
|
209
270
|
let state: SyncEngineState = {
|
|
@@ -224,6 +285,20 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
224
285
|
let controller: AbortController | null = null;
|
|
225
286
|
let stepCounts: Record<string, Partial<SyncCounts>> = {};
|
|
226
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
|
+
|
|
227
302
|
function set(next: Partial<SyncEngineState>) {
|
|
228
303
|
state = { ...state, ...next };
|
|
229
304
|
listeners.forEach((listener) => listener());
|
|
@@ -253,12 +328,89 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
253
328
|
set({ counts, lastSyncedAt: typeof last === "string" ? last : null });
|
|
254
329
|
}
|
|
255
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
|
+
|
|
256
401
|
// ── The queue ──────────────────────────────────────────────────────────────
|
|
257
402
|
|
|
258
403
|
/** Mark the scope's queue as in the request (a `sending` left by a closed tab goes back
|
|
259
|
-
* first); answers it with the statuses it had.
|
|
260
|
-
|
|
261
|
-
|
|
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);
|
|
262
414
|
await db.outbox
|
|
263
415
|
.where("scope")
|
|
264
416
|
.equals(scope)
|
|
@@ -273,8 +425,14 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
273
425
|
});
|
|
274
426
|
}
|
|
275
427
|
|
|
276
|
-
/** The request failed: each change back to the status it had.
|
|
277
|
-
|
|
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;
|
|
278
436
|
return db.outbox.bulkUpdate(sent.map((c) => ({ key: c.seq!, changes: { status: c.status } })));
|
|
279
437
|
}
|
|
280
438
|
|
|
@@ -282,10 +440,19 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
282
440
|
return db.outbox.bulkUpdate(changes.map((c) => ({ key: c.seq!, changes: fields })));
|
|
283
441
|
}
|
|
284
442
|
|
|
285
|
-
/** Put one page's rows in the copy, settle the sent changes, lay the unsent ones back.
|
|
286
|
-
|
|
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
|
+
) {
|
|
287
454
|
return db.transaction("rw", db.rows, db.outbox, db.meta, async () => {
|
|
288
|
-
|
|
455
|
+
await standing(gen, epoch);
|
|
289
456
|
const refused = answer.refused ?? null;
|
|
290
457
|
const refusedIds = new Set(refused?.change_ids ?? []);
|
|
291
458
|
const rejections = new Map((answer.rejections ?? []).map((r) => [r.change_id, r]));
|
|
@@ -367,10 +534,11 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
367
534
|
async function syncScope(
|
|
368
535
|
scope: string,
|
|
369
536
|
gen: number,
|
|
537
|
+
epoch: string | null,
|
|
370
538
|
signal: AbortSignal,
|
|
371
539
|
withBlocked: boolean,
|
|
372
540
|
): Promise<ScopeResult> {
|
|
373
|
-
const sent = await takeQueue(scope, withBlocked);
|
|
541
|
+
const sent = await takeQueue(scope, withBlocked, gen, epoch);
|
|
374
542
|
const last = Number((await db.meta.get(lastKnowledgeKey(scope)))?.value ?? 0) || 0;
|
|
375
543
|
const body = (changes: SyncWireChange[], page_cursor: SyncRequestBody["page_cursor"]): SyncRequestBody => ({
|
|
376
544
|
scope,
|
|
@@ -384,25 +552,30 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
384
552
|
try {
|
|
385
553
|
answer = await options.send(body(sent.map(toWire), null), signal);
|
|
386
554
|
} catch (err) {
|
|
387
|
-
await
|
|
555
|
+
await layBack(sent, signal);
|
|
388
556
|
if (gen !== generation) throw new Dropped();
|
|
389
557
|
if (isOffline(err)) return { offline: true };
|
|
390
558
|
const plan = planCode(err);
|
|
391
|
-
if (plan)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
}
|
|
397
570
|
return { offline: false, waiting: 0, blocked: 0, rejected: await loseScope(scope, err) };
|
|
398
|
-
|
|
571
|
+
});
|
|
399
572
|
}
|
|
400
573
|
let cursor = options.paged ? (answer.page_cursor ?? null) : null;
|
|
401
574
|
let tally: ScopeResult;
|
|
402
575
|
try {
|
|
403
|
-
tally = { offline: false, ...(await apply(scope, answer, sent, gen, cursor == null)) };
|
|
576
|
+
tally = { offline: false, ...(await apply(scope, answer, sent, gen, epoch, cursor == null)) };
|
|
404
577
|
} catch (err) {
|
|
405
|
-
if (err instanceof Dropped) await
|
|
578
|
+
if (err instanceof Dropped) await layBack(sent, signal);
|
|
406
579
|
throw err;
|
|
407
580
|
}
|
|
408
581
|
// The rest of a paged pull: pull-only; the cursor is written with the last page alone,
|
|
@@ -417,7 +590,7 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
417
590
|
throw err;
|
|
418
591
|
}
|
|
419
592
|
cursor = page.page_cursor ?? null;
|
|
420
|
-
await apply(scope, page, [], gen, cursor == null);
|
|
593
|
+
await apply(scope, page, [], gen, epoch, cursor == null);
|
|
421
594
|
}
|
|
422
595
|
return tally;
|
|
423
596
|
}
|
|
@@ -426,17 +599,100 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
426
599
|
|
|
427
600
|
async function round(withBlocked: boolean, gen: number): Promise<SyncRoundResult> {
|
|
428
601
|
const result: SyncRoundResult = { outcome: "synced", waiting: 0, blocked: 0, rejected: 0 };
|
|
429
|
-
if (gen !== generation) return { ...result, outcome: "dropped" };
|
|
430
|
-
|
|
431
|
-
if (!browserOnline()) return { ...result, outcome: "offline" };
|
|
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.
|
|
432
604
|
const own = new AbortController();
|
|
433
605
|
controller = own;
|
|
434
|
-
set({ phase: "syncing" });
|
|
435
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" });
|
|
436
692
|
for (let pass = 0; ; pass++) {
|
|
437
693
|
for (const scope of options.scopes()) {
|
|
438
694
|
if (gen !== generation) throw new Dropped();
|
|
439
|
-
const r = await syncScope(scope, gen,
|
|
695
|
+
const r = await syncScope(scope, gen, epoch, signal, withBlocked);
|
|
440
696
|
if (r.offline) return { ...result, outcome: "offline" };
|
|
441
697
|
result.waiting += r.waiting;
|
|
442
698
|
result.blocked += r.blocked;
|
|
@@ -446,7 +702,7 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
446
702
|
let changed = false;
|
|
447
703
|
for (const step of steps) {
|
|
448
704
|
if (gen !== generation) throw new Dropped();
|
|
449
|
-
const ran = await step.run(db, { signal
|
|
705
|
+
const ran = await step.run(db, { signal, isCurrent: () => gen === generation });
|
|
450
706
|
if (gen !== generation) throw new Dropped();
|
|
451
707
|
if (ran.counts) stepCounts = { ...stepCounts, [step.name]: ran.counts };
|
|
452
708
|
if (ran.offline) return { ...result, outcome: "offline" };
|
|
@@ -456,10 +712,9 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
456
712
|
}
|
|
457
713
|
return result;
|
|
458
714
|
} catch (err) {
|
|
715
|
+
if (err instanceof ForgottenElsewhere) void forgetHere();
|
|
459
716
|
if (err instanceof Dropped || gen !== generation) return { ...result, outcome: "dropped" };
|
|
460
717
|
return { ...result, outcome: "error", error: err };
|
|
461
|
-
} finally {
|
|
462
|
-
if (controller === own) controller = null;
|
|
463
718
|
}
|
|
464
719
|
}
|
|
465
720
|
|
|
@@ -548,9 +803,12 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
548
803
|
if (started && !inBackoff()) void run(false);
|
|
549
804
|
}
|
|
550
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). */
|
|
551
809
|
async function runFresh(withBlocked: boolean): Promise<SyncRoundResult> {
|
|
552
810
|
resetBackoff();
|
|
553
|
-
|
|
811
|
+
while (current) {
|
|
554
812
|
again = false;
|
|
555
813
|
await current.catch(() => undefined);
|
|
556
814
|
}
|
|
@@ -564,6 +822,26 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
564
822
|
};
|
|
565
823
|
const onOffline = () => set({ online: false });
|
|
566
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
|
+
|
|
567
845
|
const engine: SyncEngine = {
|
|
568
846
|
async write(scope, entity, id, op, data = {}) {
|
|
569
847
|
await db.transaction("rw", db.rows, db.outbox, async () => {
|
|
@@ -608,11 +886,13 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
608
886
|
},
|
|
609
887
|
|
|
610
888
|
start() {
|
|
611
|
-
if (started || sharedComputer()) return;
|
|
889
|
+
if (started || forgotten || sharedComputer()) return;
|
|
612
890
|
const other = running.get(db.name);
|
|
613
891
|
if (other && other !== engine) other.stop();
|
|
614
892
|
running.set(db.name, engine);
|
|
615
893
|
started = true;
|
|
894
|
+
captureEpoch().catch(() => undefined);
|
|
895
|
+
listen();
|
|
616
896
|
if (typeof window !== "undefined") {
|
|
617
897
|
window.addEventListener("online", onOnline);
|
|
618
898
|
window.addEventListener("offline", onOffline);
|
|
@@ -622,24 +902,7 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
622
902
|
void run(false);
|
|
623
903
|
},
|
|
624
904
|
|
|
625
|
-
stop
|
|
626
|
-
if (!started) return;
|
|
627
|
-
started = false;
|
|
628
|
-
// The round in flight writes nothing more: its request is aborted and its answer
|
|
629
|
-
// dropped, the queue laid back; the next engine's round waits for it to settle.
|
|
630
|
-
generation += 1;
|
|
631
|
-
controller?.abort();
|
|
632
|
-
if (running.get(db.name) === engine) running.delete(db.name);
|
|
633
|
-
if (poll) clearInterval(poll);
|
|
634
|
-
poll = null;
|
|
635
|
-
if (soon) clearTimeout(soon);
|
|
636
|
-
soon = null;
|
|
637
|
-
clearRetry();
|
|
638
|
-
if (typeof window !== "undefined") {
|
|
639
|
-
window.removeEventListener("online", onOnline);
|
|
640
|
-
window.removeEventListener("offline", onOffline);
|
|
641
|
-
}
|
|
642
|
-
},
|
|
905
|
+
stop: halt,
|
|
643
906
|
|
|
644
907
|
requestSync() {
|
|
645
908
|
void refreshCounts();
|
|
@@ -680,15 +943,30 @@ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineO
|
|
|
680
943
|
},
|
|
681
944
|
|
|
682
945
|
async forget({ keepQueue }) {
|
|
683
|
-
|
|
684
|
-
|
|
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);
|
|
685
952
|
const tables = keepQueue ? [db.rows, db.meta] : db.tables;
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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());
|
|
692
970
|
},
|
|
693
971
|
|
|
694
972
|
getState: () => state,
|
package/src/sync.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// The only entry point that imports `dexie` — an optional peer for that reason. It never loads
|
|
5
5
|
// `/offline`'s TanStack and idb-keyval peers: the leaf utilities both entries export live in
|
|
6
6
|
// one module of their own (src/offline/local-first.ts). NOT re-exported from the main barrel.
|
|
7
|
-
export { createSyncDb, rowByPk, SYNC_DB_PK_INDEX
|
|
7
|
+
export { createSyncDb, rowByPk, SYNC_DB_PK_INDEX } from "./sync/db";
|
|
8
8
|
export type {
|
|
9
9
|
CreateSyncDbOptions,
|
|
10
10
|
OutboxChange,
|
|
@@ -14,13 +14,7 @@ export type {
|
|
|
14
14
|
SyncedRow,
|
|
15
15
|
SyncMetaEntry,
|
|
16
16
|
} from "./sync/db";
|
|
17
|
-
export {
|
|
18
|
-
createSyncEngine,
|
|
19
|
-
DEFAULT_HOLD_CODES,
|
|
20
|
-
DEFAULT_SYNC_BACKOFF_MS,
|
|
21
|
-
releaseSyncEngine,
|
|
22
|
-
syncEngineFor,
|
|
23
|
-
} from "./sync/engine";
|
|
17
|
+
export { createSyncEngine, DEFAULT_HOLD_CODES, releaseSyncEngine, syncEngineFor } from "./sync/engine";
|
|
24
18
|
export type {
|
|
25
19
|
SyncCounts,
|
|
26
20
|
SyncEngine,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { themeBlocks } from "../eslint/role-utilities";
|
|
1
2
|
import { KIT_NAMES, TAILWIND_THEME_NAMES } from "./css-variables.generated";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -68,17 +69,15 @@ function stripComments(text: string, isCss: boolean): string {
|
|
|
68
69
|
return isCss ? noBlocks : noBlocks.replace(/(^|[^:])(\/\/[^\n]*)/g, (_, pre: string, c: string) => pre + blank(c));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/** The text with every `@theme … inline … { … }` block blanked out, braces and all.
|
|
72
|
+
/** The text with every `@theme … inline … { … }` block blanked out, braces and all. The
|
|
73
|
+
* blocks are the kit's one block finder's, `themeBlocks` (src/eslint/role-utilities.ts,
|
|
74
|
+
* which imports nothing): this was a second brace-counting copy of it, kept equal to the
|
|
75
|
+
* generator's (scripts/gen-kit-css-variables.mjs, on `themeBlocks` since DUP-13) only by
|
|
76
|
+
* token-vars-declared.test.ts (0.52 code review). */
|
|
72
77
|
function withoutInlineTheme(css: string): string {
|
|
73
78
|
let out = css;
|
|
74
|
-
for (const
|
|
75
|
-
|
|
76
|
-
let end = m.index + m[0].length - 1;
|
|
77
|
-
for (; end < css.length; end++) {
|
|
78
|
-
if (css[end] === "{") depth++;
|
|
79
|
-
else if (css[end] === "}" && --depth === 0) break;
|
|
80
|
-
}
|
|
81
|
-
out = out.slice(0, m.index) + css.slice(m.index, end + 1).replace(/[^\n]/g, " ") + out.slice(end + 1);
|
|
79
|
+
for (const { start, end } of themeBlocks(css, { inline: true })) {
|
|
80
|
+
out = out.slice(0, start) + out.slice(start, end).replace(/[^\n]/g, " ") + out.slice(end);
|
|
82
81
|
}
|
|
83
82
|
return out;
|
|
84
83
|
}
|
|
@@ -74,8 +74,6 @@ export const CHART_ROLES = {
|
|
|
74
74
|
outline: "var(--chart-ink)",
|
|
75
75
|
} as const;
|
|
76
76
|
|
|
77
|
-
/** `--chart-ink` per theme, as hex (tokens.css holds the same two). */
|
|
78
|
-
export const CHART_INK_HEX = { light: "#1a1a17", dark: "#f0efe8" } as const;
|
|
79
77
|
/** `--chart-muted` per theme, as hex (tokens.css holds the same two). */
|
|
80
78
|
export const CHART_MUTED_HEX = { light: "#6b6a63", dark: "#a8a79f" } as const;
|
|
81
79
|
|