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,603 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markdown — the fence-renderer registry, the sanitize pass's composition + POSITION, and the
|
|
3
|
-
* lazy `remend` contract (docs/CONTENT-SPEC.md §2/§6).
|
|
4
|
-
*
|
|
5
|
-
* Every case renders through the real lazy chain (react-markdown + remark-gfm + rehype-sanitize
|
|
6
|
-
* are all installed here as devDependencies), so `findBy*` — not `getBy*` — is the entry point:
|
|
7
|
-
* the first paint is the `Suspense` plain-text fallback.
|
|
8
|
-
*
|
|
9
|
-
* `composeRehypePlugins` is imported directly from `./markdown` (it is deliberately NOT re-exported
|
|
10
|
-
* by `./index.ts`) — same not-public-surface test idiom as `messageBlockRenderCounter`.
|
|
11
|
-
*/
|
|
12
|
-
import { MantineProvider } from '@mantine/core'
|
|
13
|
-
import { render, screen, waitFor } from '@testing-library/react'
|
|
14
|
-
import { describe, expect, test } from 'bun:test'
|
|
15
|
-
import type { Element, Root } from 'hast'
|
|
16
|
-
import { composeRehypePlugins, Markdown, settledOnly } from './markdown'
|
|
17
|
-
import type { MarkdownContentTrust } from './markdown'
|
|
18
|
-
|
|
19
|
-
function renderMarkdown(ui: React.ReactElement) {
|
|
20
|
-
return render(<MantineProvider>{ui}</MantineProvider>)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const VEGA_FENCE = '```vega\nspec body\n```'
|
|
24
|
-
/** The fence lands in a SETTLED block: the paragraph after the blank line becomes the tail. */
|
|
25
|
-
const VEGA_FENCE_THEN_TAIL = `${VEGA_FENCE}\n\ntrailing paragraph\n`
|
|
26
|
-
|
|
27
|
-
function vegaRenderers(testId: string) {
|
|
28
|
-
return {
|
|
29
|
-
vega: settledOnly(({ code }: { code: string }) => <div data-testid={testId}>{code}</div>),
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('fence-renderer registry', () => {
|
|
34
|
-
test('a consumer renderer claims its language once settled', async () => {
|
|
35
|
-
renderMarkdown(<Markdown fenceRenderers={vegaRenderers('vega')}>{VEGA_FENCE}</Markdown>)
|
|
36
|
-
|
|
37
|
-
const custom = await screen.findByTestId('vega')
|
|
38
|
-
expect(custom.textContent).toContain('spec body')
|
|
39
|
-
expect(screen.queryByLabelText('Copy code')).toBeNull()
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('settledOnly renders the plain CodeBlock while the fence is the unsettled tail', async () => {
|
|
43
|
-
renderMarkdown(
|
|
44
|
-
<Markdown streaming fenceRenderers={vegaRenderers('vega')}>
|
|
45
|
-
{VEGA_FENCE}
|
|
46
|
-
</Markdown>,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
// The fence is the only block, so it is the in-flight tail — unsettled.
|
|
50
|
-
const code = await screen.findByText('spec body')
|
|
51
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
52
|
-
expect(screen.queryByTestId('vega')).toBeNull()
|
|
53
|
-
// The unsettled half of the OTHER thing `settled` gates: no copy action on a streaming block.
|
|
54
|
-
expect(screen.queryByLabelText('Copy code')).toBeNull()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('a block that has settled keeps its custom renderer as the stream grows', async () => {
|
|
58
|
-
const { rerender } = renderMarkdown(
|
|
59
|
-
<Markdown streaming fenceRenderers={vegaRenderers('vega')}>
|
|
60
|
-
{VEGA_FENCE_THEN_TAIL}
|
|
61
|
-
</Markdown>,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
await screen.findByTestId('vega')
|
|
65
|
-
|
|
66
|
-
// Monotonicity: more text arrives, the tail moves further away — the settled fence must not
|
|
67
|
-
// fall back to a CodeBlock.
|
|
68
|
-
rerender(
|
|
69
|
-
<MantineProvider>
|
|
70
|
-
<Markdown streaming fenceRenderers={vegaRenderers('vega')}>
|
|
71
|
-
{`${VEGA_FENCE_THEN_TAIL}and more\n`}
|
|
72
|
-
</Markdown>
|
|
73
|
-
</MantineProvider>,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
expect(await screen.findByTestId('vega')).toBeDefined()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('a consumer entry overrides the built-in for the same language key', async () => {
|
|
80
|
-
renderMarkdown(
|
|
81
|
-
<Markdown
|
|
82
|
-
fenceRenderers={{ mermaid: ({ code }) => <div data-testid="own-mermaid">{code}</div> }}
|
|
83
|
-
>
|
|
84
|
-
{'```mermaid\ngraph TD\n A --> B\n```'}
|
|
85
|
-
</Markdown>,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
const own = await screen.findByTestId('own-mermaid')
|
|
89
|
-
expect(own.textContent).toContain('graph TD')
|
|
90
|
-
expect(screen.queryByRole('img')).toBeNull()
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
test('the raw fence meta and its parsed title both reach the renderer', async () => {
|
|
94
|
-
renderMarkdown(
|
|
95
|
-
<Markdown
|
|
96
|
-
fenceRenderers={{
|
|
97
|
-
vega: ({ meta, title }) => <div data-testid="meta">{`${meta}|${title}`}</div>,
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
{'```vega title="chart.json"\nspec body\n```'}
|
|
101
|
-
</Markdown>,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
const probe = await screen.findByTestId('meta')
|
|
105
|
-
expect(probe.textContent).toBe('title="chart.json"|chart.json')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test('an unclaimed language still falls through to CodeBlock', async () => {
|
|
109
|
-
renderMarkdown(<Markdown>{'```notalanguage\nplain body\n```'}</Markdown>)
|
|
110
|
-
|
|
111
|
-
const code = await screen.findByText('plain body')
|
|
112
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
113
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* A model-authored fence body (JSON for a `vega-lite` renderer, a payload for a `card` renderer) is
|
|
119
|
-
* routinely malformed — neither declining nor throwing may take the message down, and `CodeBlock` is
|
|
120
|
-
* the always-correct fallback both degrade to. Decline convention: `undefined` declines, `null` is a
|
|
121
|
-
* DIFFERENT deliberate "render nothing" result and is used as-is (`./fence-block`'s `FenceRenderer`
|
|
122
|
-
* doc). A throw is contained two different ways depending on where it happens — see the two
|
|
123
|
-
* `Bomb`-shaped tests below, which is exactly the distinction the module comments draw.
|
|
124
|
-
*/
|
|
125
|
-
describe('fence renderer decline & throw containment', () => {
|
|
126
|
-
test('a renderer that declines (returns undefined) falls back to CodeBlock', async () => {
|
|
127
|
-
renderMarkdown(
|
|
128
|
-
<Markdown fenceRenderers={{ decline: () => undefined }}>
|
|
129
|
-
{'```decline\nplain body\n```'}
|
|
130
|
-
</Markdown>,
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
const code = await screen.findByText('plain body')
|
|
134
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
135
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('a renderer returning null is NOT a decline — it renders nothing, no CodeBlock fallback', async () => {
|
|
139
|
-
renderMarkdown(
|
|
140
|
-
<Markdown fenceRenderers={{ blank: () => null }}>
|
|
141
|
-
{'```blank\nnever shown\n```\n\nafter the fence'}
|
|
142
|
-
</Markdown>,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
// Waits for the real pipeline (the Suspense fallback holds the whole raw text as one node, so
|
|
146
|
-
// this text only appears once react-markdown has actually rendered the document).
|
|
147
|
-
await screen.findByText('after the fence')
|
|
148
|
-
expect(screen.queryByText('never shown')).toBeNull()
|
|
149
|
-
expect(screen.queryByLabelText('Copy code')).toBeNull()
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
test('a renderer that throws SYNCHRONOUSLY inside its own body falls back to CodeBlock', async () => {
|
|
153
|
-
renderMarkdown(
|
|
154
|
-
<Markdown
|
|
155
|
-
fenceRenderers={{
|
|
156
|
-
boom: () => {
|
|
157
|
-
throw new Error('renderer blew up before returning anything')
|
|
158
|
-
},
|
|
159
|
-
}}
|
|
160
|
-
>
|
|
161
|
-
{'```boom\nraw body\n```'}
|
|
162
|
-
</Markdown>,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
// Proves containment, not just non-crash: the SAME assertions the plain-CodeBlock tests above
|
|
166
|
-
// make. If this had propagated, `renderMarkdown` itself would have thrown synchronously.
|
|
167
|
-
const code = await screen.findByText('raw body')
|
|
168
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
169
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
// A component the renderer RETURNS as a descriptor — `<Bomb />` is not invoked by calling the
|
|
173
|
-
// renderer function; React invokes it later, during ITS OWN render pass. A try/catch around the
|
|
174
|
-
// renderer call cannot reach a throw from here — only an error boundary can.
|
|
175
|
-
function Bomb(): never {
|
|
176
|
-
throw new Error('component blew up during its own render')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
test('a renderer that returns a component which throws during ITS OWN render also falls back', async () => {
|
|
180
|
-
renderMarkdown(
|
|
181
|
-
<Markdown fenceRenderers={{ boom: () => <Bomb /> }}>{'```boom\nraw body\n```'}</Markdown>,
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const code = await screen.findByText('raw body')
|
|
185
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
186
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
test('settledOnly propagates a decline through to the default once settled', async () => {
|
|
190
|
-
renderMarkdown(
|
|
191
|
-
<Markdown fenceRenderers={{ decline: settledOnly(() => undefined) }}>
|
|
192
|
-
{'```decline\nplain body\n```'}
|
|
193
|
-
</Markdown>,
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
const code = await screen.findByText('plain body')
|
|
197
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
198
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
test('settledOnly contains a throw from the wrapped renderer once settled', async () => {
|
|
202
|
-
renderMarkdown(
|
|
203
|
-
<Markdown
|
|
204
|
-
fenceRenderers={{
|
|
205
|
-
boom: settledOnly(() => {
|
|
206
|
-
throw new Error('settled renderer blew up')
|
|
207
|
-
}),
|
|
208
|
-
}}
|
|
209
|
-
>
|
|
210
|
-
{'```boom\nraw body\n```'}
|
|
211
|
-
</Markdown>,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
const code = await screen.findByText('raw body')
|
|
215
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
216
|
-
expect(screen.getByLabelText('Copy code')).toBeDefined()
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
test('a renderer returning a valid node still wins normally, decline/throw handling notwithstanding', async () => {
|
|
220
|
-
renderMarkdown(
|
|
221
|
-
<Markdown fenceRenderers={{ vega: ({ code }) => <div data-testid="vega">{code}</div> }}>
|
|
222
|
-
{'```vega\nspec body\n```'}
|
|
223
|
-
</Markdown>,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
const custom = await screen.findByTestId('vega')
|
|
227
|
-
expect(custom.textContent).toContain('spec body')
|
|
228
|
-
expect(screen.queryByLabelText('Copy code')).toBeNull()
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* "Once, not per render": the per-fence `warned` gate lives on a `useRef` inside `FenceBlock`
|
|
233
|
-
* (`./fence-block`'s `WarnedGate`), so it survives ordinary re-renders of the SAME fence position —
|
|
234
|
-
* both `fenceRenderers` and `children` are held referentially stable across the `rerender()` call
|
|
235
|
-
* below on purpose, so react-markdown's `pre` mapping keeps the same component identity across the
|
|
236
|
-
* two render passes instead of remounting (a fresh object literal per render would defeat that).
|
|
237
|
-
*
|
|
238
|
-
* NOT covered here, and disclosed rather than silently assumed: a renderer applied directly to a
|
|
239
|
-
* still-STREAMING tail (no `settledOnly`) is re-rendered from a freshly recreated `FenceBlock`
|
|
240
|
-
* instance on every token — `markdown.tsx`'s `BlockRenderer` intentionally rebuilds its
|
|
241
|
-
* `SlugTracker`/`components` map whenever the block's `text` changes, which is every token for the
|
|
242
|
-
* growing tail — so a fence that stays malformed while still streaming legitimately re-warns per
|
|
243
|
-
* token rather than staying silent. That is a real, disclosed limitation of the per-instance gate,
|
|
244
|
-
* not a claim this test makes.
|
|
245
|
-
*/
|
|
246
|
-
test('the dev warning for a throwing renderer fires once per fence, not once per render', async () => {
|
|
247
|
-
const warnings: unknown[][] = []
|
|
248
|
-
const original = console.warn
|
|
249
|
-
console.warn = (...args: unknown[]) => void warnings.push(args)
|
|
250
|
-
|
|
251
|
-
const stableRenderers = {
|
|
252
|
-
boom: () => {
|
|
253
|
-
throw new Error('renderer blew up')
|
|
254
|
-
},
|
|
255
|
-
}
|
|
256
|
-
const stableChildren = '```boom\nraw body\n```'
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
const { rerender } = renderMarkdown(
|
|
260
|
-
<Markdown fenceRenderers={stableRenderers}>{stableChildren}</Markdown>,
|
|
261
|
-
)
|
|
262
|
-
await screen.findByText('raw body')
|
|
263
|
-
expect(warnings).toHaveLength(1)
|
|
264
|
-
expect(String(warnings[0]?.[0])).toContain('[basalt] FenceBlock')
|
|
265
|
-
|
|
266
|
-
rerender(
|
|
267
|
-
<MantineProvider>
|
|
268
|
-
<Markdown fenceRenderers={stableRenderers}>{stableChildren}</Markdown>
|
|
269
|
-
</MantineProvider>,
|
|
270
|
-
)
|
|
271
|
-
await screen.findByText('raw body')
|
|
272
|
-
expect(warnings).toHaveLength(1)
|
|
273
|
-
} finally {
|
|
274
|
-
console.warn = original
|
|
275
|
-
}
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* The fence info string is MODEL-CONTROLLED — `markdown-hast` reads the language off the
|
|
281
|
-
* `language-*` class, and `defaultSchema.attributes.code` (`[['className', /^language-./]]`) waves
|
|
282
|
-
* any such class through the sanitize pass. Both registries are plain object literals, so an
|
|
283
|
-
* unguarded `map[language]` resolved `Object.prototype` members: ` ```valueOf `, ` ```hasOwnProperty `
|
|
284
|
-
* and ` ```isPrototypeOf ` THREW when called unbound, ` ```__proto__ ` threw on a non-callable, and
|
|
285
|
-
* ` ```toString `/` ```constructor ` rendered garbage in place of the code.
|
|
286
|
-
*/
|
|
287
|
-
describe('fence registry — a prototype member never resolves as a renderer', () => {
|
|
288
|
-
const HOSTILE_LANGUAGES = [
|
|
289
|
-
'valueOf',
|
|
290
|
-
'toString',
|
|
291
|
-
'constructor',
|
|
292
|
-
'__proto__',
|
|
293
|
-
'hasOwnProperty',
|
|
294
|
-
'isPrototypeOf',
|
|
295
|
-
] as const
|
|
296
|
-
|
|
297
|
-
for (const language of HOSTILE_LANGUAGES) {
|
|
298
|
-
test(`\`\`\`${language} falls through to CodeBlock instead of Object.prototype.${language}`, async () => {
|
|
299
|
-
renderMarkdown(<Markdown>{`\`\`\`${language}\nhostile body\n\`\`\``}</Markdown>)
|
|
300
|
-
|
|
301
|
-
// The Suspense fallback holds the WHOLE raw fence as one text node, so an exact-string
|
|
302
|
-
// `findByText` only matches once the real pipeline has rendered the fence.
|
|
303
|
-
const code = await screen.findByText('hostile body')
|
|
304
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
305
|
-
})
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
test('a consumer registry is indexed by own keys only, same as the built-ins', async () => {
|
|
309
|
-
renderMarkdown(
|
|
310
|
-
<Markdown fenceRenderers={vegaRenderers('vega')}>
|
|
311
|
-
{'```toString\nhostile body\n```'}
|
|
312
|
-
</Markdown>,
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
const code = await screen.findByText('hostile body')
|
|
316
|
-
expect(code.closest('pre')).not.toBeNull()
|
|
317
|
-
expect(screen.queryByTestId('vega')).toBeNull()
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// ── Sanitize ──────────────────────────────────────────────────────────────────────────────────
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* A rehype plugin that injects two elements neither `defaultSchema` nor basalt allows: `xUnsafe`
|
|
325
|
-
* (unsafe → unwrapped to its text) and `script` (in `defaultSchema.strip` → dropped whole).
|
|
326
|
-
*
|
|
327
|
-
* react-markdown does not parse raw HTML, so a `<script>` written in the markdown SOURCE never
|
|
328
|
-
* becomes an element in the first place — injecting through a plugin is the only way to put an
|
|
329
|
-
* unsafe node into the tree, and it is also exactly the threat model the sanitize pass exists for.
|
|
330
|
-
*/
|
|
331
|
-
function injectUnsafeElements() {
|
|
332
|
-
return (tree: Root): undefined => {
|
|
333
|
-
const xUnsafe: Element = {
|
|
334
|
-
type: 'element',
|
|
335
|
-
tagName: 'x-unsafe',
|
|
336
|
-
properties: {},
|
|
337
|
-
children: [{ type: 'text', value: 'UNWRAPPED' }],
|
|
338
|
-
}
|
|
339
|
-
const script: Element = {
|
|
340
|
-
type: 'element',
|
|
341
|
-
tagName: 'script',
|
|
342
|
-
properties: {},
|
|
343
|
-
children: [{ type: 'text', value: 'PWNED' }],
|
|
344
|
-
}
|
|
345
|
-
tree.children.push(xUnsafe, script)
|
|
346
|
-
return undefined
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
describe('sanitize pass', () => {
|
|
351
|
-
test('runs AFTER a consumer rehypePlugin — nothing it injects survives', async () => {
|
|
352
|
-
const { container } = renderMarkdown(
|
|
353
|
-
<Markdown rehypePlugins={[injectUnsafeElements]}>paragraph</Markdown>,
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
await screen.findByText('paragraph')
|
|
357
|
-
// `xUnsafe` is not in defaultSchema.tagNames — the element goes, its text is unwrapped.
|
|
358
|
-
expect(container.querySelector('x-unsafe')).toBeNull()
|
|
359
|
-
expect(container.textContent).toContain('UNWRAPPED')
|
|
360
|
-
// `script` is in defaultSchema.strip — element AND contents dropped. Were the sanitize pass
|
|
361
|
-
// registered BEFORE the consumer plugin, both of these would have survived untouched.
|
|
362
|
-
expect(container.querySelector('script')).toBeNull()
|
|
363
|
-
expect(container.textContent).not.toContain('PWNED')
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
test('sanitizeSchema additions reach the pipeline without destroying the defaults', async () => {
|
|
367
|
-
const { container } = renderMarkdown(
|
|
368
|
-
<Markdown rehypePlugins={[injectUnsafeElements]} sanitizeSchema={{ tagNames: ['x-unsafe'] }}>
|
|
369
|
-
paragraph
|
|
370
|
-
</Markdown>,
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
await screen.findByText('paragraph')
|
|
374
|
-
// The addition took effect...
|
|
375
|
-
expect(container.querySelector('x-unsafe')).not.toBeNull()
|
|
376
|
-
// ...and `defaultSchema.strip` survived the merge. A shallow top-level spread (what
|
|
377
|
-
// hast-util-sanitize does with a raw schema) would have wiped `strip` and let this through.
|
|
378
|
-
expect(container.textContent).not.toContain('PWNED')
|
|
379
|
-
})
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* The always-on sanitize pass must not break GFM footnotes. TWO layers prefix ids and only one of
|
|
384
|
-
* them touches `href`: `mdast-util-to-hast` namespaces both sides with `'user-content-'`, then
|
|
385
|
-
* `hast-util-sanitize` prefixes again — but `href` is not in `defaultSchema.clobber`, so
|
|
386
|
-
* `id="user-content-user-content-fn-1"` ended up next to `href="#user-content-fn-1"` and every
|
|
387
|
-
* footnote link dangled. `markdown.tsx`'s `FOOTNOTE_CLOBBER_FIX` turns the sanitizer's layer off
|
|
388
|
-
* and leaves the to-hast one, which covers id and href alike.
|
|
389
|
-
*/
|
|
390
|
-
describe('GFM footnotes survive the sanitize pass', () => {
|
|
391
|
-
const FOOTNOTE_DOC = 'A claim.[^1] And another.[^note]\n\n[^1]: The evidence.\n\n[^note]: More.\n'
|
|
392
|
-
|
|
393
|
-
test('every in-document link resolves to an element that is actually rendered', async () => {
|
|
394
|
-
const { container } = renderMarkdown(<Markdown>{FOOTNOTE_DOC}</Markdown>)
|
|
395
|
-
|
|
396
|
-
// The Suspense fallback is a plain text span with no links, so this waits for the real render.
|
|
397
|
-
const links = await screen.findAllByRole('link')
|
|
398
|
-
const hashHrefs = links
|
|
399
|
-
.map((link) => link.getAttribute('href') ?? '')
|
|
400
|
-
.filter((href) => href.startsWith('#'))
|
|
401
|
-
|
|
402
|
-
// Two refs + two back-refs.
|
|
403
|
-
expect(hashHrefs.length).toBe(4)
|
|
404
|
-
for (const href of hashHrefs) {
|
|
405
|
-
expect(container.querySelector(`[id="${href.slice(1)}"]`)).not.toBeNull()
|
|
406
|
-
}
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
test('a footnote ref keeps the id its own back-link points at', async () => {
|
|
410
|
-
const { container } = renderMarkdown(<Markdown>{FOOTNOTE_DOC}</Markdown>)
|
|
411
|
-
|
|
412
|
-
await screen.findAllByRole('link')
|
|
413
|
-
// The OTHER half of the round trip, and a separate bug from the double-prefix: `LinkRenderer`
|
|
414
|
-
// used to rebuild the anchor from `href` alone, discarding the `id` that the back-link in the
|
|
415
|
-
// footnote body targets.
|
|
416
|
-
expect(container.querySelector('a[id="user-content-fnref-1"]')).not.toBeNull()
|
|
417
|
-
expect(container.querySelector('a[id="user-content-fnref-note"]')).not.toBeNull()
|
|
418
|
-
})
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* `contentTrust` — NOT `streaming` — selects the image allowlist. Auto-fetched images are the
|
|
423
|
-
* classic prompt-injection exfiltration channel (``), and a
|
|
424
|
-
* finished agent message is settled but still model-generated: keying the policy off "is this run
|
|
425
|
-
* in flight" reopened the channel for every completed message.
|
|
426
|
-
*/
|
|
427
|
-
describe('image trust policy', () => {
|
|
428
|
-
const IMAGE_DOC = '\n\n'
|
|
429
|
-
|
|
430
|
-
async function renderImages(ui: React.ReactElement) {
|
|
431
|
-
const { container } = renderMarkdown(ui)
|
|
432
|
-
// `findByAltText` only ever matches an <img>, so the Suspense text fallback cannot satisfy it.
|
|
433
|
-
await screen.findByAltText('local')
|
|
434
|
-
return container
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
test('trusted content (the default) keeps the open https allowlist', async () => {
|
|
438
|
-
const container = await renderImages(<Markdown>{IMAGE_DOC}</Markdown>)
|
|
439
|
-
|
|
440
|
-
expect(screen.getByAltText('probe').getAttribute('src')).toBe(
|
|
441
|
-
'https://cdn.example/pixel.png?q=leak',
|
|
442
|
-
)
|
|
443
|
-
expect(container.innerHTML).toContain('cdn.example')
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
test('untrusted content blocks an off-origin image even though it is SETTLED', async () => {
|
|
447
|
-
const container = await renderImages(<Markdown contentTrust="untrusted">{IMAGE_DOC}</Markdown>)
|
|
448
|
-
|
|
449
|
-
// Same-origin still renders — this is an allowlist, not a blanket image kill.
|
|
450
|
-
expect(screen.getByAltText('local').getAttribute('src')).toBe('/assets/local.png')
|
|
451
|
-
// The off-origin one is dropped whole: no <img>, and the URL never reaches the DOM.
|
|
452
|
-
expect(screen.queryByAltText('probe')).toBeNull()
|
|
453
|
-
expect(container.querySelector('img[src^="https://"]')).toBeNull()
|
|
454
|
-
expect(container.innerHTML).not.toContain('cdn.example')
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
test('streaming alone still defaults to untrusted (the pre-1.12 fail-safe)', async () => {
|
|
458
|
-
const container = await renderImages(<Markdown streaming>{IMAGE_DOC}</Markdown>)
|
|
459
|
-
|
|
460
|
-
expect(container.innerHTML).not.toContain('cdn.example')
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
test('an explicit allowedImagePrefixes opts an origin back in over the untrusted default', async () => {
|
|
464
|
-
const container = await renderImages(
|
|
465
|
-
<Markdown contentTrust="untrusted" allowedImagePrefixes={['https://cdn.example/', '/']}>
|
|
466
|
-
{IMAGE_DOC}
|
|
467
|
-
</Markdown>,
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
expect(container.innerHTML).toContain('cdn.example')
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* `contentTrust` is typed `MarkdownContentTrust`, but that is compile-time only — a plain-JS
|
|
475
|
-
* consumer or an `as unknown as MarkdownContentTrust` cast (used below, same idiom as
|
|
476
|
-
* `tool-chip.test.tsx`'s hostile-input casts) can hand this ANY string. `IMAGE_PREFIXES_BY_TRUST`
|
|
477
|
-
* is a plain object literal, so an unguarded lookup resolves `Object.prototype` members for a key
|
|
478
|
-
* like `'constructor'`/`'toString'`, or `undefined` for a plain unrecognised key.
|
|
479
|
-
*
|
|
480
|
-
* EXECUTED against the pre-fix code, NEITHER case "fails closed" on its own: `undefined` and a
|
|
481
|
-
* `Function.prototype` member both reach `createUrlTransform`'s `isAllowedUrl`, which
|
|
482
|
-
* unconditionally calls `prefixes.some(...)` — that CRASHES the render with an uncaught
|
|
483
|
-
* `TypeError` the instant an `<img>` is transformed (nothing between `Markdown` and
|
|
484
|
-
* react-markdown's `img` visit catches it), rather than degrading to plain text with the image
|
|
485
|
-
* dropped. Same defect shape, same fix, as the fence registry's `lookupFenceRenderer`
|
|
486
|
-
* (`./fence-block`) — restrict the lookup to OWN keys and fail closed to the MOST RESTRICTIVE
|
|
487
|
-
* policy for anything else, proven here by the render completing (not crashing) AND still
|
|
488
|
-
* blocking the off-origin image.
|
|
489
|
-
*/
|
|
490
|
-
describe('an unrecognised or prototype-key contentTrust fails closed, not open or crashed', () => {
|
|
491
|
-
const HOSTILE_CONTENT_TRUST_VALUES = [
|
|
492
|
-
'nonsense',
|
|
493
|
-
'toString',
|
|
494
|
-
'constructor',
|
|
495
|
-
'__proto__',
|
|
496
|
-
] as const
|
|
497
|
-
|
|
498
|
-
for (const hostile of HOSTILE_CONTENT_TRUST_VALUES) {
|
|
499
|
-
test(`contentTrust="${hostile}"`, async () => {
|
|
500
|
-
const container = await renderImages(
|
|
501
|
-
<Markdown contentTrust={hostile as unknown as MarkdownContentTrust}>
|
|
502
|
-
{IMAGE_DOC}
|
|
503
|
-
</Markdown>,
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
// Same-origin still renders — the restrictive policy is an allowlist, not a blanket kill.
|
|
507
|
-
expect(screen.getByAltText('local').getAttribute('src')).toBe('/assets/local.png')
|
|
508
|
-
// The off-origin image is dropped whole, exactly like the explicit 'untrusted' case above.
|
|
509
|
-
expect(screen.queryByAltText('probe')).toBeNull()
|
|
510
|
-
expect(container.querySelector('img[src^="https://"]')).toBeNull()
|
|
511
|
-
expect(container.innerHTML).not.toContain('cdn.example')
|
|
512
|
-
})
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
})
|
|
516
|
-
|
|
517
|
-
describe('composeRehypePlugins', () => {
|
|
518
|
-
const FAKE_PLUGIN = () => undefined
|
|
519
|
-
const sanitizer = {
|
|
520
|
-
plugin: FAKE_PLUGIN,
|
|
521
|
-
baseSchema: { tagNames: ['p'], strip: ['script'] },
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
test('appends [plugin, schema] LAST, after every consumer plugin', () => {
|
|
525
|
-
const first = () => undefined
|
|
526
|
-
const second = () => undefined
|
|
527
|
-
|
|
528
|
-
const composed = composeRehypePlugins({
|
|
529
|
-
sanitizer,
|
|
530
|
-
consumerPlugins: [first, second],
|
|
531
|
-
sanitizeSchema: { tagNames: ['x-unsafe'] },
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
expect(composed.length).toBe(3)
|
|
535
|
-
expect(composed[0]).toBe(first)
|
|
536
|
-
expect(composed[1]).toBe(second)
|
|
537
|
-
|
|
538
|
-
const tail = composed[2] as [unknown, { tagNames: string[]; strip: string[] }]
|
|
539
|
-
expect(tail[0]).toBe(FAKE_PLUGIN)
|
|
540
|
-
expect(tail[1].tagNames).toContain('p')
|
|
541
|
-
expect(tail[1].tagNames).toContain('x-unsafe')
|
|
542
|
-
expect(tail[1].strip).toContain('script')
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
test('an absent rehype-sanitize peer leaves the consumer list untouched', () => {
|
|
546
|
-
const only = () => undefined
|
|
547
|
-
expect(composeRehypePlugins({ sanitizer: null, consumerPlugins: [only] })).toEqual([only])
|
|
548
|
-
expect(composeRehypePlugins({ sanitizer: null })).toEqual([])
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
test('the composed schema leaves id-namespacing to mdast-util-to-hast', () => {
|
|
552
|
-
// The pipeline-level half of the footnote fix, pinned at the unit level. `mergeSanitizeSchema`
|
|
553
|
-
// deliberately IGNORES an empty `clobberPrefix` coming from a consumer extension (that is the
|
|
554
|
-
// additions-only channel refusing to let outside code disable a protection), so the decision
|
|
555
|
-
// is made past the merge — and a consumer cannot re-enable double-prefixing by accident.
|
|
556
|
-
const composed = composeRehypePlugins({
|
|
557
|
-
sanitizer,
|
|
558
|
-
sanitizeSchema: { clobberPrefix: 'user-content-' },
|
|
559
|
-
})
|
|
560
|
-
const tail = composed[0] as [unknown, { clobberPrefix: string }]
|
|
561
|
-
expect(tail[1].clobberPrefix).toBe('')
|
|
562
|
-
})
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
// ── remend ────────────────────────────────────────────────────────────────────────────────────
|
|
566
|
-
|
|
567
|
-
describe('lazy remend', () => {
|
|
568
|
-
test('the streaming tail is repaired once the peer resolves', async () => {
|
|
569
|
-
const { container } = renderMarkdown(<Markdown streaming>{'a **bold'}</Markdown>)
|
|
570
|
-
|
|
571
|
-
// NOT `findByText(/bold/)`: the Suspense fallback renders the full raw text, so that matches on
|
|
572
|
-
// the FIRST paint and the assertion below then only passes when an earlier test in the file
|
|
573
|
-
// happened to warm the `resolvedRemend` singleton first. Waiting on `<strong>` waits for the
|
|
574
|
-
// thing under test — remend closing the dangling `**` — and holds in isolation.
|
|
575
|
-
await waitFor(() => {
|
|
576
|
-
expect(container.querySelector('strong')).not.toBeNull()
|
|
577
|
-
})
|
|
578
|
-
expect(container.textContent).toContain('bold')
|
|
579
|
-
// remend removed the markers rather than leaving them as literal text.
|
|
580
|
-
expect(container.textContent).not.toContain('**')
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
test('the streaming tail always shows its text — through BOTH repair states, never blank', async () => {
|
|
584
|
-
// The `repair === null` branch (`StreamingTailBlock`, the state a permanently-absent peer stays
|
|
585
|
-
// in forever) CANNOT be isolated in this process: `remend` is installed here, `resolvedRemend`
|
|
586
|
-
// is a module-level singleton any earlier streaming test warms, and a `mock.module` would
|
|
587
|
-
// poison it for every later test. So this asserts the invariant that actually holds in both
|
|
588
|
-
// states — the tail renders its text, in one spelling or the other, and never throws or blanks
|
|
589
|
-
// — first synchronously on the initial paint, then again after the pipeline has settled.
|
|
590
|
-
// It deliberately claims no more than that; the repair itself is the test above.
|
|
591
|
-
const { container } = renderMarkdown(<Markdown streaming>{'a **bold'}</Markdown>)
|
|
592
|
-
|
|
593
|
-
expect(container.textContent).toMatch(/a \*{0,2}bold/)
|
|
594
|
-
|
|
595
|
-
// Settle on `<p>` — react-markdown's own output — NOT on `<strong>`: waiting for the repair
|
|
596
|
-
// would make this a duplicate of the test above rather than an independent invariant. The
|
|
597
|
-
// Suspense fallback is a bare `<span>`, so this still waits for the real pipeline.
|
|
598
|
-
await waitFor(() => {
|
|
599
|
-
expect(container.querySelector('p')).not.toBeNull()
|
|
600
|
-
})
|
|
601
|
-
expect(container.textContent).toMatch(/a \*{0,2}bold/)
|
|
602
|
-
})
|
|
603
|
-
})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression test for the responsive sticky-header-clearance split (Decision 3,
|
|
3
|
-
* `deriveSpacing`'s JSDoc in `tokens/palette.ts`, third bullet) — the CSS side of it has no other
|
|
4
|
-
* coverage: `theme/spacing.test.ts`/`density-relations.test.ts` lock the two `--vx-space-sticky-
|
|
5
|
-
* header-clearance*` NUMBERS, but nothing asserted that `prose.module.css` actually wires the
|
|
6
|
-
* DESKTOP var to the unconditional rule and the MOBILE var to the `max-width` override — an
|
|
7
|
-
* inverted media query, a swap of the two vars, or a deleted `@media` block would all pass the rest
|
|
8
|
-
* of the suite silently. Asserted directly against the shipped CSS text, same pattern as
|
|
9
|
-
* `styles.floor.test.ts`.
|
|
10
|
-
*/
|
|
11
|
-
import { readFileSync } from 'node:fs'
|
|
12
|
-
import { dirname, resolve } from 'node:path'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
import { describe, expect, it } from 'bun:test'
|
|
15
|
-
|
|
16
|
-
const PROSE_CSS_PATH = resolve(dirname(fileURLToPath(import.meta.url)), 'prose.module.css')
|
|
17
|
-
|
|
18
|
-
describe('prose.module.css — sticky-header clearance (one header height, plus the PageBar row)', () => {
|
|
19
|
-
const css = readFileSync(PROSE_CSS_PATH, 'utf8')
|
|
20
|
-
|
|
21
|
-
// The first (unconditional) `.root h2, .root h3, .root h4 { scroll-margin-top: … }` rule in the
|
|
22
|
-
// file — `.match` without the `g` flag returns the first match, which is this one since it
|
|
23
|
-
// precedes the `@media` override in source.
|
|
24
|
-
const baseRuleMatch = css.match(
|
|
25
|
-
/\.root h2,\s*\.root h3,\s*\.root h4\s*\{\s*scroll-margin-top:\s*([^;]+);\s*\}/,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
it('clears the header plus the sticky PageBar row on every viewport (one header height since C14)', () => {
|
|
29
|
-
expect(baseRuleMatch).not.toBeNull()
|
|
30
|
-
expect(baseRuleMatch?.[1]).toBe(
|
|
31
|
-
'calc(var(--vx-space-sticky-header-clearance) + var(--basalt-page-bar-h, 0px))',
|
|
32
|
-
)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('has no mobile override — the -mobile clearance token no longer exists', () => {
|
|
36
|
-
expect(css).not.toContain('clearance-mobile')
|
|
37
|
-
expect(css).not.toContain('@media (max-width: 47.99375em)')
|
|
38
|
-
})
|
|
39
|
-
})
|