basalt-ui 1.26.0 → 1.28.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/AGENTS.md +23 -23
- package/CHANGELOG.md +67 -0
- package/MIGRATING.md +896 -37
- package/README.md +33 -3
- package/agent/rules/basalt-batteries.md +25 -10
- package/agent/rules/basalt-controls.md +73 -43
- package/agent/rules/basalt-mantine.md +17 -6
- package/agent/rules/basalt-state.md +5 -0
- package/agent/rules/basalt-tokens.md +1 -1
- package/configs/oxlint-plugin.js +1286 -73
- package/configs/oxlint-plugin.test.ts +1338 -29
- package/configs/oxlint.json +12 -5
- package/dist/agent/coalesce.d.ts.map +1 -1
- package/dist/agent/coalesce.js +2 -1
- package/dist/agent/merge.d.ts.map +1 -1
- package/dist/agent/merge.js +2 -1
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/part-list.js +2 -0
- package/dist/agent/stick-to-bottom.d.ts +3 -6
- package/dist/agent/stick-to-bottom.d.ts.map +1 -1
- package/dist/agent/stick-to-bottom.js +6 -3
- package/dist/agent-chat/composer.d.ts +6 -2
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +49 -33
- package/dist/agent-chat/relative-time.d.ts +0 -9
- package/dist/agent-chat/relative-time.d.ts.map +1 -1
- package/dist/agent-chat/relative-time.js +2 -1
- package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
- package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
- package/dist/agent-chat/thread-detail-panel.js +8 -5
- package/dist/agent-chat/thread-feed-row.d.ts +2 -1
- package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed-row.js +92 -92
- package/dist/agent-chat/thread-feed.d.ts +3 -2
- package/dist/agent-chat/thread-feed.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed.js +8 -3
- package/dist/agent-chat/thread-message.d.ts +4 -3
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +70 -31
- package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
- package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
- package/dist/agent-chat/thread-outcome-card.js +9 -2
- package/dist/agent-chat/thread-workspace.d.ts +7 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +62 -29
- package/dist/agent-chat/tool-chip.d.ts +3 -2
- package/dist/agent-chat/tool-chip.d.ts.map +1 -1
- package/dist/agent-chat/tool-chip.js +7 -2
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
- package/dist/charts/hooks/useBandPlot.js +15 -8
- package/dist/charts/index.d.ts +8 -7
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +45 -5
- package/dist/charts/kinds/BandStrip.d.ts +10 -7
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +12 -7
- package/dist/charts/kinds/Bars.d.ts +12 -6
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +37 -11
- package/dist/charts/kinds/Donut.d.ts +6 -5
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +19 -2
- package/dist/charts/kinds/DualPanel.d.ts +6 -5
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +36 -8
- package/dist/charts/kinds/Heatmap.d.ts +10 -6
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +58 -31
- package/dist/charts/kinds/MirroredBars.d.ts +6 -5
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +9 -2
- package/dist/charts/kinds/MultiLine.d.ts +11 -6
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +24 -15
- package/dist/charts/kinds/StackedArea.d.ts +18 -8
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +29 -4
- package/dist/charts/kinds/ZonedLine.d.ts +12 -8
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +33 -23
- package/dist/charts/layout/auto-margin.d.ts +65 -8
- package/dist/charts/layout/auto-margin.d.ts.map +1 -1
- package/dist/charts/layout/auto-margin.js +45 -10
- package/dist/charts/layout/log-ticks.d.ts +27 -0
- package/dist/charts/layout/log-ticks.d.ts.map +1 -0
- package/dist/charts/layout/log-ticks.js +42 -0
- package/dist/charts/primitives/Axes.d.ts +11 -2
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +15 -5
- package/dist/charts/primitives/CartesianChart.d.ts +45 -7
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +86 -28
- package/dist/charts/primitives/ChartCard.d.ts +27 -2
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +37 -17
- package/dist/charts/primitives/ChartFrame.d.ts +30 -4
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +45 -15
- package/dist/charts/primitives/ChartLegend.d.ts +13 -2
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +33 -13
- package/dist/charts/primitives/ChartPending.d.ts +77 -4
- package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
- package/dist/charts/primitives/ChartPending.js +100 -6
- package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +18 -5
- package/dist/charts/primitives/Crosshair.d.ts +3 -0
- package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
- package/dist/charts/primitives/Crosshair.js +3 -1
- package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
- package/dist/charts/primitives/chart-frame-layout.js +89 -0
- package/dist/charts/primitives/chart-tier.d.ts +16 -0
- package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
- package/dist/charts/primitives/chart-tier.js +20 -0
- package/dist/charts/series.d.ts +55 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +27 -1
- package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
- package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/BarSparkline.js +9 -3
- package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
- package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/LineSparkline.js +15 -3
- package/dist/charts/sparklines/index.d.ts +2 -2
- package/dist/charts/sparklines/index.d.ts.map +1 -1
- package/dist/charts/utils/format.d.ts +92 -2
- package/dist/charts/utils/format.d.ts.map +1 -1
- package/dist/charts/utils/format.js +64 -1
- package/dist/charts/utils/ticks.d.ts +81 -2
- package/dist/charts/utils/ticks.d.ts.map +1 -1
- package/dist/charts/utils/ticks.js +54 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/commands/ShortcutsHelp.d.ts +3 -2
- package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
- package/dist/commands/ShortcutsHelp.js +8 -2
- package/dist/commands/define-commands.d.ts.map +1 -1
- package/dist/commands/define-commands.js +2 -2
- package/dist/commands/define-overlays.d.ts +102 -0
- package/dist/commands/define-overlays.d.ts.map +1 -1
- package/dist/commands/define-overlays.js +102 -2
- package/dist/commands/index.d.ts +10 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -1
- package/dist/commands/overlays-mount.d.ts +13 -6
- package/dist/commands/overlays-mount.d.ts.map +1 -1
- package/dist/commands/overlays-mount.js +23 -14
- package/dist/common/errors.d.ts +34 -0
- package/dist/common/errors.d.ts.map +1 -0
- package/dist/common/errors.js +36 -0
- package/dist/common/index.d.ts +15 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +29 -0
- package/dist/common/props.d.ts +63 -0
- package/dist/common/props.d.ts.map +1 -0
- package/dist/common/props.js +6 -0
- package/dist/common/query-state-like.d.ts +43 -0
- package/dist/common/query-state-like.d.ts.map +1 -0
- package/dist/common/query-state-like.js +20 -0
- package/dist/common/refs.d.ts +29 -0
- package/dist/common/refs.d.ts.map +1 -0
- package/dist/common/refs.js +29 -0
- package/dist/common/scroll-parent.d.ts +23 -0
- package/dist/common/scroll-parent.d.ts.map +1 -0
- package/dist/common/scroll-parent.js +21 -0
- package/dist/common/validate.d.ts +38 -0
- package/dist/common/validate.d.ts.map +1 -0
- package/dist/common/validate.js +36 -0
- package/dist/connectivity/connectivity-indicator.d.ts +3 -1
- package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
- package/dist/connectivity/connectivity-indicator.js +3 -1
- package/dist/connectivity/connectivity-provider.d.ts.map +1 -1
- package/dist/connectivity/connectivity-provider.js +4 -1
- package/dist/connectivity/index.d.ts +1 -0
- package/dist/connectivity/index.d.ts.map +1 -1
- package/dist/connectivity/types.d.ts +2 -2
- package/dist/connectivity/types.d.ts.map +1 -1
- package/dist/connectivity/use-connectivity.d.ts.map +1 -1
- package/dist/content/article-layout.module.css +11 -4
- package/dist/content/callout.d.ts +8 -4
- package/dist/content/callout.d.ts.map +1 -1
- package/dist/content/callout.js +15 -8
- package/dist/content/guide.d.ts +5 -4
- package/dist/content/guide.d.ts.map +1 -1
- package/dist/content/guide.js +13 -4
- package/dist/content/heading-anchor.d.ts +24 -2
- package/dist/content/heading-anchor.d.ts.map +1 -1
- package/dist/content/heading-anchor.js +23 -9
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +2 -0
- package/dist/content/prose.module.css +9 -7
- package/dist/content/reading-progress.d.ts +10 -4
- package/dist/content/reading-progress.d.ts.map +1 -1
- package/dist/content/reading-progress.js +26 -10
- package/dist/content/slug.d.ts +4 -0
- package/dist/content/slug.d.ts.map +1 -1
- package/dist/content/toc.d.ts +11 -0
- package/dist/content/toc.d.ts.map +1 -1
- package/dist/content/toc.js +23 -10
- package/dist/controls/actions.d.ts +49 -6
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/actions.js +39 -8
- package/dist/controls/compare-filter.d.ts +3 -2
- package/dist/controls/compare-filter.d.ts.map +1 -1
- package/dist/controls/compare-filter.js +17 -2
- package/dist/controls/control-group.d.ts +3 -3
- package/dist/controls/control-group.d.ts.map +1 -1
- package/dist/controls/control-group.js +9 -2
- package/dist/controls/controls.module.css +169 -186
- package/dist/controls/controls.module.css.d.ts +9 -11
- package/dist/controls/enum-filter.d.ts +13 -3
- package/dist/controls/enum-filter.d.ts.map +1 -1
- package/dist/controls/enum-filter.js +32 -15
- package/dist/controls/filter-context.d.ts +15 -6
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-pill.d.ts +3 -3
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +4 -1
- package/dist/controls/filter-set.d.ts +3 -2
- package/dist/controls/filter-set.d.ts.map +1 -1
- package/dist/controls/filter-set.js +66 -41
- package/dist/controls/filter-sheet.d.ts +20 -80
- package/dist/controls/filter-sheet.d.ts.map +1 -1
- package/dist/controls/filter-sheet.js +9 -95
- package/dist/controls/index.d.ts +3 -0
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +6 -0
- package/dist/controls/multi-select-filter.d.ts +18 -2
- package/dist/controls/multi-select-filter.d.ts.map +1 -1
- package/dist/controls/multi-select-filter.js +124 -22
- package/dist/controls/number-filter.d.ts +28 -0
- package/dist/controls/number-filter.d.ts.map +1 -0
- package/dist/controls/number-filter.js +172 -0
- package/dist/controls/panel-row.d.ts +132 -0
- package/dist/controls/panel-row.d.ts.map +1 -0
- package/dist/controls/panel-row.js +173 -0
- package/dist/controls/panel-row.module.css +86 -0
- package/dist/controls/panel-row.module.css.d.ts +13 -0
- package/dist/controls/range-filter.d.ts +20 -5
- package/dist/controls/range-filter.d.ts.map +1 -1
- package/dist/controls/range-filter.js +75 -38
- package/dist/controls/search-filter.d.ts +3 -2
- package/dist/controls/search-filter.d.ts.map +1 -1
- package/dist/controls/search-filter.js +26 -11
- package/dist/controls/select-filter.d.ts +2 -1
- package/dist/controls/select-filter.d.ts.map +1 -1
- package/dist/controls/select-filter.js +4 -0
- package/dist/controls/slider-control.d.ts +36 -0
- package/dist/controls/slider-control.d.ts.map +1 -0
- package/dist/controls/slider-control.js +64 -0
- package/dist/controls/sync-button.d.ts +3 -2
- package/dist/controls/sync-button.d.ts.map +1 -1
- package/dist/controls/sync-button.js +16 -3
- package/dist/controls/toggle-filter.d.ts +3 -2
- package/dist/controls/toggle-filter.d.ts.map +1 -1
- package/dist/controls/toggle-filter.js +29 -12
- package/dist/controls/view-tabs.d.ts +3 -2
- package/dist/controls/view-tabs.d.ts.map +1 -1
- package/dist/controls/view-tabs.js +43 -11
- package/dist/controls-dates/date-range-picker.d.ts +1 -1
- package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
- package/dist/controls-dates/date-range-picker.js +6 -1
- package/dist/dashboard/delta-badge.d.ts +5 -1
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/empty-state.d.ts +10 -3
- package/dist/dashboard/empty-state.d.ts.map +1 -1
- package/dist/dashboard/empty-state.js +37 -6
- package/dist/dashboard/index.d.ts +6 -4
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +10 -2
- package/dist/dashboard/query-state.d.ts +48 -27
- package/dist/dashboard/query-state.d.ts.map +1 -1
- package/dist/dashboard/query-state.js +111 -57
- package/dist/dashboard/settings-section.d.ts +19 -9
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +74 -25
- package/dist/dashboard/stat-card.d.ts +76 -19
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +78 -38
- package/dist/dashboard/stat-card.module.css +112 -26
- package/dist/dashboard/stat-card.module.css.d.ts +5 -0
- package/dist/dashboard/stat-group.d.ts +41 -0
- package/dist/dashboard/stat-group.d.ts.map +1 -0
- package/dist/dashboard/stat-group.js +30 -0
- package/dist/dashboard/stat-group.module.css +66 -0
- package/dist/dashboard/stat-group.module.css.d.ts +8 -0
- package/dist/dashboard/widget-grid.d.ts +27 -0
- package/dist/dashboard/widget-grid.d.ts.map +1 -0
- package/dist/dashboard/widget-grid.js +41 -0
- package/dist/dashboard/widget-grid.module.css +53 -0
- package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
- package/dist/data/data-table.d.ts +151 -12
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +392 -138
- package/dist/data/data-table.module.css +64 -0
- package/dist/data/data-table.module.css.d.ts +11 -0
- package/dist/data/index.d.ts +2 -2
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +24 -2
- package/dist/data/query-branch.d.ts +28 -0
- package/dist/data/query-branch.d.ts.map +1 -0
- package/dist/data/query-branch.js +10 -0
- package/dist/data/table.d.ts +2 -2
- package/dist/data/table.d.ts.map +1 -1
- package/dist/data/virtual-list.d.ts +65 -6
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual-list.js +89 -38
- package/dist/data/virtual.d.ts +1 -1
- package/dist/data/virtual.d.ts.map +1 -1
- package/dist/forms/FormErrorSummary.d.ts +4 -3
- package/dist/forms/FormErrorSummary.d.ts.map +1 -1
- package/dist/forms/FormErrorSummary.js +6 -4
- package/dist/forms/create-form.d.ts +2 -2
- package/dist/forms/field.d.ts +46 -6
- package/dist/forms/field.d.ts.map +1 -1
- package/dist/forms/field.js +10 -5
- package/dist/forms/form-layout.d.ts +142 -0
- package/dist/forms/form-layout.d.ts.map +1 -0
- package/dist/forms/form-layout.js +110 -0
- package/dist/forms/form-layout.module.css +145 -0
- package/dist/forms/form-layout.module.css.d.ts +20 -0
- package/dist/forms/form-state.d.ts +42 -0
- package/dist/forms/form-state.d.ts.map +1 -0
- package/dist/forms/form-state.js +26 -0
- package/dist/forms/index.d.ts +22 -6
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +19 -3
- package/dist/forms/use-field-array.d.ts +60 -0
- package/dist/forms/use-field-array.d.ts.map +1 -0
- package/dist/forms/use-field-array.js +20 -0
- package/dist/forms/use-form-draft.d.ts +25 -10
- package/dist/forms/use-form-draft.d.ts.map +1 -1
- package/dist/forms/use-form-draft.js +55 -18
- package/dist/forms/use-form-submit.d.ts +88 -0
- package/dist/forms/use-form-submit.d.ts.map +1 -0
- package/dist/forms/use-form-submit.js +63 -0
- package/dist/guard/index.d.ts +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +27 -11
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -4
- package/dist/nav/types.d.ts +15 -6
- package/dist/nav/types.d.ts.map +1 -1
- package/dist/notifications/bell.d.ts +3 -2
- package/dist/notifications/bell.d.ts.map +1 -1
- package/dist/notifications/bell.js +73 -56
- package/dist/notifications/center.d.ts +15 -4
- package/dist/notifications/center.d.ts.map +1 -1
- package/dist/notifications/center.js +5 -2
- package/dist/notifications/define-notifications.d.ts +56 -14
- package/dist/notifications/define-notifications.d.ts.map +1 -1
- package/dist/notifications/define-notifications.js +2 -1
- package/dist/notifications/index.d.ts +6 -2
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +5 -1
- package/dist/notifications/mount-guard.d.ts +8 -0
- package/dist/notifications/mount-guard.d.ts.map +1 -0
- package/dist/notifications/mount-guard.js +19 -0
- package/dist/notifications/notify.d.ts +58 -1
- package/dist/notifications/notify.d.ts.map +1 -1
- package/dist/notifications/notify.js +46 -2
- package/dist/notifications/overlay.d.ts.map +1 -1
- package/dist/notifications/overlay.js +2 -0
- package/dist/notifications/store.d.ts +7 -0
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/provider/index.d.ts +37 -5
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +66 -28
- package/dist/provider/lab-theme.d.ts.map +1 -1
- package/dist/provider/lab-theme.js +2 -1
- package/dist/query/devtools.d.ts.map +1 -1
- package/dist/query/devtools.js +1 -0
- package/dist/router-tanstack/field.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts.map +1 -1
- package/dist/router-tanstack/index.d.ts +1 -1
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.d.ts +4 -1
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.js +92 -8
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +1 -1
- package/dist/section/index.d.ts +1 -1
- package/dist/section/index.d.ts.map +1 -1
- package/dist/section/section.d.ts +36 -2
- package/dist/section/section.d.ts.map +1 -1
- package/dist/section/section.js +49 -57
- package/dist/section/section.module.css +11 -0
- package/dist/shell/account-types.d.ts +5 -2
- package/dist/shell/account-types.d.ts.map +1 -1
- package/dist/shell/app-brand.d.ts +13 -0
- package/dist/shell/app-brand.d.ts.map +1 -0
- package/dist/shell/app-brand.js +94 -0
- package/dist/shell/app-brand.module.css +109 -0
- package/dist/shell/app-brand.module.css.d.ts +14 -0
- package/dist/shell/app-breadcrumbs.d.ts +4 -2
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +33 -22
- package/dist/shell/app-header.module.css +10 -5
- package/dist/shell/app-main.module.css +107 -0
- package/dist/shell/app-main.module.css.d.ts +11 -0
- package/dist/shell/app-mobile-nav.d.ts +3 -2
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +29 -15
- package/dist/shell/app-mobile-nav.module.css +48 -27
- package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
- package/dist/shell/app-sidebar-account.d.ts +2 -1
- package/dist/shell/app-sidebar-account.d.ts.map +1 -1
- package/dist/shell/app-sidebar-account.js +57 -22
- package/dist/shell/app-sidebar.d.ts +9 -3
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +55 -109
- package/dist/shell/app-sidebar.module.css +53 -96
- package/dist/shell/app-sidebar.module.css.d.ts +0 -5
- package/dist/shell/index.d.ts +14 -6
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +47 -12
- package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
- package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/shell/isomorphic-layout-effect.js +5 -0
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +27 -6
- package/dist/shell/nav-count-badge.d.ts +5 -3
- package/dist/shell/nav-count-badge.d.ts.map +1 -1
- package/dist/shell/nav-count-badge.js +3 -1
- package/dist/shell/page-aside.d.ts +43 -0
- package/dist/shell/page-aside.d.ts.map +1 -0
- package/dist/shell/page-aside.js +202 -0
- package/dist/shell/page-aside.module.css +174 -0
- package/dist/shell/page-aside.module.css.d.ts +14 -0
- package/dist/shell/page-bar.d.ts +55 -15
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +130 -41
- package/dist/shell/page-bar.module.css +107 -13
- package/dist/shell/page-bar.module.css.d.ts +4 -1
- package/dist/shell/sidebar-search.d.ts +4 -2
- package/dist/shell/sidebar-search.d.ts.map +1 -1
- package/dist/shell/sidebar-search.js +9 -4
- package/dist/shell/sidebar-search.module.css +1 -1
- package/dist/state/fields.d.ts +227 -34
- package/dist/state/fields.d.ts.map +1 -1
- package/dist/state/fields.js +77 -30
- package/dist/state/persisted-or-local.d.ts +9 -0
- package/dist/state/persisted-or-local.d.ts.map +1 -0
- package/dist/state/persisted-or-local.js +24 -0
- package/dist/state/persisted.d.ts +12 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +36 -18
- package/dist/state.d.ts +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +2 -6
- package/dist/surfaces.d.ts +12 -11
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +43 -12
- package/dist/theme/ctl-theme.d.ts.map +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +102 -17
- package/dist/theme/nav-link.module.css +37 -26
- package/dist/theme/segmented-control.module.css +22 -0
- package/dist/theme/segmented-control.module.css.d.ts +1 -0
- package/dist/theme-lab/boot.d.ts +66 -0
- package/dist/theme-lab/boot.d.ts.map +1 -0
- package/dist/theme-lab/boot.js +82 -0
- package/dist/theme-lab/index.d.ts +4 -43
- package/dist/theme-lab/index.d.ts.map +1 -1
- package/dist/theme-lab/index.js +9 -69
- package/dist/theme-toggle/index.d.ts +3 -2
- package/dist/theme-toggle/index.d.ts.map +1 -1
- package/dist/theme-toggle/index.js +8 -2
- package/dist/tokens/index.d.ts +42 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +35 -6
- package/dist/tokens/palette.d.ts +45 -25
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +31 -14
- package/dist/tokens.css +4 -5
- package/dist/utils/is-dev.d.ts +3 -0
- package/dist/utils/is-dev.d.ts.map +1 -0
- package/dist/utils/is-dev.js +6 -0
- package/dist/widget-header/delta-badge.d.ts +7 -1
- package/dist/widget-header/delta-badge.d.ts.map +1 -1
- package/dist/widget-header/delta-badge.js +8 -1
- package/dist/widget-header/index.d.ts +2 -2
- package/dist/widget-header/index.d.ts.map +1 -1
- package/dist/widget-header/index.js +3 -1
- package/dist/widget-header/widget-header.d.ts +99 -4
- package/dist/widget-header/widget-header.d.ts.map +1 -1
- package/dist/widget-header/widget-header.js +45 -18
- package/dist/widget-header/widget-header.module.css +87 -7
- package/dist/widget-header/widget-header.module.css.d.ts +1 -0
- package/llms.txt +6 -6
- package/package.json +49 -25
- package/src/agent/coalesce.ts +2 -1
- package/src/agent/merge.ts +2 -1
- package/src/agent/part-list.tsx +2 -0
- package/src/agent/stick-to-bottom.tsx +9 -11
- package/src/agent-chat/composer.tsx +124 -103
- package/src/agent-chat/relative-time.ts +2 -1
- package/src/agent-chat/thread-detail-panel.tsx +9 -5
- package/src/agent-chat/thread-feed-row.tsx +17 -9
- package/src/agent-chat/thread-feed.tsx +9 -3
- package/src/agent-chat/thread-message.tsx +48 -13
- package/src/agent-chat/thread-outcome-card.tsx +9 -1
- package/src/agent-chat/thread-workspace.tsx +72 -33
- package/src/agent-chat/tool-chip.tsx +8 -2
- package/src/charts/hooks/useBandPlot.ts +26 -8
- package/src/charts/index.ts +39 -4
- package/src/charts/kinds/BandStrip.tsx +36 -14
- package/src/charts/kinds/Bars.tsx +67 -16
- package/src/charts/kinds/Donut.tsx +30 -3
- package/src/charts/kinds/DualPanel.tsx +56 -10
- package/src/charts/kinds/Heatmap.tsx +90 -33
- package/src/charts/kinds/MirroredBars.tsx +21 -3
- package/src/charts/kinds/MultiLine.tsx +61 -18
- package/src/charts/kinds/StackedArea.tsx +77 -6
- package/src/charts/kinds/ZonedLine.tsx +71 -31
- package/src/charts/layout/auto-margin.ts +134 -21
- package/src/charts/layout/log-ticks.ts +90 -0
- package/src/charts/primitives/Axes.tsx +33 -5
- package/src/charts/primitives/CartesianChart.tsx +194 -41
- package/src/charts/primitives/ChartCard.tsx +66 -25
- package/src/charts/primitives/ChartFrame.tsx +107 -26
- package/src/charts/primitives/ChartLegend.tsx +43 -13
- package/src/charts/primitives/ChartPending.tsx +185 -7
- package/src/charts/primitives/ChartTooltip.tsx +37 -5
- package/src/charts/primitives/Crosshair.tsx +6 -3
- package/src/charts/primitives/chart-frame-layout.ts +212 -0
- package/src/charts/primitives/chart-tier.tsx +37 -0
- package/src/charts/series.ts +77 -1
- package/src/charts/sparklines/BarSparkline.tsx +11 -3
- package/src/charts/sparklines/LineSparkline.tsx +18 -4
- package/src/charts/sparklines/index.ts +2 -2
- package/src/charts/utils/format.ts +163 -2
- package/src/charts/utils/ticks.ts +149 -4
- package/src/cli/index.ts +8 -3
- package/src/commands/ShortcutsHelp.tsx +10 -3
- package/src/commands/define-commands.ts +2 -2
- package/src/commands/define-overlays.ts +216 -1
- package/src/commands/index.ts +12 -0
- package/src/commands/overlays-mount.tsx +60 -26
- package/src/common/errors.ts +73 -0
- package/src/common/index.ts +21 -0
- package/src/common/props.ts +71 -0
- package/src/common/query-state-like.ts +64 -0
- package/src/common/refs.ts +58 -0
- package/src/common/scroll-parent.ts +42 -0
- package/src/common/validate.ts +94 -0
- package/src/connectivity/connectivity-indicator.tsx +6 -1
- package/src/connectivity/connectivity-provider.tsx +4 -1
- package/src/connectivity/index.ts +1 -0
- package/src/connectivity/types.ts +5 -2
- package/src/connectivity/use-connectivity.ts +3 -2
- package/src/content/article-layout.module.css +11 -4
- package/src/content/callout.tsx +13 -7
- package/src/content/guide.tsx +14 -4
- package/src/content/heading-anchor.tsx +14 -3
- package/src/content/index.ts +2 -0
- package/src/content/markdown.tsx +2 -0
- package/src/content/prose.module.css +9 -7
- package/src/content/reading-progress.tsx +40 -15
- package/src/content/slug.ts +4 -0
- package/src/content/toc.tsx +47 -10
- package/src/controls/actions.tsx +89 -9
- package/src/controls/compare-filter.tsx +20 -3
- package/src/controls/control-group.tsx +11 -4
- package/src/controls/controls.module.css +169 -186
- package/src/controls/controls.module.css.d.ts +9 -11
- package/src/controls/enum-filter.tsx +46 -19
- package/src/controls/filter-context.tsx +21 -8
- package/src/controls/filter-pill.tsx +6 -3
- package/src/controls/filter-set.tsx +32 -13
- package/src/controls/filter-sheet.tsx +44 -194
- package/src/controls/index.ts +8 -0
- package/src/controls/multi-select-filter.tsx +179 -29
- package/src/controls/number-filter.tsx +329 -0
- package/src/controls/panel-row.module.css +86 -0
- package/src/controls/panel-row.module.css.d.ts +13 -0
- package/src/controls/panel-row.tsx +392 -0
- package/src/controls/range-filter.tsx +133 -43
- package/src/controls/search-filter.tsx +31 -13
- package/src/controls/select-filter.tsx +11 -1
- package/src/controls/slider-control.tsx +159 -0
- package/src/controls/sync-button.tsx +13 -3
- package/src/controls/toggle-filter.tsx +37 -17
- package/src/controls/view-tabs.tsx +63 -11
- package/src/controls-dates/date-range-picker.tsx +8 -1
- package/src/dashboard/delta-badge.tsx +5 -1
- package/src/dashboard/empty-state.tsx +72 -25
- package/src/dashboard/index.ts +19 -2
- package/src/dashboard/query-state.tsx +154 -84
- package/src/dashboard/settings-section.tsx +87 -31
- package/src/dashboard/stat-card.module.css +112 -26
- package/src/dashboard/stat-card.module.css.d.ts +5 -0
- package/src/dashboard/stat-card.tsx +207 -68
- package/src/dashboard/stat-group.module.css +66 -0
- package/src/dashboard/stat-group.module.css.d.ts +8 -0
- package/src/dashboard/stat-group.tsx +74 -0
- package/src/dashboard/widget-grid.module.css +53 -0
- package/src/dashboard/widget-grid.module.css.d.ts +9 -0
- package/src/dashboard/widget-grid.tsx +127 -0
- package/src/data/data-table.module.css +64 -0
- package/src/data/data-table.module.css.d.ts +11 -0
- package/src/data/data-table.tsx +927 -346
- package/src/data/index.ts +30 -2
- package/src/data/query-branch.ts +53 -0
- package/src/data/table.ts +5 -0
- package/src/data/virtual-list.tsx +189 -55
- package/src/data/virtual.ts +5 -1
- package/src/forms/FormErrorSummary.tsx +13 -6
- package/src/forms/create-form.ts +2 -2
- package/src/forms/field.ts +60 -11
- package/src/forms/form-layout.module.css +145 -0
- package/src/forms/form-layout.module.css.d.ts +20 -0
- package/src/forms/form-layout.tsx +305 -0
- package/src/forms/form-state.tsx +94 -0
- package/src/forms/index.ts +38 -7
- package/src/forms/use-field-array.ts +94 -0
- package/src/forms/use-form-draft.ts +145 -53
- package/src/forms/use-form-submit.ts +199 -0
- package/src/guard/index.ts +91 -23
- package/src/index.ts +79 -10
- package/src/nav/types.ts +15 -6
- package/src/notifications/bell.tsx +18 -4
- package/src/notifications/center.tsx +18 -5
- package/src/notifications/define-notifications.ts +64 -18
- package/src/notifications/index.ts +12 -1
- package/src/notifications/mount-guard.ts +41 -0
- package/src/notifications/notify.tsx +122 -3
- package/src/notifications/overlay.tsx +2 -0
- package/src/notifications/store.ts +7 -0
- package/src/provider/index.tsx +101 -13
- package/src/provider/lab-theme.ts +2 -1
- package/src/query/devtools.tsx +1 -0
- package/src/router-tanstack/field.ts +5 -0
- package/src/router-tanstack/index.ts +4 -0
- package/src/router-tanstack/search-store.ts +171 -12
- package/src/router-tanstack/use-nav.ts +18 -13
- package/src/section/index.ts +1 -1
- package/src/section/section.module.css +11 -0
- package/src/section/section.tsx +163 -96
- package/src/shell/account-types.ts +5 -2
- package/src/shell/app-brand.module.css +109 -0
- package/src/shell/app-brand.module.css.d.ts +14 -0
- package/src/shell/app-brand.tsx +140 -0
- package/src/shell/app-breadcrumbs.tsx +19 -9
- package/src/shell/app-header.module.css +10 -5
- package/src/shell/app-main.module.css +107 -0
- package/src/shell/app-main.module.css.d.ts +11 -0
- package/src/shell/app-mobile-nav.module.css +48 -27
- package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
- package/src/shell/app-mobile-nav.tsx +43 -11
- package/src/shell/app-sidebar-account.tsx +29 -6
- package/src/shell/app-sidebar.module.css +53 -96
- package/src/shell/app-sidebar.module.css.d.ts +0 -5
- package/src/shell/app-sidebar.tsx +40 -113
- package/src/shell/index.tsx +156 -65
- package/src/shell/isomorphic-layout-effect.ts +14 -0
- package/src/shell/mobile-nav-model.ts +66 -10
- package/src/shell/nav-count-badge.tsx +6 -1
- package/src/shell/page-aside.module.css +174 -0
- package/src/shell/page-aside.module.css.d.ts +14 -0
- package/src/shell/page-aside.tsx +377 -0
- package/src/shell/page-bar.module.css +107 -13
- package/src/shell/page-bar.module.css.d.ts +4 -1
- package/src/shell/page-bar.tsx +294 -94
- package/src/shell/sidebar-search.module.css +1 -1
- package/src/shell/sidebar-search.tsx +13 -4
- package/src/state/fields.ts +360 -69
- package/src/state/persisted-or-local.ts +50 -0
- package/src/state/persisted.ts +76 -26
- package/src/state.ts +4 -7
- package/src/surfaces.ts +43 -12
- package/src/theme/ctl-theme.tsx +10 -0
- package/src/theme/index.ts +129 -16
- package/src/theme/nav-link.module.css +37 -26
- package/src/theme/segmented-control.module.css +22 -0
- package/src/theme/segmented-control.module.css.d.ts +1 -0
- package/src/theme-lab/boot.ts +140 -0
- package/src/theme-lab/index.tsx +21 -112
- package/src/theme-toggle/index.tsx +10 -3
- package/src/tokens/index.ts +50 -6
- package/src/tokens/palette.ts +58 -30
- package/src/utils/is-dev.ts +4 -0
- package/src/widget-header/delta-badge.tsx +25 -5
- package/src/widget-header/index.ts +10 -2
- package/src/widget-header/widget-header.module.css +87 -7
- package/src/widget-header/widget-header.module.css.d.ts +1 -0
- package/src/widget-header/widget-header.tsx +166 -42
- package/dist/agent/adapter.test.d.ts +0 -2
- package/dist/agent/adapter.test.d.ts.map +0 -1
- package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
- package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
- package/dist/agent/coalesce.test.d.ts +0 -2
- package/dist/agent/coalesce.test.d.ts.map +0 -1
- package/dist/agent/foreign.test.d.ts +0 -2
- package/dist/agent/foreign.test.d.ts.map +0 -1
- package/dist/agent/id.test.d.ts +0 -2
- package/dist/agent/id.test.d.ts.map +0 -1
- package/dist/agent/merge.test.d.ts +0 -2
- package/dist/agent/merge.test.d.ts.map +0 -1
- package/dist/agent/part-list.test.d.ts +0 -2
- package/dist/agent/part-list.test.d.ts.map +0 -1
- package/dist/agent/parts.test.d.ts +0 -2
- package/dist/agent/parts.test.d.ts.map +0 -1
- package/dist/agent/thread.test.d.ts +0 -2
- package/dist/agent/thread.test.d.ts.map +0 -1
- package/dist/agent/transport.test.d.ts +0 -2
- package/dist/agent/transport.test.d.ts.map +0 -1
- package/dist/agent/use-agent-stream.test.d.ts +0 -2
- package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
- package/dist/agent-chat/composer.test.d.ts +0 -2
- package/dist/agent-chat/composer.test.d.ts.map +0 -1
- package/dist/agent-chat/relative-time.test.d.ts +0 -2
- package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
- package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-feed.test.d.ts +0 -2
- package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-message.test.d.ts +0 -2
- package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
- package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
- package/dist/agent-chat/tool-chip.test.d.ts +0 -2
- package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
- package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
- package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
- package/dist/charts/cursor/resolve.test.d.ts +0 -2
- package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
- package/dist/charts/cursor/store.test.d.ts +0 -2
- package/dist/charts/cursor/store.test.d.ts.map +0 -1
- package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
- package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
- package/dist/charts/kinds/Bars.test.d.ts +0 -2
- package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
- package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
- package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
- package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
- package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
- package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
- package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
- package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
- package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
- package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
- package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
- package/dist/charts/layout/auto-margin.test.d.ts +0 -2
- package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
- package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
- package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
- package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
- package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
- package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
- package/dist/charts/series.test.d.ts +0 -2
- package/dist/charts/series.test.d.ts.map +0 -1
- package/dist/charts/utils/domain.test.d.ts +0 -2
- package/dist/charts/utils/domain.test.d.ts.map +0 -1
- package/dist/charts/utils/measure-text.test.d.ts +0 -2
- package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
- package/dist/cli/check-coverage.test.d.ts +0 -2
- package/dist/cli/check-coverage.test.d.ts.map +0 -1
- package/dist/cli/check-theme.test.d.ts +0 -2
- package/dist/cli/check-theme.test.d.ts.map +0 -1
- package/dist/cli/help.test.d.ts +0 -2
- package/dist/cli/help.test.d.ts.map +0 -1
- package/dist/cli/init-sync.test.d.ts +0 -2
- package/dist/cli/init-sync.test.d.ts.map +0 -1
- package/dist/cli/placement-engine.test.d.ts +0 -2
- package/dist/cli/placement-engine.test.d.ts.map +0 -1
- package/dist/cli/shipped-versions.test.d.ts +0 -2
- package/dist/cli/shipped-versions.test.d.ts.map +0 -1
- package/dist/cli/tarball-parity.test.d.ts +0 -2
- package/dist/cli/tarball-parity.test.d.ts.map +0 -1
- package/dist/cli/template-format.test.d.ts +0 -2
- package/dist/cli/template-format.test.d.ts.map +0 -1
- package/dist/cli/template-ledger.test.d.ts +0 -2
- package/dist/cli/template-ledger.test.d.ts.map +0 -1
- package/dist/cli/tokens-css.test.d.ts +0 -2
- package/dist/cli/tokens-css.test.d.ts.map +0 -1
- package/dist/cli/toolchain-wiring.test.d.ts +0 -2
- package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
- package/dist/cli/waiver-audit.test.d.ts +0 -2
- package/dist/cli/waiver-audit.test.d.ts.map +0 -1
- package/dist/content/article-actions.test.d.ts +0 -2
- package/dist/content/article-actions.test.d.ts.map +0 -1
- package/dist/content/article-model.test.d.ts +0 -2
- package/dist/content/article-model.test.d.ts.map +0 -1
- package/dist/content/block-split.test.d.ts +0 -2
- package/dist/content/block-split.test.d.ts.map +0 -1
- package/dist/content/heading-components.test.d.ts +0 -2
- package/dist/content/heading-components.test.d.ts.map +0 -1
- package/dist/content/markdown.test.d.ts +0 -2
- package/dist/content/markdown.test.d.ts.map +0 -1
- package/dist/content/prose.module.css.test.d.ts +0 -2
- package/dist/content/prose.module.css.test.d.ts.map +0 -1
- package/dist/content/sanitize.test.d.ts +0 -2
- package/dist/content/sanitize.test.d.ts.map +0 -1
- package/dist/content/slug.test.d.ts +0 -2
- package/dist/content/slug.test.d.ts.map +0 -1
- package/dist/controls/actions.test.d.ts +0 -2
- package/dist/controls/actions.test.d.ts.map +0 -1
- package/dist/controls/actions.type-guard.test.d.ts +0 -2
- package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
- package/dist/controls/control-group.test.d.ts +0 -2
- package/dist/controls/control-group.test.d.ts.map +0 -1
- package/dist/controls/controls.router.test.d.ts +0 -2
- package/dist/controls/controls.router.test.d.ts.map +0 -1
- package/dist/controls/filter-set.test.d.ts +0 -2
- package/dist/controls/filter-set.test.d.ts.map +0 -1
- package/dist/controls/peer-boundary.test.d.ts +0 -2
- package/dist/controls/peer-boundary.test.d.ts.map +0 -1
- package/dist/controls/props-contract.test.d.ts +0 -2
- package/dist/controls/props-contract.test.d.ts.map +0 -1
- package/dist/controls/sync-button.test.d.ts +0 -2
- package/dist/controls/sync-button.test.d.ts.map +0 -1
- package/dist/dashboard/query-state.test.d.ts +0 -2
- package/dist/dashboard/query-state.test.d.ts.map +0 -1
- package/dist/dashboard/settings-section.test.d.ts +0 -2
- package/dist/dashboard/settings-section.test.d.ts.map +0 -1
- package/dist/dashboard/stat-card.test.d.ts +0 -2
- package/dist/dashboard/stat-card.test.d.ts.map +0 -1
- package/dist/data/data-table.test.d.ts +0 -2
- package/dist/data/data-table.test.d.ts.map +0 -1
- package/dist/guard/check-source.test.d.ts +0 -2
- package/dist/guard/check-source.test.d.ts.map +0 -1
- package/dist/guard/grace.test.d.ts +0 -20
- package/dist/guard/grace.test.d.ts.map +0 -1
- package/dist/guard/guard-hook.test.d.ts +0 -2
- package/dist/guard/guard-hook.test.d.ts.map +0 -1
- package/dist/provider/index.test.d.ts +0 -2
- package/dist/provider/index.test.d.ts.map +0 -1
- package/dist/provider/lab-theme.test.d.ts +0 -2
- package/dist/provider/lab-theme.test.d.ts.map +0 -1
- package/dist/query/error-message.test.d.ts +0 -2
- package/dist/query/error-message.test.d.ts.map +0 -1
- package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
- package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/nav.test.d.ts +0 -2
- package/dist/router-tanstack/nav.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
- package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
- package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-store.test.d.ts +0 -2
- package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/use-nav.test.d.ts +0 -2
- package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
- package/dist/section/section.test.d.ts +0 -2
- package/dist/section/section.test.d.ts.map +0 -1
- package/dist/shell/app-mobile-nav.test.d.ts +0 -2
- package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
- package/dist/shell/app-sidebar.test.d.ts +0 -2
- package/dist/shell/app-sidebar.test.d.ts.map +0 -1
- package/dist/shell/index.test.d.ts +0 -2
- package/dist/shell/index.test.d.ts.map +0 -1
- package/dist/shell/mobile-nav-model.test.d.ts +0 -2
- package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
- package/dist/shell/page-bar.test.d.ts +0 -2
- package/dist/shell/page-bar.test.d.ts.map +0 -1
- package/dist/state/fields.test.d.ts +0 -2
- package/dist/state/fields.test.d.ts.map +0 -1
- package/dist/styles.floor.test.d.ts +0 -2
- package/dist/styles.floor.test.d.ts.map +0 -1
- package/dist/theme/border-coverage.test.d.ts +0 -18
- package/dist/theme/border-coverage.test.d.ts.map +0 -1
- package/dist/theme/contrast.test.d.ts +0 -2
- package/dist/theme/contrast.test.d.ts.map +0 -1
- package/dist/theme/ctl-theme.test.d.ts +0 -2
- package/dist/theme/ctl-theme.test.d.ts.map +0 -1
- package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
- package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
- package/dist/theme/density-option.test.d.ts +0 -2
- package/dist/theme/density-option.test.d.ts.map +0 -1
- package/dist/theme/density-relations.test.d.ts +0 -2
- package/dist/theme/density-relations.test.d.ts.map +0 -1
- package/dist/theme/derive-theme.test.d.ts +0 -2
- package/dist/theme/derive-theme.test.d.ts.map +0 -1
- package/dist/theme/divider-law.test.d.ts +0 -2
- package/dist/theme/divider-law.test.d.ts.map +0 -1
- package/dist/theme/fonts-option.test.d.ts +0 -2
- package/dist/theme/fonts-option.test.d.ts.map +0 -1
- package/dist/theme/icon-slot.test.d.ts +0 -2
- package/dist/theme/icon-slot.test.d.ts.map +0 -1
- package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
- package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
- package/dist/theme/radius-option.test.d.ts +0 -2
- package/dist/theme/radius-option.test.d.ts.map +0 -1
- package/dist/theme/radius.test.d.ts +0 -2
- package/dist/theme/radius.test.d.ts.map +0 -1
- package/dist/theme/shadow-surfaces.test.d.ts +0 -26
- package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
- package/dist/theme/spacing.test.d.ts +0 -2
- package/dist/theme/spacing.test.d.ts.map +0 -1
- package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
- package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
- package/dist/theme-lab/derive-controls.test.d.ts +0 -2
- package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
- package/dist/theme-lab/derive-state.test.d.ts +0 -2
- package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
- package/dist/theme-lab/theme-lab.test.d.ts +0 -2
- package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
- package/dist/tokens/build-fonts-css.test.d.ts +0 -2
- package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
- package/dist/tokens/build-radius-css.test.d.ts +0 -2
- package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
- package/dist/tokens/chart-margin.test.d.ts +0 -2
- package/dist/tokens/chart-margin.test.d.ts.map +0 -1
- package/dist/tokens/density.test.d.ts +0 -2
- package/dist/tokens/density.test.d.ts.map +0 -1
- package/dist/tokens/derive-radius.test.d.ts +0 -2
- package/dist/tokens/derive-radius.test.d.ts.map +0 -1
- package/dist/tokens/derive.test.d.ts +0 -2
- package/dist/tokens/derive.test.d.ts.map +0 -1
- package/dist/tokens/palette.test.d.ts +0 -2
- package/dist/tokens/palette.test.d.ts.map +0 -1
- package/dist/tokens/text-scale.test.d.ts +0 -2
- package/dist/tokens/text-scale.test.d.ts.map +0 -1
- package/dist/vite-color-mix.test.d.ts +0 -2
- package/dist/vite-color-mix.test.d.ts.map +0 -1
- package/dist/vite.test.d.ts +0 -2
- package/dist/vite.test.d.ts.map +0 -1
- package/dist/widget-header/delta-badge.test.d.ts +0 -2
- package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
- package/dist/widget-header/no-mantine.test.d.ts +0 -2
- package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
- package/dist/widget-header/widget-header.test.d.ts +0 -2
- package/dist/widget-header/widget-header.test.d.ts.map +0 -1
- package/src/agent/adapter.test.ts +0 -1640
- package/src/agent/ai-sdk-transport.test.ts +0 -658
- package/src/agent/coalesce.test.ts +0 -387
- package/src/agent/foreign.test.ts +0 -51
- package/src/agent/id.test.ts +0 -169
- package/src/agent/merge.test.ts +0 -206
- package/src/agent/part-list.test.tsx +0 -112
- package/src/agent/parts.test.ts +0 -720
- package/src/agent/thread.test.ts +0 -220
- package/src/agent/transport.test.ts +0 -70
- package/src/agent/use-agent-stream.test.tsx +0 -287
- package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
- package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
- package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
- package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
- package/src/agent-chat/composer.test.tsx +0 -924
- package/src/agent-chat/relative-time.test.ts +0 -95
- package/src/agent-chat/thread-feed-row.test.tsx +0 -510
- package/src/agent-chat/thread-feed.test.tsx +0 -241
- package/src/agent-chat/thread-message.test.tsx +0 -1285
- package/src/agent-chat/thread-workspace.test.tsx +0 -169
- package/src/agent-chat/tool-chip.test.tsx +0 -139
- package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
- package/src/charts/cursor/resolve.test.ts +0 -151
- package/src/charts/cursor/store.test.ts +0 -64
- package/src/charts/kinds/BandStrip.test.tsx +0 -282
- package/src/charts/kinds/Bars.test.tsx +0 -183
- package/src/charts/kinds/DualPanel.test.tsx +0 -527
- package/src/charts/kinds/MirroredBars.test.tsx +0 -232
- package/src/charts/kinds/MultiLine.test.tsx +0 -218
- package/src/charts/kinds/StackedArea.test.tsx +0 -114
- package/src/charts/kinds/ZonedLine.test.tsx +0 -147
- package/src/charts/layout/auto-margin.test.ts +0 -42
- package/src/charts/primitives/CartesianChart.test.tsx +0 -647
- package/src/charts/primitives/ChartCard.test.tsx +0 -66
- package/src/charts/primitives/ChartFrame.test.tsx +0 -142
- package/src/charts/primitives/ChartLegend.test.tsx +0 -78
- package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
- package/src/charts/primitives/XZoneRects.test.tsx +0 -123
- package/src/charts/series.test.ts +0 -112
- package/src/charts/utils/domain.test.ts +0 -54
- package/src/charts/utils/measure-text.test.ts +0 -36
- package/src/cli/check-coverage.test.ts +0 -297
- package/src/cli/check-theme.test.ts +0 -837
- package/src/cli/help.test.ts +0 -170
- package/src/cli/init-sync.test.ts +0 -343
- package/src/cli/placement-engine.test.ts +0 -298
- package/src/cli/shipped-versions.test.ts +0 -83
- package/src/cli/tarball-parity.test.ts +0 -44
- package/src/cli/template-format.test.ts +0 -64
- package/src/cli/template-ledger.test.ts +0 -219
- package/src/cli/tokens-css.test.ts +0 -249
- package/src/cli/toolchain-wiring.test.ts +0 -1358
- package/src/cli/waiver-audit.test.ts +0 -215
- package/src/content/article-actions.test.ts +0 -78
- package/src/content/article-model.test.ts +0 -119
- package/src/content/block-split.test.ts +0 -75
- package/src/content/heading-components.test.tsx +0 -65
- package/src/content/markdown.test.tsx +0 -603
- package/src/content/prose.module.css.test.ts +0 -39
- package/src/content/sanitize.test.ts +0 -659
- package/src/content/slug.test.ts +0 -62
- package/src/controls/actions.test.tsx +0 -252
- package/src/controls/actions.type-guard.test.ts +0 -63
- package/src/controls/control-group.test.tsx +0 -229
- package/src/controls/controls.router.test.tsx +0 -637
- package/src/controls/filter-set.test.tsx +0 -472
- package/src/controls/peer-boundary.test.ts +0 -85
- package/src/controls/props-contract.test.tsx +0 -106
- package/src/controls/sync-button.test.tsx +0 -183
- package/src/dashboard/query-state.test.tsx +0 -152
- package/src/dashboard/settings-section.test.tsx +0 -56
- package/src/dashboard/stat-card.test.tsx +0 -154
- package/src/data/data-table.test.tsx +0 -434
- package/src/guard/check-source.test.ts +0 -3355
- package/src/guard/grace.test.ts +0 -149
- package/src/guard/guard-hook.test.ts +0 -203
- package/src/provider/index.test.tsx +0 -101
- package/src/provider/lab-theme.test.ts +0 -147
- package/src/query/error-message.test.ts +0 -67
- package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
- package/src/router-tanstack/nav.test.ts +0 -111
- package/src/router-tanstack/search-param-store.test.ts +0 -135
- package/src/router-tanstack/search-store.router.test.tsx +0 -661
- package/src/router-tanstack/search-store.test.ts +0 -418
- package/src/router-tanstack/use-nav.test.tsx +0 -187
- package/src/section/section.test.tsx +0 -204
- package/src/shell/app-mobile-nav.test.tsx +0 -403
- package/src/shell/app-sidebar.test.tsx +0 -509
- package/src/shell/index.test.tsx +0 -428
- package/src/shell/mobile-nav-model.test.ts +0 -517
- package/src/shell/page-bar.test.tsx +0 -316
- package/src/state/fields.test.tsx +0 -258
- package/src/styles.floor.test.ts +0 -36
- package/src/theme/border-coverage.test.ts +0 -272
- package/src/theme/contrast.test.ts +0 -354
- package/src/theme/ctl-theme.test.tsx +0 -275
- package/src/theme/ctl-tier-coverage.test.ts +0 -170
- package/src/theme/density-option.test.ts +0 -96
- package/src/theme/density-relations.test.ts +0 -506
- package/src/theme/derive-theme.test.ts +0 -101
- package/src/theme/divider-law.test.ts +0 -259
- package/src/theme/fonts-option.test.ts +0 -75
- package/src/theme/icon-slot.test.tsx +0 -275
- package/src/theme/layout-rhythm-css.test.ts +0 -72
- package/src/theme/radius-option.test.ts +0 -86
- package/src/theme/radius.test.ts +0 -142
- package/src/theme/shadow-surfaces.test.ts +0 -488
- package/src/theme/spacing.test.ts +0 -495
- package/src/theme/use-basalt-spacing.test.tsx +0 -55
- package/src/theme-lab/derive-controls.test.ts +0 -94
- package/src/theme-lab/derive-state.test.ts +0 -71
- package/src/theme-lab/theme-lab.test.ts +0 -46
- package/src/tokens/build-fonts-css.test.ts +0 -52
- package/src/tokens/build-radius-css.test.ts +0 -25
- package/src/tokens/chart-margin.test.ts +0 -39
- package/src/tokens/density.test.ts +0 -259
- package/src/tokens/derive-radius.test.ts +0 -62
- package/src/tokens/derive.test.ts +0 -30
- package/src/tokens/palette.test.ts +0 -114
- package/src/tokens/text-scale.test.ts +0 -51
- package/src/vite-color-mix.test.ts +0 -46
- package/src/vite.test.ts +0 -423
- package/src/widget-header/delta-badge.test.tsx +0 -60
- package/src/widget-header/no-mantine.test.ts +0 -33
- package/src/widget-header/widget-header.test.tsx +0 -187
|
@@ -1,924 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composer — the slot-bearing prop set from AGENT-CHAT-SPEC.md §8: the `{ text, attachments }`
|
|
3
|
-
* submit payload, the clear-only-on-success draft contract, the keyboard contract (including the
|
|
4
|
-
* IME guard), the streaming/stop gate, and `draftKey` persistence across an unmount.
|
|
5
|
-
*
|
|
6
|
-
* `draftKey`s are UNIQUE PER TEST on purpose. Composer memoizes one store per key outside React
|
|
7
|
-
* and that store's in-memory value — not localStorage — is the snapshot authority, so clearing
|
|
8
|
-
* localStorage between tests would NOT reset a store that a previous test already hydrated.
|
|
9
|
-
*/
|
|
10
|
-
import { MantineProvider } from '@mantine/core'
|
|
11
|
-
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
12
|
-
import { createRef } from 'react'
|
|
13
|
-
import { afterEach, describe, expect, test } from 'bun:test'
|
|
14
|
-
import { Composer } from './composer'
|
|
15
|
-
import type { ComposerAttachment, ComposerHandle, ComposerProps, ComposerSubmit } from './composer'
|
|
16
|
-
|
|
17
|
-
afterEach(cleanup)
|
|
18
|
-
|
|
19
|
-
function renderComposer(props: ComposerProps) {
|
|
20
|
-
return render(
|
|
21
|
-
<MantineProvider>
|
|
22
|
-
<Composer {...props} />
|
|
23
|
-
</MantineProvider>,
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const noop = (): void => {}
|
|
28
|
-
|
|
29
|
-
function input(): HTMLTextAreaElement {
|
|
30
|
-
return screen.getByRole('textbox') as HTMLTextAreaElement
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function type(text: string): void {
|
|
34
|
-
fireEvent.change(input(), { target: { value: text } })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Several tests below capture a callback's argument into a `let ... | null` variable, then read
|
|
39
|
-
* it back after a `fireEvent`/`act` call that (opaquely, from TS's point of view) triggers the
|
|
40
|
-
* callback synchronously. TS's control-flow analysis can't see through that call boundary, so at
|
|
41
|
-
* the read site it narrows the variable back to its `null` initializer instead of the declared
|
|
42
|
-
* union — `expect()`'s generic then infers `null` and the `[]`/`[ATTACHMENT]` expectation fails
|
|
43
|
-
* to match any overload. Reading through a NON-generic, explicitly-typed function sidesteps the
|
|
44
|
-
* over-narrowing: the return type comes from the fixed signature, not re-inferred from the
|
|
45
|
-
* (already over-narrowed) argument, the way a generic passthrough would.
|
|
46
|
-
*/
|
|
47
|
-
function widen(value: readonly ComposerAttachment[] | null): readonly ComposerAttachment[] | null {
|
|
48
|
-
return value
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const ATTACHMENT: ComposerAttachment = {
|
|
52
|
-
id: 'a1',
|
|
53
|
-
name: 'shot.png',
|
|
54
|
-
mediaType: 'image/png',
|
|
55
|
-
size: 1024,
|
|
56
|
-
url: 'blob:shot',
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Builds a genuine native `Promise` whose prototype chain has been repointed away from THIS
|
|
61
|
-
* realm's `Promise.prototype` — the same shape a promise crossing an iframe/worker boundary would
|
|
62
|
-
* have. `Object.getOwnPropertyDescriptors(Promise.prototype)` copies `then`, `catch`, `finally`,
|
|
63
|
-
* `constructor` and `[Symbol.toStringTag]` onto a fresh object used as the new prototype, so every
|
|
64
|
-
* method is the ORIGINAL native implementation (nothing reimplemented) and the value still
|
|
65
|
-
* satisfies `Promise<T>` structurally with no cast. Only the prototype IDENTITY changes, which is
|
|
66
|
-
* exactly what `instanceof Promise` tests — so it reads `false` for this value while `.then`/
|
|
67
|
-
* `.catch` keep working, because those built-ins check the promise's internal slot, not its
|
|
68
|
-
* exposed prototype. Descriptors are copied via `Object.getOwnPropertyDescriptors` rather than an
|
|
69
|
-
* object-literal `{ then: ... }` so no literal `then` key appears in this file's source — that
|
|
70
|
-
* keeps `unicorn/no-thenable` (which flags authoring a `then` property on a plain object) from
|
|
71
|
-
* firing on what is actually a reflected copy of a real `Promise.prototype`, not a hand-rolled
|
|
72
|
-
* thenable.
|
|
73
|
-
*/
|
|
74
|
-
function crossRealmPromise<T>(executor: ConstructorParameters<typeof Promise<T>>[0]): Promise<T> {
|
|
75
|
-
const native = new Promise<T>(executor)
|
|
76
|
-
const otherRealmProto: object = Object.create(
|
|
77
|
-
Object.prototype,
|
|
78
|
-
Object.getOwnPropertyDescriptors(Promise.prototype),
|
|
79
|
-
)
|
|
80
|
-
Object.setPrototypeOf(native, otherRealmProto)
|
|
81
|
-
return native
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('submit payload + draft lifecycle', () => {
|
|
85
|
-
test('submits the trimmed text with an empty attachment list and clears the draft', () => {
|
|
86
|
-
const calls: ComposerSubmit[] = []
|
|
87
|
-
renderComposer({
|
|
88
|
-
onSubmit: (payload) => {
|
|
89
|
-
calls.push(payload)
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
type(' hello ')
|
|
94
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
95
|
-
|
|
96
|
-
expect(calls).toEqual([{ text: 'hello', attachments: [] }])
|
|
97
|
-
expect(input().value).toBe('')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
test('forwards pending attachments and asks for a reset — an empty caption still sends', () => {
|
|
101
|
-
const calls: ComposerSubmit[] = []
|
|
102
|
-
let reset: readonly ComposerAttachment[] | null = null
|
|
103
|
-
renderComposer({
|
|
104
|
-
onSubmit: (payload) => {
|
|
105
|
-
calls.push(payload)
|
|
106
|
-
},
|
|
107
|
-
attachments: [ATTACHMENT],
|
|
108
|
-
onAttachmentsChange: (next) => {
|
|
109
|
-
reset = next
|
|
110
|
-
},
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
114
|
-
|
|
115
|
-
expect(calls).toEqual([{ text: '', attachments: [ATTACHMENT] }])
|
|
116
|
-
expect(widen(reset)).toEqual([])
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('an empty draft with no attachments cannot be submitted', () => {
|
|
120
|
-
let submits = 0
|
|
121
|
-
renderComposer({
|
|
122
|
-
onSubmit: () => {
|
|
123
|
-
submits += 1
|
|
124
|
-
},
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
expect((screen.getByLabelText('Send message') as HTMLButtonElement).disabled).toBe(true)
|
|
128
|
-
fireEvent.keyDown(input(), { key: 'Enter' })
|
|
129
|
-
|
|
130
|
-
expect(submits).toBe(0)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* The "a failed send does not eat what was typed" guarantee, stated precisely: the clear is
|
|
135
|
-
* sequenced STRICTLY AFTER `onSubmit` returns and is not in a `finally`, so a parent that throws
|
|
136
|
-
* to reject the send never reaches it.
|
|
137
|
-
*
|
|
138
|
-
* The throw itself is deliberately NOT exercised here. React 19 does not let an error thrown in
|
|
139
|
-
* an event handler propagate out of `dispatchEvent` — it hands it to the global `reportError`,
|
|
140
|
-
* captured by `react.development.js` at MODULE EVALUATION (react.development.js:714), so it can
|
|
141
|
-
* be neither caught at the `fireEvent` call nor intercepted by stubbing `reportError` afterwards,
|
|
142
|
-
* and `bun test` fails the file. What IS observable — and is what actually delivers the
|
|
143
|
-
* guarantee — is the ordering.
|
|
144
|
-
*
|
|
145
|
-
* That ordering must be probed through STATE, not the DOM. `input().value` is worthless here:
|
|
146
|
-
* the value comes from `useSyncExternalStore`, and React batches the resulting re-render to the
|
|
147
|
-
* end of the discrete click event, so the textarea still reads 'keep me' inside the callback
|
|
148
|
-
* whether `store.set('')` ran before `onSubmit` or after it — an assertion that cannot fail.
|
|
149
|
-
* The store's localStorage write IS synchronous, so with a `draftKey` the persisted entry flips
|
|
150
|
-
* the instant the ordering is wrong.
|
|
151
|
-
*/
|
|
152
|
-
test('the persisted draft is intact while onSubmit runs, and clears only once it returns', () => {
|
|
153
|
-
const storageKey = 'basalt:composer-draft:clear-after-onsubmit'
|
|
154
|
-
let persistedDuringSubmit: string | null = 'not observed'
|
|
155
|
-
renderComposer({
|
|
156
|
-
onSubmit: () => {
|
|
157
|
-
persistedDuringSubmit = window.localStorage.getItem(storageKey)
|
|
158
|
-
},
|
|
159
|
-
draftKey: 'clear-after-onsubmit',
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
type('keep me')
|
|
163
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
164
|
-
|
|
165
|
-
expect(persistedDuringSubmit).toBe(JSON.stringify({ v: 1, value: 'keep me' }))
|
|
166
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
167
|
-
expect(input().value).toBe('')
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
describe('async onSubmit — optimistic clear, rejection restores', () => {
|
|
172
|
-
/**
|
|
173
|
-
* A plain `void`-returning handler — the pre-existing contract — must keep working unchanged.
|
|
174
|
-
* This is the backward-compatibility guard for widening `onSubmit` to `void | Promise<void>`.
|
|
175
|
-
*/
|
|
176
|
-
test('a void-returning onSubmit still clears the draft as before', () => {
|
|
177
|
-
const calls: ComposerSubmit[] = []
|
|
178
|
-
renderComposer({
|
|
179
|
-
onSubmit: (payload) => {
|
|
180
|
-
calls.push(payload)
|
|
181
|
-
},
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
type('plain handler')
|
|
185
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
186
|
-
|
|
187
|
-
expect(calls).toEqual([{ text: 'plain handler', attachments: [] }])
|
|
188
|
-
expect(input().value).toBe('')
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
test('a resolving async onSubmit leaves the draft and attachments cleared', async () => {
|
|
192
|
-
const storageKey = 'basalt:composer-draft:resolves'
|
|
193
|
-
let resolveSubmit: (() => void) | null = null
|
|
194
|
-
let reset: readonly ComposerAttachment[] | null = null
|
|
195
|
-
renderComposer({
|
|
196
|
-
onSubmit: () =>
|
|
197
|
-
new Promise<void>((resolve) => {
|
|
198
|
-
resolveSubmit = resolve
|
|
199
|
-
}),
|
|
200
|
-
attachments: [ATTACHMENT],
|
|
201
|
-
onAttachmentsChange: (next) => {
|
|
202
|
-
reset = next
|
|
203
|
-
},
|
|
204
|
-
draftKey: 'resolves',
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
type('will succeed')
|
|
208
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
209
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
210
|
-
expect(widen(reset)).toEqual([])
|
|
211
|
-
|
|
212
|
-
if (resolveSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
213
|
-
await act(async () => {
|
|
214
|
-
resolveSubmit?.()
|
|
215
|
-
await Promise.resolve()
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
219
|
-
expect(input().value).toBe('')
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
test('a synchronous throw still keeps the draft and attachments (unchanged pre-async behaviour)', () => {
|
|
223
|
-
const storageKey = 'basalt:composer-draft:sync-throw'
|
|
224
|
-
let reset: readonly ComposerAttachment[] | null = null
|
|
225
|
-
renderComposer({
|
|
226
|
-
onSubmit: () => {
|
|
227
|
-
throw new Error('reject synchronously')
|
|
228
|
-
},
|
|
229
|
-
attachments: [ATTACHMENT],
|
|
230
|
-
onAttachmentsChange: (next) => {
|
|
231
|
-
reset = next
|
|
232
|
-
},
|
|
233
|
-
draftKey: 'sync-throw',
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
type('keep me on throw')
|
|
237
|
-
expect(() => fireEvent.click(screen.getByLabelText('Send message'))).toThrow()
|
|
238
|
-
|
|
239
|
-
expect(window.localStorage.getItem(storageKey)).toBe(
|
|
240
|
-
JSON.stringify({ v: 1, value: 'keep me on throw' }),
|
|
241
|
-
)
|
|
242
|
-
expect(reset).toBeNull()
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
test('a rejecting async onSubmit restores the text and the attachments', async () => {
|
|
246
|
-
const storageKey = 'basalt:composer-draft:rejects'
|
|
247
|
-
let rejectSubmit: ((reason: unknown) => void) | null = null
|
|
248
|
-
let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
|
|
249
|
-
const { rerender } = renderComposer({
|
|
250
|
-
onSubmit: () =>
|
|
251
|
-
new Promise<void>((_resolve, reject) => {
|
|
252
|
-
rejectSubmit = reject
|
|
253
|
-
}),
|
|
254
|
-
attachments: [ATTACHMENT],
|
|
255
|
-
onAttachmentsChange: (next) => {
|
|
256
|
-
lastAttachmentsChange = next
|
|
257
|
-
},
|
|
258
|
-
draftKey: 'rejects',
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
type('network will fail')
|
|
262
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
263
|
-
// Optimistic clear already happened.
|
|
264
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
265
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
266
|
-
|
|
267
|
-
// The caller applies the optimistic clear it was asked for — a controlled `attachments` prop
|
|
268
|
-
// round-trips back through a render, exactly like a real `useState` setter would.
|
|
269
|
-
rerender(
|
|
270
|
-
<MantineProvider>
|
|
271
|
-
<Composer
|
|
272
|
-
onSubmit={noop}
|
|
273
|
-
attachments={[]}
|
|
274
|
-
onAttachmentsChange={(next) => {
|
|
275
|
-
lastAttachmentsChange = next
|
|
276
|
-
}}
|
|
277
|
-
draftKey="rejects"
|
|
278
|
-
/>
|
|
279
|
-
</MantineProvider>,
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
283
|
-
await act(async () => {
|
|
284
|
-
rejectSubmit?.(new Error('network error'))
|
|
285
|
-
await Promise.resolve()
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
expect(window.localStorage.getItem(storageKey)).toBe(
|
|
289
|
-
JSON.stringify({ v: 1, value: 'network will fail' }),
|
|
290
|
-
)
|
|
291
|
-
expect(widen(lastAttachmentsChange)).toEqual([ATTACHMENT])
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Finding 1 regression: `onSubmit` is typed `void | Promise<void>`, and a bare structural
|
|
296
|
-
* thenable does NOT actually satisfy that type (`PromiseLike<void>` is missing `catch`/
|
|
297
|
-
* `finally`/`[Symbol.toStringTag]`, which `Promise<void>` requires — TS2322). What DOES satisfy
|
|
298
|
-
* it, and DOES still defeat `result instanceof Promise`, is a promise from ANOTHER REALM: a
|
|
299
|
-
* genuine native promise whose `instanceof` reads false because `instanceof` compares prototype
|
|
300
|
-
* IDENTITY against THIS realm's `Promise`, not structural shape. `crossRealmPromise` builds
|
|
301
|
-
* exactly that. Its rejection must still restore the text and the attachments, exactly like a
|
|
302
|
-
* same-realm native `Promise` rejecting does above.
|
|
303
|
-
*/
|
|
304
|
-
test('a cross-realm promise rejecting still restores the text and the attachments', async () => {
|
|
305
|
-
const storageKey = 'basalt:composer-draft:cross-realm-rejects'
|
|
306
|
-
let rejectSubmit: ((reason: unknown) => void) | null = null
|
|
307
|
-
const crossRealm = crossRealmPromise<void>((_resolve, reject) => {
|
|
308
|
-
rejectSubmit = reject
|
|
309
|
-
})
|
|
310
|
-
// The whole point of the fixture: prove it is genuinely NOT of this realm's `Promise` before
|
|
311
|
-
// relying on anything else about it — otherwise this test would silently degrade into a re-run
|
|
312
|
-
// of the native-promise case above and prove nothing about the regression it targets.
|
|
313
|
-
expect(crossRealm instanceof Promise).toBe(false)
|
|
314
|
-
|
|
315
|
-
let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
|
|
316
|
-
const { rerender } = renderComposer({
|
|
317
|
-
onSubmit: () => crossRealm,
|
|
318
|
-
attachments: [ATTACHMENT],
|
|
319
|
-
onAttachmentsChange: (next) => {
|
|
320
|
-
lastAttachmentsChange = next
|
|
321
|
-
},
|
|
322
|
-
draftKey: 'cross-realm-rejects',
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
type('a cross-realm send')
|
|
326
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
327
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
328
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
329
|
-
|
|
330
|
-
// Same controlled-prop round trip as the native-Promise version of this test above.
|
|
331
|
-
rerender(
|
|
332
|
-
<MantineProvider>
|
|
333
|
-
<Composer
|
|
334
|
-
onSubmit={noop}
|
|
335
|
-
attachments={[]}
|
|
336
|
-
onAttachmentsChange={(next) => {
|
|
337
|
-
lastAttachmentsChange = next
|
|
338
|
-
}}
|
|
339
|
-
draftKey="cross-realm-rejects"
|
|
340
|
-
/>
|
|
341
|
-
</MantineProvider>,
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
345
|
-
await act(async () => {
|
|
346
|
-
rejectSubmit?.(new Error('cross-realm rejection'))
|
|
347
|
-
await Promise.resolve()
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
expect(window.localStorage.getItem(storageKey)).toBe(
|
|
351
|
-
JSON.stringify({ v: 1, value: 'a cross-realm send' }),
|
|
352
|
-
)
|
|
353
|
-
expect(widen(lastAttachmentsChange)).toEqual([ATTACHMENT])
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
test('does not restore text the user already replaced by the time the rejection arrives', async () => {
|
|
357
|
-
const storageKey = 'basalt:composer-draft:typed-over'
|
|
358
|
-
let rejectSubmit: ((reason: unknown) => void) | null = null
|
|
359
|
-
renderComposer({
|
|
360
|
-
onSubmit: () =>
|
|
361
|
-
new Promise<void>((_resolve, reject) => {
|
|
362
|
-
rejectSubmit = reject
|
|
363
|
-
}),
|
|
364
|
-
draftKey: 'typed-over',
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
type('the failed message')
|
|
368
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
369
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
370
|
-
|
|
371
|
-
// The user starts a fresh, unrelated message before the rejection lands.
|
|
372
|
-
type('a brand new thought')
|
|
373
|
-
|
|
374
|
-
if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
375
|
-
await act(async () => {
|
|
376
|
-
rejectSubmit?.(new Error('network error'))
|
|
377
|
-
await Promise.resolve()
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
expect(window.localStorage.getItem(storageKey)).toBe(
|
|
381
|
-
JSON.stringify({ v: 1, value: 'a brand new thought' }),
|
|
382
|
-
)
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
test('a first submit rejecting after a second succeeded does not clobber the cleared draft', async () => {
|
|
386
|
-
const storageKey = 'basalt:composer-draft:superseded'
|
|
387
|
-
const rejections: Array<(reason: unknown) => void> = []
|
|
388
|
-
let call = 0
|
|
389
|
-
renderComposer({
|
|
390
|
-
onSubmit: () =>
|
|
391
|
-
new Promise<void>((resolve, reject) => {
|
|
392
|
-
call += 1
|
|
393
|
-
if (call === 1) rejections.push(reject)
|
|
394
|
-
else resolve()
|
|
395
|
-
}),
|
|
396
|
-
draftKey: 'superseded',
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
type('first message')
|
|
400
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
401
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
402
|
-
|
|
403
|
-
type('second message')
|
|
404
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
405
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
406
|
-
|
|
407
|
-
const rejectFirst = rejections[0]
|
|
408
|
-
if (rejectFirst === undefined) throw new Error('expected the first submit to have rejected')
|
|
409
|
-
await act(async () => {
|
|
410
|
-
rejectFirst(new Error('first send failed after the second already went through'))
|
|
411
|
-
await Promise.resolve()
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
// The second submit's own (successful) clear must win — the first message does not reappear.
|
|
415
|
-
expect(window.localStorage.getItem(storageKey)).toBeNull()
|
|
416
|
-
expect(input().value).toBe('')
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
test('does not restore attachments the caller already changed via the controlled prop', async () => {
|
|
420
|
-
let rejectSubmit: ((reason: unknown) => void) | null = null
|
|
421
|
-
let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
|
|
422
|
-
const { rerender } = renderComposer({
|
|
423
|
-
onSubmit: () =>
|
|
424
|
-
new Promise<void>((_resolve, reject) => {
|
|
425
|
-
rejectSubmit = reject
|
|
426
|
-
}),
|
|
427
|
-
attachments: [ATTACHMENT],
|
|
428
|
-
onAttachmentsChange: (next) => {
|
|
429
|
-
lastAttachmentsChange = next
|
|
430
|
-
},
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
434
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
435
|
-
|
|
436
|
-
// The caller applies the optimistic clear, then independently attaches something new —
|
|
437
|
-
// NOT via a second Composer submit, just its own unrelated state change.
|
|
438
|
-
const replacement: ComposerAttachment = { ...ATTACHMENT, id: 'a2', name: 'other.png' }
|
|
439
|
-
rerender(
|
|
440
|
-
<MantineProvider>
|
|
441
|
-
<Composer
|
|
442
|
-
onSubmit={noop}
|
|
443
|
-
attachments={[replacement]}
|
|
444
|
-
onAttachmentsChange={(next) => {
|
|
445
|
-
lastAttachmentsChange = next
|
|
446
|
-
}}
|
|
447
|
-
/>
|
|
448
|
-
</MantineProvider>,
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
452
|
-
await act(async () => {
|
|
453
|
-
rejectSubmit?.(new Error('network error'))
|
|
454
|
-
await Promise.resolve()
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
// The replacement attachment must survive — the failed send's original attachment is dropped.
|
|
458
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
test('does not restore attachments into a different thread after draftKey changed', async () => {
|
|
462
|
-
let rejectSubmit: ((reason: unknown) => void) | null = null
|
|
463
|
-
let lastAttachmentsChange: readonly ComposerAttachment[] | null = null
|
|
464
|
-
const { rerender } = renderComposer({
|
|
465
|
-
onSubmit: () =>
|
|
466
|
-
new Promise<void>((_resolve, reject) => {
|
|
467
|
-
rejectSubmit = reject
|
|
468
|
-
}),
|
|
469
|
-
attachments: [ATTACHMENT],
|
|
470
|
-
onAttachmentsChange: (next) => {
|
|
471
|
-
lastAttachmentsChange = next
|
|
472
|
-
},
|
|
473
|
-
draftKey: 'thread-a',
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
477
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
478
|
-
|
|
479
|
-
// The consumer switches this same Composer instance to a different thread before the
|
|
480
|
-
// rejection arrives — a fresh, still-empty attachments slot for that other thread.
|
|
481
|
-
rerender(
|
|
482
|
-
<MantineProvider>
|
|
483
|
-
<Composer
|
|
484
|
-
onSubmit={noop}
|
|
485
|
-
attachments={[]}
|
|
486
|
-
onAttachmentsChange={(next) => {
|
|
487
|
-
lastAttachmentsChange = next
|
|
488
|
-
}}
|
|
489
|
-
draftKey="thread-b"
|
|
490
|
-
/>
|
|
491
|
-
</MantineProvider>,
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
if (rejectSubmit === null) throw new Error('expected onSubmit to have been called')
|
|
495
|
-
await act(async () => {
|
|
496
|
-
rejectSubmit?.(new Error('network error'))
|
|
497
|
-
await Promise.resolve()
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
// Thread B's attachments slot must stay untouched by thread A's failed send.
|
|
501
|
-
expect(widen(lastAttachmentsChange)).toEqual([])
|
|
502
|
-
})
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
describe('keyboard contract', () => {
|
|
506
|
-
test('Enter submits', () => {
|
|
507
|
-
let submits = 0
|
|
508
|
-
renderComposer({
|
|
509
|
-
onSubmit: () => {
|
|
510
|
-
submits += 1
|
|
511
|
-
},
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
type('go')
|
|
515
|
-
fireEvent.keyDown(input(), { key: 'Enter' })
|
|
516
|
-
|
|
517
|
-
expect(submits).toBe(1)
|
|
518
|
-
expect(input().value).toBe('')
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
test('Shift+Enter does not submit', () => {
|
|
522
|
-
let submits = 0
|
|
523
|
-
renderComposer({
|
|
524
|
-
onSubmit: () => {
|
|
525
|
-
submits += 1
|
|
526
|
-
},
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
type('go')
|
|
530
|
-
fireEvent.keyDown(input(), { key: 'Enter', shiftKey: true })
|
|
531
|
-
|
|
532
|
-
expect(submits).toBe(0)
|
|
533
|
-
expect(input().value).toBe('go')
|
|
534
|
-
})
|
|
535
|
-
|
|
536
|
-
test('Enter mid-IME-composition does not submit — it commits the candidate', () => {
|
|
537
|
-
let submits = 0
|
|
538
|
-
renderComposer({
|
|
539
|
-
onSubmit: () => {
|
|
540
|
-
submits += 1
|
|
541
|
-
},
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
type('にほん')
|
|
545
|
-
fireEvent.keyDown(input(), { key: 'Enter', isComposing: true })
|
|
546
|
-
|
|
547
|
-
expect(submits).toBe(0)
|
|
548
|
-
expect(input().value).toBe('にほん')
|
|
549
|
-
})
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
describe('streaming gate', () => {
|
|
553
|
-
test('streaming without onStop disables the input and keeps the send action', () => {
|
|
554
|
-
renderComposer({ onSubmit: noop, streaming: true })
|
|
555
|
-
|
|
556
|
-
expect(input().disabled).toBe(true)
|
|
557
|
-
expect(screen.getByLabelText('Send message')).toBeDefined()
|
|
558
|
-
expect(screen.queryByLabelText('Stop generating')).toBeNull()
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
test('streaming with onStop swaps send for a Stop action', () => {
|
|
562
|
-
let stops = 0
|
|
563
|
-
renderComposer({
|
|
564
|
-
onSubmit: noop,
|
|
565
|
-
streaming: true,
|
|
566
|
-
onStop: () => {
|
|
567
|
-
stops += 1
|
|
568
|
-
},
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
expect(screen.queryByLabelText('Send message')).toBeNull()
|
|
572
|
-
fireEvent.click(screen.getByLabelText('Stop generating'))
|
|
573
|
-
|
|
574
|
-
expect(stops).toBe(1)
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* The combination `allowSubmitWhileStreaming` exists for: the textarea stays live mid-run, so a
|
|
579
|
-
* mouse or touch user needs a Send button as much as a keyboard user needs Enter. Stop replacing
|
|
580
|
-
* Send here would make the send action keyboard-only.
|
|
581
|
-
*/
|
|
582
|
-
test('allowSubmitWhileStreaming with onStop renders BOTH the stop and the send action', () => {
|
|
583
|
-
const calls: ComposerSubmit[] = []
|
|
584
|
-
let stops = 0
|
|
585
|
-
renderComposer({
|
|
586
|
-
onSubmit: (payload) => {
|
|
587
|
-
calls.push(payload)
|
|
588
|
-
},
|
|
589
|
-
streaming: true,
|
|
590
|
-
allowSubmitWhileStreaming: true,
|
|
591
|
-
onStop: () => {
|
|
592
|
-
stops += 1
|
|
593
|
-
},
|
|
594
|
-
})
|
|
595
|
-
|
|
596
|
-
type('interrupt')
|
|
597
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
598
|
-
expect(calls).toEqual([{ text: 'interrupt', attachments: [] }])
|
|
599
|
-
|
|
600
|
-
fireEvent.click(screen.getByLabelText('Stop generating'))
|
|
601
|
-
expect(stops).toBe(1)
|
|
602
|
-
})
|
|
603
|
-
|
|
604
|
-
test('allowSubmitWhileStreaming permits a submit mid-run', () => {
|
|
605
|
-
const calls: ComposerSubmit[] = []
|
|
606
|
-
renderComposer({
|
|
607
|
-
onSubmit: (payload) => {
|
|
608
|
-
calls.push(payload)
|
|
609
|
-
},
|
|
610
|
-
streaming: true,
|
|
611
|
-
allowSubmitWhileStreaming: true,
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
expect(input().disabled).toBe(false)
|
|
615
|
-
type('interrupt')
|
|
616
|
-
fireEvent.keyDown(input(), { key: 'Enter' })
|
|
617
|
-
|
|
618
|
-
expect(calls).toEqual([{ text: 'interrupt', attachments: [] }])
|
|
619
|
-
})
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
describe('draftKey', () => {
|
|
623
|
-
test('a draft survives an unmount and lands in the basalt: envelope', () => {
|
|
624
|
-
const { unmount } = renderComposer({ onSubmit: noop, draftKey: 'survives-unmount' })
|
|
625
|
-
type('half a thought')
|
|
626
|
-
unmount()
|
|
627
|
-
|
|
628
|
-
expect(window.localStorage.getItem('basalt:composer-draft:survives-unmount')).toBe(
|
|
629
|
-
JSON.stringify({ v: 1, value: 'half a thought' }),
|
|
630
|
-
)
|
|
631
|
-
|
|
632
|
-
renderComposer({ onSubmit: noop, draftKey: 'survives-unmount' })
|
|
633
|
-
expect(input().value).toBe('half a thought')
|
|
634
|
-
})
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* The REAL page-reload path, and the only test that exercises it. 'a draft survives an unmount'
|
|
638
|
-
* above proves much less than it looks: the store lives in a module-scope Map that outlives
|
|
639
|
-
* `cleanup()`, so the remount reuses the same already-hydrated object and never touches storage.
|
|
640
|
-
* Here the key has no store yet, so the first `get()` has to go through `readPersistedValue` —
|
|
641
|
-
* which is what a fresh process does after a reload. A wrong prefix on the read side survives
|
|
642
|
-
* every other test in this file and fails only this one.
|
|
643
|
-
*/
|
|
644
|
-
test('hydrates a draft a previous session left in storage', () => {
|
|
645
|
-
window.localStorage.setItem(
|
|
646
|
-
'basalt:composer-draft:hydrated-from-storage',
|
|
647
|
-
JSON.stringify({ v: 1, value: 'from a previous session' }),
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
renderComposer({ onSubmit: noop, draftKey: 'hydrated-from-storage' })
|
|
651
|
-
|
|
652
|
-
expect(input().value).toBe('from a previous session')
|
|
653
|
-
})
|
|
654
|
-
|
|
655
|
-
test('a stale-version envelope is ignored rather than hydrated', () => {
|
|
656
|
-
window.localStorage.setItem(
|
|
657
|
-
'basalt:composer-draft:stale-envelope',
|
|
658
|
-
JSON.stringify({ v: 0, value: 'written by an older shape' }),
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
renderComposer({ onSubmit: noop, draftKey: 'stale-envelope' })
|
|
662
|
-
|
|
663
|
-
expect(input().value).toBe('')
|
|
664
|
-
})
|
|
665
|
-
|
|
666
|
-
test("switching draftKey swaps to that key's draft and does not leak the old one", () => {
|
|
667
|
-
const { rerender } = renderComposer({ onSubmit: noop, draftKey: 'swap-a' })
|
|
668
|
-
type('alpha')
|
|
669
|
-
|
|
670
|
-
rerender(
|
|
671
|
-
<MantineProvider>
|
|
672
|
-
<Composer onSubmit={noop} draftKey="swap-b" />
|
|
673
|
-
</MantineProvider>,
|
|
674
|
-
)
|
|
675
|
-
expect(input().value).toBe('')
|
|
676
|
-
type('beta')
|
|
677
|
-
|
|
678
|
-
rerender(
|
|
679
|
-
<MantineProvider>
|
|
680
|
-
<Composer onSubmit={noop} draftKey="swap-a" />
|
|
681
|
-
</MantineProvider>,
|
|
682
|
-
)
|
|
683
|
-
expect(input().value).toBe('alpha')
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
test('a successful submit removes the persisted entry rather than leaving an empty husk', () => {
|
|
687
|
-
renderComposer({ onSubmit: noop, draftKey: 'cleared-on-send' })
|
|
688
|
-
type('sent')
|
|
689
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
690
|
-
|
|
691
|
-
expect(window.localStorage.getItem('basalt:composer-draft:cleared-on-send')).toBeNull()
|
|
692
|
-
})
|
|
693
|
-
|
|
694
|
-
test('two keyless Composers do not share a draft', () => {
|
|
695
|
-
render(
|
|
696
|
-
<MantineProvider>
|
|
697
|
-
<Composer onSubmit={noop} placeholder="first" />
|
|
698
|
-
<Composer onSubmit={noop} placeholder="second" />
|
|
699
|
-
</MantineProvider>,
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
const boxes = screen.getAllByRole('textbox') as HTMLTextAreaElement[]
|
|
703
|
-
const [first, second] = [boxes[0], boxes[1]]
|
|
704
|
-
if (first === undefined || second === undefined) throw new Error('expected two composers')
|
|
705
|
-
fireEvent.change(first, { target: { value: 'only mine' } })
|
|
706
|
-
|
|
707
|
-
expect(first.value).toBe('only mine')
|
|
708
|
-
expect(second.value).toBe('')
|
|
709
|
-
})
|
|
710
|
-
})
|
|
711
|
-
|
|
712
|
-
describe('slots', () => {
|
|
713
|
-
test('leftSection and rightSection render inside the input row', () => {
|
|
714
|
-
renderComposer({
|
|
715
|
-
onSubmit: noop,
|
|
716
|
-
leftSection: <div data-testid="left" />,
|
|
717
|
-
rightSection: <div data-testid="right" />,
|
|
718
|
-
})
|
|
719
|
-
|
|
720
|
-
expect(screen.getByTestId('left')).toBeDefined()
|
|
721
|
-
expect(screen.getByTestId('right')).toBeDefined()
|
|
722
|
-
})
|
|
723
|
-
|
|
724
|
-
test('the default hint row is the Enter/Shift+Enter contract', () => {
|
|
725
|
-
renderComposer({ onSubmit: noop })
|
|
726
|
-
|
|
727
|
-
expect(screen.getByText('to send')).toBeDefined()
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
test('hint replaces the whole default row', () => {
|
|
731
|
-
renderComposer({ onSubmit: noop, hint: <div data-testid="hint" /> })
|
|
732
|
-
|
|
733
|
-
expect(screen.getByTestId('hint')).toBeDefined()
|
|
734
|
-
expect(screen.queryByText('to send')).toBeNull()
|
|
735
|
-
})
|
|
736
|
-
|
|
737
|
-
test('hint={null} drops the row entirely', () => {
|
|
738
|
-
renderComposer({ onSubmit: noop, hint: null })
|
|
739
|
-
|
|
740
|
-
expect(screen.queryByText('to send')).toBeNull()
|
|
741
|
-
})
|
|
742
|
-
})
|
|
743
|
-
|
|
744
|
-
describe('ComposerHandle (ref)', () => {
|
|
745
|
-
test('insertText appends at the end when the textarea has never been focused', () => {
|
|
746
|
-
const handle = createRef<ComposerHandle>()
|
|
747
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
748
|
-
|
|
749
|
-
type('partial')
|
|
750
|
-
act(() => {
|
|
751
|
-
handle.current?.insertText(' more')
|
|
752
|
-
})
|
|
753
|
-
|
|
754
|
-
expect(input().value).toBe('partial more')
|
|
755
|
-
})
|
|
756
|
-
|
|
757
|
-
test('insertText inserts at the cursor position, not blindly at the end', () => {
|
|
758
|
-
const handle = createRef<ComposerHandle>()
|
|
759
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
760
|
-
|
|
761
|
-
type('hello world')
|
|
762
|
-
const el = input()
|
|
763
|
-
el.focus()
|
|
764
|
-
el.setSelectionRange(5, 5) // caret right after "hello"
|
|
765
|
-
act(() => {
|
|
766
|
-
handle.current?.insertText(' there')
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
expect(el.value).toBe('hello there world')
|
|
770
|
-
})
|
|
771
|
-
|
|
772
|
-
test('insertText replaces a selection rather than inserting alongside it', () => {
|
|
773
|
-
const handle = createRef<ComposerHandle>()
|
|
774
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
775
|
-
|
|
776
|
-
type('hello brave world')
|
|
777
|
-
const el = input()
|
|
778
|
-
el.focus()
|
|
779
|
-
el.setSelectionRange(6, 11) // "brave"
|
|
780
|
-
act(() => {
|
|
781
|
-
handle.current?.insertText('bold')
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
expect(el.value).toBe('hello bold world')
|
|
785
|
-
})
|
|
786
|
-
|
|
787
|
-
test('setValue replaces the whole draft value', () => {
|
|
788
|
-
const handle = createRef<ComposerHandle>()
|
|
789
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
790
|
-
|
|
791
|
-
type('old text')
|
|
792
|
-
act(() => {
|
|
793
|
-
handle.current?.setValue('brand new')
|
|
794
|
-
})
|
|
795
|
-
|
|
796
|
-
expect(input().value).toBe('brand new')
|
|
797
|
-
})
|
|
798
|
-
|
|
799
|
-
test('focus moves focus to the textarea', () => {
|
|
800
|
-
const handle = createRef<ComposerHandle>()
|
|
801
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
802
|
-
|
|
803
|
-
act(() => {
|
|
804
|
-
handle.current?.focus()
|
|
805
|
-
})
|
|
806
|
-
|
|
807
|
-
expect(document.activeElement).toBe(input())
|
|
808
|
-
})
|
|
809
|
-
|
|
810
|
-
test('an inserted value persists under draftKey and survives remount', () => {
|
|
811
|
-
const handle = createRef<ComposerHandle>()
|
|
812
|
-
const { unmount } = renderComposer({ onSubmit: noop, draftKey: 'ref-persists', ref: handle })
|
|
813
|
-
|
|
814
|
-
act(() => {
|
|
815
|
-
handle.current?.insertText('via ref')
|
|
816
|
-
})
|
|
817
|
-
unmount()
|
|
818
|
-
|
|
819
|
-
expect(window.localStorage.getItem('basalt:composer-draft:ref-persists')).toBe(
|
|
820
|
-
JSON.stringify({ v: 1, value: 'via ref' }),
|
|
821
|
-
)
|
|
822
|
-
|
|
823
|
-
renderComposer({ onSubmit: noop, draftKey: 'ref-persists' })
|
|
824
|
-
expect(input().value).toBe('via ref')
|
|
825
|
-
})
|
|
826
|
-
|
|
827
|
-
test('insertText works with no draftKey (the per-instance memory store)', () => {
|
|
828
|
-
const handle = createRef<ComposerHandle>()
|
|
829
|
-
renderComposer({ onSubmit: noop, ref: handle })
|
|
830
|
-
|
|
831
|
-
act(() => {
|
|
832
|
-
handle.current?.insertText('memory only')
|
|
833
|
-
})
|
|
834
|
-
|
|
835
|
-
expect(input().value).toBe('memory only')
|
|
836
|
-
})
|
|
837
|
-
|
|
838
|
-
test('a submit after an imperative insertText carries the inserted text in its payload', () => {
|
|
839
|
-
const calls: ComposerSubmit[] = []
|
|
840
|
-
const handle = createRef<ComposerHandle>()
|
|
841
|
-
renderComposer({
|
|
842
|
-
onSubmit: (payload) => {
|
|
843
|
-
calls.push(payload)
|
|
844
|
-
},
|
|
845
|
-
ref: handle,
|
|
846
|
-
})
|
|
847
|
-
|
|
848
|
-
act(() => {
|
|
849
|
-
handle.current?.insertText('from voice')
|
|
850
|
-
})
|
|
851
|
-
fireEvent.click(screen.getByLabelText('Send message'))
|
|
852
|
-
|
|
853
|
-
expect(calls).toEqual([{ text: 'from voice', attachments: [] }])
|
|
854
|
-
})
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Regression for the stale `pendingCaretRef` bug: `insertText`/`setValue` used to arm the ref
|
|
858
|
-
* UNCONDITIONALLY, even when the write was a no-op (didn't change the store's value). Because the
|
|
859
|
-
* caret-consuming layout effect is keyed on `[value]`, a no-op write never triggers it — the ref
|
|
860
|
-
* stayed armed. The NEXT edit that actually changes the value (ordinary typing through
|
|
861
|
-
* `onChange`, which never touches this ref itself) then had that stale, unrelated position
|
|
862
|
-
* forced onto it instead of wherever the real edit naturally landed.
|
|
863
|
-
*
|
|
864
|
-
* Both tests below prove the fix by comparison, not by hard-coding a "natural" DOM/React
|
|
865
|
-
* selection-restoration value (which this harness cannot predict independently of the bug):
|
|
866
|
-
* the SAME real edit is run once with the no-op priming it and once without, and the resulting
|
|
867
|
-
* caret must be identical in both — a no-op write must be invisible to what follows.
|
|
868
|
-
*/
|
|
869
|
-
describe('a no-op write does not arm a stale caret for the next real edit', () => {
|
|
870
|
-
test('insertText("") at a collapsed caret', () => {
|
|
871
|
-
const primed = createRef<ComposerHandle>()
|
|
872
|
-
renderComposer({ onSubmit: noop, ref: primed })
|
|
873
|
-
type('hellox')
|
|
874
|
-
const primedEl = input()
|
|
875
|
-
primedEl.focus()
|
|
876
|
-
primedEl.setSelectionRange(5, 5)
|
|
877
|
-
act(() => {
|
|
878
|
-
primed.current?.insertText('') // no-op: content unchanged
|
|
879
|
-
})
|
|
880
|
-
fireEvent.change(primedEl, { target: { value: 'hellofoox' } })
|
|
881
|
-
const primedCaret = primedEl.selectionStart
|
|
882
|
-
|
|
883
|
-
cleanup()
|
|
884
|
-
|
|
885
|
-
const control = createRef<ComposerHandle>()
|
|
886
|
-
renderComposer({ onSubmit: noop, ref: control })
|
|
887
|
-
type('hellox')
|
|
888
|
-
const controlEl = input()
|
|
889
|
-
controlEl.focus()
|
|
890
|
-
controlEl.setSelectionRange(5, 5)
|
|
891
|
-
// No insertText('') here — this is the same real edit, unprimed.
|
|
892
|
-
fireEvent.change(controlEl, { target: { value: 'hellofoox' } })
|
|
893
|
-
const controlCaret = controlEl.selectionStart
|
|
894
|
-
|
|
895
|
-
expect(primedCaret).toBe(controlCaret)
|
|
896
|
-
})
|
|
897
|
-
|
|
898
|
-
test('setValue with the value it already has', () => {
|
|
899
|
-
const primed = createRef<ComposerHandle>()
|
|
900
|
-
renderComposer({ onSubmit: noop, ref: primed })
|
|
901
|
-
type('abcdef')
|
|
902
|
-
const primedEl = input()
|
|
903
|
-
primedEl.focus()
|
|
904
|
-
act(() => {
|
|
905
|
-
primed.current?.setValue('abcdef') // no-op: already this value
|
|
906
|
-
})
|
|
907
|
-
fireEvent.change(primedEl, { target: { value: 'abXcdef' } })
|
|
908
|
-
const primedCaret = primedEl.selectionStart
|
|
909
|
-
|
|
910
|
-
cleanup()
|
|
911
|
-
|
|
912
|
-
const control = createRef<ComposerHandle>()
|
|
913
|
-
renderComposer({ onSubmit: noop, ref: control })
|
|
914
|
-
type('abcdef')
|
|
915
|
-
const controlEl = input()
|
|
916
|
-
controlEl.focus()
|
|
917
|
-
// No setValue('abcdef') here — this is the same real edit, unprimed.
|
|
918
|
-
fireEvent.change(controlEl, { target: { value: 'abXcdef' } })
|
|
919
|
-
const controlCaret = controlEl.selectionStart
|
|
920
|
-
|
|
921
|
-
expect(primedCaret).toBe(controlCaret)
|
|
922
|
-
})
|
|
923
|
-
})
|
|
924
|
-
})
|