basalt-ui 1.40.3 → 2.0.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 +50 -45
- package/CHANGELOG.md +543 -513
- package/MIGRATING.md +969 -37
- package/README.md +172 -367
- package/agent/rules/basalt-batteries.md +19 -74
- package/agent/rules/basalt-charts.md +21 -95
- package/agent/rules/basalt-controls.md +23 -131
- package/agent/rules/basalt-mantine.md +26 -99
- package/agent/rules/basalt-state.md +21 -103
- package/agent/rules/basalt-tokens.md +33 -114
- package/agent/skills/basalt-app/SKILL.md +229 -72
- package/agent/skills/basalt-charts/SKILL.md +23 -57
- package/agent/skills/basalt-design/SKILL.md +12 -70
- package/agent/templates/AGENTS-block.md.tpl +30 -0
- package/agent/templates/DESIGN.md.tpl +2 -2
- package/agent/templates/__root.tpl +9 -16
- package/agent/templates/main.tpl +21 -0
- package/agent/templates/nav.tpl +18 -0
- package/configs/check.yml +1 -1
- package/configs/oxlint-basalt.json +4 -10
- package/configs/oxlint-plugin.js +468 -847
- package/configs/oxlint-plugin.test.ts +662 -710
- package/configs/oxlint.json +10 -2
- package/dist/agent/ai-sdk-transport.d.ts +19 -7
- package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
- package/dist/agent/ai-sdk-transport.js +10 -7
- package/dist/{agent-chat → agent/chat}/composer.d.ts +16 -16
- package/dist/agent/chat/composer.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/composer.js +10 -9
- package/dist/{agent-chat → agent/chat}/message-affordances.d.ts +7 -6
- package/dist/agent/chat/message-affordances.d.ts.map +1 -0
- package/dist/agent/chat/relative-time.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/thread-feed-row.d.ts +18 -16
- package/dist/agent/chat/thread-feed-row.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/thread-feed-row.js +12 -11
- package/dist/{agent-chat → agent/chat}/thread-feed.d.ts +8 -8
- package/dist/agent/chat/thread-feed.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/thread-feed.js +12 -10
- package/dist/{agent-chat → agent/chat}/thread-message.d.ts +12 -9
- package/dist/agent/chat/thread-message.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/thread-message.js +15 -13
- package/dist/{agent-chat → agent/chat}/thread-outcome-card.d.ts +2 -2
- package/dist/agent/chat/thread-outcome-card.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/thread-outcome-card.js +3 -3
- package/dist/{agent-chat → agent/chat}/tool-chip.d.ts +5 -5
- package/dist/agent/chat/tool-chip.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/tool-chip.js +4 -4
- package/dist/agent/chat/use-keyboard-inset.d.ts.map +1 -0
- package/dist/{agent-chat → agent/chat}/use-keyboard-inset.js +1 -1
- package/dist/{agent-chat → agent/chat}/virtualize.d.ts +7 -7
- package/dist/agent/chat/virtualize.d.ts.map +1 -0
- package/dist/agent/error.d.ts +31 -0
- package/dist/agent/error.d.ts.map +1 -0
- package/dist/agent/error.js +18 -0
- package/dist/agent/foreign.d.ts +3 -2
- package/dist/agent/foreign.d.ts.map +1 -1
- package/dist/agent/history.d.ts +2 -36
- package/dist/agent/history.d.ts.map +1 -1
- package/dist/agent/history.js +0 -32
- package/dist/agent/id.d.ts +4 -5
- package/dist/agent/id.d.ts.map +1 -1
- package/dist/agent/index.d.ts +19 -52
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +10 -32
- package/dist/agent/outcome.d.ts +3 -14
- package/dist/agent/outcome.d.ts.map +1 -1
- package/dist/agent/outcome.js +0 -30
- package/dist/agent/part-list.d.ts +3 -3
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/parts.d.ts +23 -24
- package/dist/agent/parts.d.ts.map +1 -1
- package/dist/agent/run-committer.d.ts +23 -0
- package/dist/agent/run-committer.d.ts.map +1 -0
- package/dist/agent/run-committer.js +41 -0
- package/dist/agent/run-records.d.ts +77 -0
- package/dist/agent/run-records.d.ts.map +1 -0
- package/dist/agent/run-records.js +69 -0
- package/dist/agent/thread.d.ts +23 -47
- package/dist/agent/thread.d.ts.map +1 -1
- package/dist/agent/thread.js +3 -134
- package/dist/agent/transport.d.ts +6 -69
- package/dist/agent/transport.d.ts.map +1 -1
- package/dist/agent/transport.js +0 -18
- package/dist/agent/use-agent-thread-runs.d.ts +63 -26
- package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.js +202 -66
- package/dist/charts/BandStrip.d.ts +46 -0
- package/dist/charts/BandStrip.d.ts.map +1 -0
- package/dist/charts/BandStrip.js +151 -0
- package/dist/charts/Bars.d.ts +19 -0
- package/dist/charts/Bars.d.ts.map +1 -0
- package/dist/charts/Bars.js +77 -0
- package/dist/charts/CartesianChart.d.ts +57 -0
- package/dist/charts/CartesianChart.d.ts.map +1 -0
- package/dist/charts/CartesianChart.js +121 -0
- package/dist/charts/ChartCard.d.ts +34 -0
- package/dist/charts/ChartCard.d.ts.map +1 -0
- package/dist/charts/ChartCard.js +115 -0
- package/dist/charts/Donut.d.ts +25 -0
- package/dist/charts/Donut.d.ts.map +1 -0
- package/dist/charts/Donut.js +191 -0
- package/dist/charts/DualPanel.d.ts +47 -0
- package/dist/charts/DualPanel.d.ts.map +1 -0
- package/dist/charts/DualPanel.js +214 -0
- package/dist/charts/Heatmap.d.ts +41 -0
- package/dist/charts/Heatmap.d.ts.map +1 -0
- package/dist/charts/Heatmap.js +216 -0
- package/dist/charts/Lines.d.ts +11 -0
- package/dist/charts/Lines.d.ts.map +1 -0
- package/dist/charts/Lines.js +114 -0
- package/dist/charts/MirroredBars.d.ts +44 -0
- package/dist/charts/MirroredBars.d.ts.map +1 -0
- package/dist/charts/MirroredBars.js +190 -0
- package/dist/charts/Sparkline.d.ts +22 -0
- package/dist/charts/Sparkline.d.ts.map +1 -0
- package/dist/charts/Sparkline.js +129 -0
- package/dist/charts/StackedArea.d.ts +15 -0
- package/dist/charts/StackedArea.d.ts.map +1 -0
- package/dist/charts/StackedArea.js +46 -0
- package/dist/charts/box.d.ts +38 -0
- package/dist/charts/box.d.ts.map +1 -0
- package/dist/charts/box.js +72 -0
- package/dist/charts/card-context.d.ts +3 -0
- package/dist/charts/card-context.d.ts.map +1 -0
- package/dist/charts/card-context.js +7 -0
- package/dist/charts/chart-box.module.css +64 -0
- package/{src/content/article-card.module.css.d.ts → dist/charts/chart-box.module.css.d.ts} +5 -9
- package/dist/charts/chart-card.module.css +25 -0
- package/dist/{dashboard/widget-grid.module.css.d.ts → charts/chart-card.module.css.d.ts} +1 -3
- package/dist/charts/chart-legend.module.css +38 -0
- package/{src/dashboard/widget-grid.module.css.d.ts → dist/charts/chart-legend.module.css.d.ts} +3 -3
- package/dist/charts/compile.d.ts +62 -0
- package/dist/charts/compile.d.ts.map +1 -0
- package/dist/charts/compile.js +386 -0
- package/dist/charts/cursor.d.ts +50 -0
- package/dist/charts/cursor.d.ts.map +1 -0
- package/dist/charts/cursor.js +157 -0
- package/dist/charts/curves.d.ts +12 -0
- package/dist/charts/curves.d.ts.map +1 -0
- package/dist/charts/curves.js +86 -0
- package/dist/charts/donut.module.css +54 -0
- package/dist/{content/article-card.module.css.d.ts → charts/donut.module.css.d.ts} +2 -9
- package/dist/charts/host.d.ts +17 -0
- package/dist/charts/host.d.ts.map +1 -0
- package/dist/charts/host.js +35 -0
- package/dist/charts/index.d.ts +18 -53
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +17 -189
- package/dist/charts/legend.d.ts +23 -0
- package/dist/charts/legend.d.ts.map +1 -0
- package/dist/charts/legend.js +160 -0
- package/dist/charts/marks.d.ts +25 -0
- package/dist/charts/marks.d.ts.map +1 -0
- package/dist/charts/marks.js +128 -0
- package/dist/charts/panel.d.ts +56 -0
- package/dist/charts/panel.d.ts.map +1 -0
- package/dist/charts/panel.js +123 -0
- package/dist/charts/scales.d.ts +44 -0
- package/dist/charts/scales.d.ts.map +1 -0
- package/dist/charts/scales.js +163 -0
- package/dist/charts/sizing.d.ts +12 -0
- package/dist/charts/sizing.d.ts.map +1 -0
- package/dist/charts/sizing.js +33 -0
- package/dist/charts/state.d.ts +17 -0
- package/dist/charts/state.d.ts.map +1 -0
- package/dist/charts/state.js +53 -0
- package/dist/charts/theme.d.ts +38 -19
- package/dist/charts/theme.d.ts.map +1 -1
- package/dist/charts/theme.js +48 -26
- package/dist/charts/types.d.ts +227 -0
- package/dist/charts/types.d.ts.map +1 -0
- package/dist/charts/types.js +4 -0
- package/dist/cli/audit-allows.d.ts +4 -17
- package/dist/cli/audit-allows.d.ts.map +1 -1
- package/dist/cli/config.d.ts +14 -43
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/doctor-upgrade.d.ts +31 -0
- package/dist/cli/doctor-upgrade.d.ts.map +1 -0
- package/dist/cli/doctor-upgrade.js +269 -0
- package/dist/cli/doctor.d.ts +13 -26
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +79 -8
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +2 -4
- package/dist/cli/index.d.ts +68 -135
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +89 -76
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +43 -27
- package/dist/cli/sync.d.ts +5 -21
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +4 -6
- package/dist/cli/tokens-css.d.ts +7 -28
- package/dist/cli/tokens-css.d.ts.map +1 -1
- package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
- package/dist/commands/ShortcutsHelp.js +4 -5
- package/dist/commands/define-commands.d.ts +15 -31
- package/dist/commands/define-commands.d.ts.map +1 -1
- package/dist/commands/define-commands.js +1 -11
- package/dist/commands/define-overlays.d.ts +5 -19
- package/dist/commands/define-overlays.d.ts.map +1 -1
- package/dist/commands/define-overlays.js +3 -11
- package/dist/commands/index.d.ts +10 -40
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +9 -46
- package/dist/{notifications → commands/notifications}/bell.d.ts +1 -1
- package/dist/commands/notifications/bell.d.ts.map +1 -0
- package/dist/{notifications → commands/notifications}/center.d.ts +2 -2
- package/dist/commands/notifications/center.d.ts.map +1 -0
- package/dist/{notifications → commands/notifications}/center.js +2 -2
- package/dist/{notifications → commands/notifications}/define-notifications.d.ts +3 -3
- package/dist/commands/notifications/define-notifications.d.ts.map +1 -0
- package/dist/commands/notifications/notify.d.ts +44 -0
- package/dist/commands/notifications/notify.d.ts.map +1 -0
- package/dist/commands/notifications/notify.js +79 -0
- package/dist/{notifications → commands/notifications}/store.d.ts +2 -0
- package/dist/commands/notifications/store.d.ts.map +1 -0
- package/dist/{notifications → commands/notifications}/store.js +4 -0
- package/dist/commands/projectors.d.ts +1 -131
- package/dist/commands/projectors.d.ts.map +1 -1
- package/dist/commands/projectors.js +2 -73
- package/dist/commands/shell-bridge.d.ts +3 -3
- package/dist/commands/shortcut-format.d.ts +5 -4
- package/dist/commands/shortcut-format.d.ts.map +1 -1
- package/dist/common/errors.d.ts +3 -3
- package/dist/common/use-measured-widths.d.ts.map +1 -1
- package/dist/common/use-measured-widths.js +5 -3
- package/dist/common/validate.d.ts +2 -2
- package/dist/content/article-layout.d.ts.map +1 -1
- package/dist/content/article-layout.js +3 -3
- package/dist/content/article-model.d.ts +1 -46
- package/dist/content/article-model.d.ts.map +1 -1
- package/dist/content/article-model.js +1 -29
- package/dist/content/copy-action.d.ts +1 -1
- package/dist/content/glyphs.d.ts +1 -1
- package/dist/content/index.d.ts +11 -41
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +3 -35
- package/dist/content/markdown.d.ts +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +1 -1
- package/dist/content/mermaid.d.ts.map +1 -1
- package/dist/content/mermaid.js +7 -7
- package/dist/content/slug.d.ts +1 -8
- package/dist/content/slug.d.ts.map +1 -1
- package/dist/content/slug.js +1 -8
- package/dist/content/url-hardening.d.ts +2 -2
- package/dist/content/url-hardening.d.ts.map +1 -1
- package/dist/content/url-hardening.js +2 -0
- package/dist/controls/actions.d.ts +16 -1
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/enum-filter.d.ts +1 -1
- package/dist/controls/enum-filter.d.ts.map +1 -1
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-context.js +3 -1
- package/dist/controls/filter-set.js +4 -6
- package/dist/controls/form-layout.d.ts +1 -1
- package/dist/controls/form-layout.d.ts.map +1 -1
- package/dist/controls/form-layout.js +1 -1
- package/dist/controls/glyphs.d.ts +0 -2
- package/dist/controls/glyphs.d.ts.map +1 -1
- package/dist/controls/glyphs.js +0 -7
- package/dist/controls/index.d.ts +12 -17
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +5 -39
- package/dist/controls/multi-select-filter.d.ts +4 -3
- package/dist/controls/multi-select-filter.d.ts.map +1 -1
- package/dist/controls/number-filter.d.ts +4 -3
- package/dist/controls/number-filter.d.ts.map +1 -1
- package/dist/controls/number-filter.js +3 -1
- package/dist/controls/range-filter.d.ts +4 -3
- package/dist/controls/range-filter.d.ts.map +1 -1
- package/dist/controls/search-filter.d.ts +4 -3
- package/dist/controls/search-filter.d.ts.map +1 -1
- package/dist/controls/search-filter.js +19 -14
- package/dist/controls/select-filter.d.ts +4 -3
- package/dist/controls/select-filter.d.ts.map +1 -1
- package/dist/controls/sync-button.d.ts.map +1 -1
- package/dist/controls/sync-button.js +12 -15
- package/dist/controls/toggle-filter.d.ts +4 -3
- package/dist/controls/toggle-filter.d.ts.map +1 -1
- package/dist/controls/uncontrolled.d.ts +12 -0
- package/dist/controls/uncontrolled.d.ts.map +1 -0
- package/dist/controls/view-tabs.d.ts +2 -2
- package/dist/controls/view-tabs.d.ts.map +1 -1
- package/dist/controls-dates/index.d.ts +0 -1
- package/dist/controls-dates/index.d.ts.map +1 -1
- package/dist/dashboard/index.d.ts +7 -9
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -23
- package/dist/dashboard/section.d.ts +4 -6
- package/dist/dashboard/section.d.ts.map +1 -1
- package/dist/dashboard/section.js +4 -10
- package/dist/dashboard/stat-card.d.ts +4 -4
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +3 -3
- package/dist/dashboard/widget-header.module.css +9 -7
- package/dist/dev-dock.d.ts.map +1 -1
- package/dist/dev-dock.js +6 -1
- package/dist/format/date.d.ts +0 -34
- package/dist/format/date.d.ts.map +1 -1
- package/dist/format/date.js +0 -54
- package/dist/format/index.d.ts +7 -22
- package/dist/format/index.d.ts.map +1 -1
- package/dist/format/index.js +2 -43
- package/dist/format/number.d.ts +3 -14
- package/dist/format/number.d.ts.map +1 -1
- package/dist/format/number.js +0 -13
- package/dist/format/shared.d.ts +2 -3
- package/dist/format/shared.d.ts.map +1 -1
- package/dist/forms/index.d.ts +0 -11
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +1 -14
- package/dist/guard/index.d.ts +6 -21
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +27 -115
- package/dist/guard/types.d.ts +7 -18
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +16 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -117
- package/dist/provider/create-router.d.ts +44 -0
- package/dist/provider/create-router.d.ts.map +1 -0
- package/dist/provider/create-router.js +62 -0
- package/dist/provider/hotkeys.d.ts +8 -0
- package/dist/provider/hotkeys.d.ts.map +1 -0
- package/dist/provider/hotkeys.js +50 -0
- package/dist/provider/index.d.ts +33 -110
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +182 -82
- package/dist/provider/lab-theme.d.ts.map +1 -1
- package/dist/provider/route-transition.d.ts +76 -0
- package/dist/provider/route-transition.d.ts.map +1 -0
- package/dist/provider/route-transition.js +98 -0
- package/dist/provider/spotlight-actions.d.ts +10 -0
- package/dist/provider/spotlight-actions.d.ts.map +1 -0
- package/dist/provider/spotlight-actions.js +55 -0
- package/dist/provider/test-router.d.ts +6 -0
- package/dist/provider/test-router.d.ts.map +1 -0
- package/dist/provider/test-router.js +18 -0
- package/dist/query-client.d.ts +4 -20
- package/dist/query-client.d.ts.map +1 -1
- package/dist/query-client.js +2 -2
- package/dist/router-tanstack/index.d.ts +18 -64
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/index.js +2 -16
- package/dist/router-tanstack/nav.d.ts +12 -1
- package/dist/router-tanstack/nav.d.ts.map +1 -1
- package/dist/router-tanstack/nav.js +8 -1
- package/dist/router-tanstack/search-store.d.ts +1 -1
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.d.ts +8 -9
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +20 -1
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +1 -1
- package/dist/shell/index.d.ts +31 -10
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +14 -3
- package/dist/shell/page-aside.d.ts +2 -2
- package/dist/shell/page-aside.d.ts.map +1 -1
- package/dist/shell/page-aside.js +31 -12
- package/dist/shell/page-bar.d.ts +2 -2
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +1 -1
- package/dist/state.d.ts +10 -12
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -6
- package/dist/styles.css +107 -7
- package/dist/surfaces.d.ts +28 -103
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +54 -143
- package/dist/{data → table}/data-table-state.d.ts +1 -1
- package/dist/table/data-table-state.d.ts.map +1 -0
- package/dist/{data → table}/data-table-state.js +4 -3
- package/dist/{data → table}/data-table.d.ts +28 -49
- package/dist/table/data-table.d.ts.map +1 -0
- package/dist/{data → table}/data-table.js +74 -60
- package/dist/table/index.d.ts +18 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/{data/table.js → table/index.js} +1 -1
- package/dist/table/query-branch.d.ts.map +1 -0
- package/dist/table/table-features.d.ts +69 -0
- package/dist/table/table-features.d.ts.map +1 -0
- package/dist/table/table-features.js +49 -0
- package/dist/telemetry.d.ts +77 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +33 -0
- package/dist/theme/index.d.ts +10 -21
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +3 -14
- package/dist/theme/theme-toggle.d.ts.map +1 -1
- package/dist/theme/theme-toggle.js +7 -33
- package/dist/theme/theme-toggle.module.css +30 -0
- package/dist/theme/theme-toggle.module.css.d.ts +10 -0
- package/dist/theme-lab/derive-controls.d.ts.map +1 -1
- package/dist/theme-lab/derive-controls.js +5 -3
- package/dist/theme-lab/derive-state.d.ts.map +1 -1
- package/dist/theme-lab/derive-state.js +2 -1
- package/dist/tokens/core.d.ts +360 -0
- package/dist/tokens/core.d.ts.map +1 -0
- package/dist/tokens/core.js +708 -0
- package/dist/tokens/derive.d.ts +1 -1
- package/dist/tokens/derive.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +5 -373
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +8 -700
- package/dist/tokens/palette.d.ts +2 -6
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +1 -4
- package/dist/vite.d.ts +4 -3
- package/dist/vite.d.ts.map +1 -1
- package/llms.txt +43 -85
- package/package.json +77 -170
- package/src/agent/ai-sdk-transport.ts +57 -37
- package/src/{agent-chat → agent/chat}/composer.tsx +29 -30
- package/src/{agent-chat → agent/chat}/message-affordances.ts +8 -5
- package/src/{agent-chat → agent/chat}/thread-feed-row.tsx +30 -35
- package/src/{agent-chat → agent/chat}/thread-feed.tsx +40 -35
- package/src/{agent-chat → agent/chat}/thread-message.tsx +37 -35
- package/src/{agent-chat → agent/chat}/thread-outcome-card.tsx +5 -5
- package/src/{agent-chat → agent/chat}/tool-chip.tsx +12 -12
- package/src/{agent-chat → agent/chat}/use-keyboard-inset.ts +1 -1
- package/src/{agent-chat → agent/chat}/virtualize.ts +5 -5
- package/src/agent/error.ts +43 -0
- package/src/agent/foreign.ts +3 -2
- package/src/agent/history.ts +2 -107
- package/src/agent/id.ts +4 -5
- package/src/agent/index.ts +23 -112
- package/src/agent/outcome.ts +3 -56
- package/src/agent/part-list.tsx +3 -3
- package/src/agent/parts.ts +23 -22
- package/src/agent/run-committer.ts +64 -0
- package/src/agent/run-records.ts +147 -0
- package/src/agent/thread.ts +24 -231
- package/src/agent/transport.ts +6 -96
- package/src/agent/use-agent-thread-runs.ts +364 -199
- package/src/charts/BandStrip.tsx +276 -0
- package/src/charts/Bars.tsx +141 -0
- package/src/charts/CartesianChart.tsx +206 -0
- package/src/charts/ChartCard.tsx +178 -0
- package/src/charts/Donut.tsx +256 -0
- package/src/charts/DualPanel.tsx +326 -0
- package/src/charts/Heatmap.tsx +282 -0
- package/src/charts/Lines.tsx +151 -0
- package/src/charts/MirroredBars.tsx +303 -0
- package/src/charts/Sparkline.tsx +176 -0
- package/src/charts/StackedArea.tsx +80 -0
- package/src/charts/box.tsx +96 -0
- package/src/charts/card-context.ts +11 -0
- package/src/charts/chart-box.module.css +64 -0
- package/src/charts/chart-box.module.css.d.ts +12 -0
- package/src/charts/chart-card.module.css +25 -0
- package/src/charts/chart-card.module.css.d.ts +8 -0
- package/src/charts/chart-legend.module.css +38 -0
- package/src/charts/chart-legend.module.css.d.ts +10 -0
- package/src/charts/compile.ts +585 -0
- package/src/charts/cursor.ts +282 -0
- package/src/charts/curves.ts +111 -0
- package/src/charts/donut.module.css +54 -0
- package/src/charts/donut.module.css.d.ts +9 -0
- package/src/charts/host.tsx +60 -0
- package/src/charts/index.ts +41 -178
- package/src/charts/legend.tsx +204 -0
- package/src/charts/marks.ts +201 -0
- package/src/charts/panel.tsx +227 -0
- package/src/charts/scales.ts +237 -0
- package/src/charts/sizing.ts +47 -0
- package/src/charts/state.tsx +80 -0
- package/src/charts/theme.ts +65 -0
- package/src/charts/types.ts +242 -0
- package/src/cli/audit-allows.ts +8 -30
- package/src/cli/config.ts +14 -43
- package/src/cli/doctor-upgrade.ts +392 -0
- package/src/cli/doctor.ts +120 -68
- package/src/cli/help.ts +2 -4
- package/src/cli/index.ts +161 -276
- package/src/cli/init.ts +60 -37
- package/src/cli/sync.ts +13 -45
- package/src/cli/tokens-css.ts +7 -28
- package/src/commands/ShortcutsHelp.tsx +8 -9
- package/src/commands/define-commands.ts +16 -43
- package/src/commands/define-overlays.ts +12 -36
- package/src/commands/index.ts +11 -94
- package/src/{notifications → commands/notifications}/bell.tsx +2 -2
- package/src/{notifications → commands/notifications}/center.tsx +7 -5
- package/src/{notifications → commands/notifications}/define-notifications.ts +3 -3
- package/src/commands/notifications/notify.tsx +158 -0
- package/src/{notifications → commands/notifications}/store.ts +10 -2
- package/src/commands/projectors.ts +5 -225
- package/src/commands/shell-bridge.ts +5 -5
- package/src/commands/shortcut-format.ts +5 -4
- package/src/common/errors.ts +3 -3
- package/src/common/use-measured-widths.ts +6 -3
- package/src/common/validate.ts +2 -2
- package/src/content/article-layout.tsx +37 -38
- package/src/content/article-model.ts +1 -84
- package/src/content/copy-action.tsx +1 -1
- package/src/content/glyphs.tsx +1 -1
- package/src/content/index.ts +12 -83
- package/src/content/markdown.tsx +4 -3
- package/src/content/mermaid.tsx +9 -7
- package/src/content/slug.ts +1 -16
- package/src/content/url-hardening.ts +8 -2
- package/src/controls/actions.tsx +24 -1
- package/src/controls/enum-filter.tsx +1 -1
- package/src/controls/filter-context.tsx +3 -1
- package/src/controls/filter-set.tsx +5 -7
- package/src/controls/form-layout.tsx +2 -2
- package/src/controls/glyphs.tsx +0 -10
- package/src/controls/index.ts +13 -64
- package/src/controls/multi-select-filter.tsx +5 -4
- package/src/controls/number-filter.tsx +8 -4
- package/src/controls/range-filter.tsx +5 -4
- package/src/controls/search-filter.tsx +25 -19
- package/src/controls/select-filter.tsx +5 -4
- package/src/controls/sync-button.tsx +18 -20
- package/src/controls/toggle-filter.tsx +5 -4
- package/src/controls/uncontrolled.ts +12 -0
- package/src/controls/view-tabs.tsx +3 -3
- package/src/controls-dates/date-range-picker.tsx +1 -1
- package/src/controls-dates/index.ts +0 -1
- package/src/dashboard/index.ts +7 -48
- package/src/dashboard/section.tsx +11 -40
- package/src/dashboard/stat-card.tsx +14 -16
- package/src/dashboard/widget-header.module.css +9 -7
- package/src/dev-dock.tsx +14 -27
- package/src/format/date.ts +2 -92
- package/src/format/index.ts +7 -53
- package/src/format/number.ts +3 -26
- package/src/format/shared.ts +2 -3
- package/src/forms/index.ts +0 -23
- package/src/guard/index.ts +37 -192
- package/src/guard/types.ts +6 -19
- package/src/index.ts +30 -282
- package/src/provider/create-router.tsx +138 -0
- package/src/provider/hotkeys.ts +70 -0
- package/src/provider/index.tsx +260 -230
- package/src/provider/lab-theme.ts +3 -2
- package/src/provider/route-transition.ts +199 -0
- package/src/provider/spotlight-actions.tsx +69 -0
- package/src/provider/test-router.tsx +25 -0
- package/src/query-client.ts +4 -22
- package/src/router-tanstack/index.ts +18 -112
- package/src/router-tanstack/nav.ts +28 -2
- package/src/router-tanstack/search-store.ts +9 -2
- package/src/router-tanstack/use-nav.ts +47 -13
- package/src/shell/app-mobile-nav.tsx +3 -2
- package/src/shell/app-sidebar.tsx +1 -1
- package/src/shell/aside-docking.ts +1 -1
- package/src/shell/index.tsx +61 -20
- package/src/shell/page-aside.tsx +47 -23
- package/src/shell/page-bar.tsx +3 -3
- package/src/shell/page-title.tsx +1 -1
- package/src/state.ts +11 -37
- package/src/styles.css +107 -7
- package/src/surfaces.ts +56 -151
- package/src/{data → table}/data-table-state.ts +5 -4
- package/src/{data → table}/data-table.tsx +170 -161
- package/src/table/index.ts +18 -0
- package/src/table/table-features.ts +102 -0
- package/src/telemetry.ts +110 -0
- package/src/theme/index.ts +22 -47
- package/src/theme/theme-toggle.module.css +30 -0
- package/src/theme/theme-toggle.module.css.d.ts +10 -0
- package/src/theme/theme-toggle.tsx +15 -43
- package/src/theme-lab/derive-controls.tsx +5 -3
- package/src/theme-lab/derive-state.ts +2 -1
- package/src/tokens/core.ts +1163 -0
- package/src/tokens/derive.ts +1 -1
- package/src/tokens/index.ts +14 -1181
- package/src/tokens/palette.ts +3 -7
- package/src/vite.ts +3 -3
- package/agent/templates/CLAUDE-block.md.tpl +0 -40
- package/agent/templates/query-client.ts.tpl +0 -26
- package/dist/agent/adapter.d.ts +0 -150
- package/dist/agent/adapter.d.ts.map +0 -1
- package/dist/agent/adapter.js +0 -482
- package/dist/agent/use-agent-stream.d.ts +0 -35
- package/dist/agent/use-agent-stream.d.ts.map +0 -1
- package/dist/agent/use-agent-stream.js +0 -59
- package/dist/agent-chat/composer.d.ts.map +0 -1
- package/dist/agent-chat/index.d.ts +0 -31
- package/dist/agent-chat/index.d.ts.map +0 -1
- package/dist/agent-chat/index.js +0 -19
- package/dist/agent-chat/message-affordances.d.ts.map +0 -1
- package/dist/agent-chat/relative-time.d.ts.map +0 -1
- package/dist/agent-chat/thread-detail-panel.d.ts +0 -28
- package/dist/agent-chat/thread-detail-panel.d.ts.map +0 -1
- package/dist/agent-chat/thread-detail-panel.js +0 -115
- package/dist/agent-chat/thread-feed-row.d.ts.map +0 -1
- package/dist/agent-chat/thread-feed.d.ts.map +0 -1
- package/dist/agent-chat/thread-message.d.ts.map +0 -1
- package/dist/agent-chat/thread-outcome-card.d.ts.map +0 -1
- package/dist/agent-chat/thread-workspace.d.ts +0 -44
- package/dist/agent-chat/thread-workspace.d.ts.map +0 -1
- package/dist/agent-chat/thread-workspace.js +0 -153
- package/dist/agent-chat/tool-chip.d.ts.map +0 -1
- package/dist/agent-chat/use-keyboard-inset.d.ts.map +0 -1
- package/dist/agent-chat/virtualize.d.ts.map +0 -1
- package/dist/charts/cursor/resolve.d.ts +0 -75
- package/dist/charts/cursor/resolve.d.ts.map +0 -1
- package/dist/charts/cursor/resolve.js +0 -91
- package/dist/charts/cursor/scope.d.ts +0 -20
- package/dist/charts/cursor/scope.d.ts.map +0 -1
- package/dist/charts/cursor/scope.js +0 -20
- package/dist/charts/cursor/store.d.ts +0 -35
- package/dist/charts/cursor/store.d.ts.map +0 -1
- package/dist/charts/cursor/store.js +0 -25
- package/dist/charts/cursor/touch-pin.d.ts +0 -45
- package/dist/charts/cursor/touch-pin.d.ts.map +0 -1
- package/dist/charts/cursor/touch-pin.js +0 -79
- package/dist/charts/hooks/use-discrete-cursor.d.ts +0 -66
- package/dist/charts/hooks/use-discrete-cursor.d.ts.map +0 -1
- package/dist/charts/hooks/use-discrete-cursor.js +0 -198
- package/dist/charts/hooks/use-hysteresis.d.ts +0 -18
- package/dist/charts/hooks/use-hysteresis.d.ts.map +0 -1
- package/dist/charts/hooks/use-hysteresis.js +0 -12
- package/dist/charts/hooks/useBandPlot.d.ts +0 -145
- package/dist/charts/hooks/useBandPlot.d.ts.map +0 -1
- package/dist/charts/hooks/useBandPlot.js +0 -178
- package/dist/charts/hooks/useChartCursor.d.ts +0 -75
- package/dist/charts/hooks/useChartCursor.d.ts.map +0 -1
- package/dist/charts/hooks/useChartCursor.js +0 -200
- package/dist/charts/hooks/useChartSize.d.ts +0 -29
- package/dist/charts/hooks/useChartSize.d.ts.map +0 -1
- package/dist/charts/hooks/useChartSize.js +0 -8
- package/dist/charts/kinds/BandStrip.d.ts +0 -132
- package/dist/charts/kinds/BandStrip.d.ts.map +0 -1
- package/dist/charts/kinds/BandStrip.js +0 -241
- package/dist/charts/kinds/Bars.d.ts +0 -141
- package/dist/charts/kinds/Bars.d.ts.map +0 -1
- package/dist/charts/kinds/Bars.js +0 -343
- package/dist/charts/kinds/Donut.d.ts +0 -77
- package/dist/charts/kinds/Donut.d.ts.map +0 -1
- package/dist/charts/kinds/Donut.js +0 -245
- package/dist/charts/kinds/DualPanel.d.ts +0 -111
- package/dist/charts/kinds/DualPanel.d.ts.map +0 -1
- package/dist/charts/kinds/DualPanel.js +0 -471
- package/dist/charts/kinds/Heatmap.d.ts +0 -85
- package/dist/charts/kinds/Heatmap.d.ts.map +0 -1
- package/dist/charts/kinds/Heatmap.js +0 -243
- package/dist/charts/kinds/MirroredBars.d.ts +0 -98
- package/dist/charts/kinds/MirroredBars.d.ts.map +0 -1
- package/dist/charts/kinds/MirroredBars.js +0 -292
- package/dist/charts/kinds/MultiLine.d.ts +0 -87
- package/dist/charts/kinds/MultiLine.d.ts.map +0 -1
- package/dist/charts/kinds/MultiLine.js +0 -176
- package/dist/charts/kinds/StackedArea.d.ts +0 -73
- package/dist/charts/kinds/StackedArea.d.ts.map +0 -1
- package/dist/charts/kinds/StackedArea.js +0 -135
- package/dist/charts/kinds/ZonedLine.d.ts +0 -97
- package/dist/charts/kinds/ZonedLine.d.ts.map +0 -1
- package/dist/charts/kinds/ZonedLine.js +0 -155
- package/dist/charts/kinds/band-chrome.d.ts +0 -31
- package/dist/charts/kinds/band-chrome.d.ts.map +0 -1
- package/dist/charts/kinds/band-chrome.js +0 -71
- package/dist/charts/kinds/donut-layout.d.ts +0 -47
- package/dist/charts/kinds/donut-layout.d.ts.map +0 -1
- package/dist/charts/kinds/donut-layout.js +0 -52
- package/dist/charts/layout/auto-margin.d.ts +0 -122
- package/dist/charts/layout/auto-margin.d.ts.map +0 -1
- package/dist/charts/layout/auto-margin.js +0 -81
- package/dist/charts/layout/end-labels.d.ts +0 -64
- package/dist/charts/layout/end-labels.d.ts.map +0 -1
- package/dist/charts/layout/end-labels.js +0 -76
- package/dist/charts/layout/log-ticks.d.ts +0 -27
- package/dist/charts/layout/log-ticks.d.ts.map +0 -1
- package/dist/charts/layout/log-ticks.js +0 -42
- package/dist/charts/primitives/AreaGradient.d.ts +0 -18
- package/dist/charts/primitives/AreaGradient.d.ts.map +0 -1
- package/dist/charts/primitives/AreaGradient.js +0 -13
- package/dist/charts/primitives/Axes.d.ts +0 -83
- package/dist/charts/primitives/Axes.d.ts.map +0 -1
- package/dist/charts/primitives/Axes.js +0 -168
- package/dist/charts/primitives/CartesianChart.d.ts +0 -257
- package/dist/charts/primitives/CartesianChart.d.ts.map +0 -1
- package/dist/charts/primitives/CartesianChart.js +0 -549
- package/dist/charts/primitives/ChartCard.d.ts +0 -66
- package/dist/charts/primitives/ChartCard.d.ts.map +0 -1
- package/dist/charts/primitives/ChartCard.js +0 -143
- package/dist/charts/primitives/ChartFrame.d.ts +0 -156
- package/dist/charts/primitives/ChartFrame.d.ts.map +0 -1
- package/dist/charts/primitives/ChartFrame.js +0 -241
- package/dist/charts/primitives/ChartLegend.d.ts +0 -67
- package/dist/charts/primitives/ChartLegend.d.ts.map +0 -1
- package/dist/charts/primitives/ChartLegend.js +0 -292
- package/dist/charts/primitives/ChartPending.d.ts +0 -113
- package/dist/charts/primitives/ChartPending.d.ts.map +0 -1
- package/dist/charts/primitives/ChartPending.js +0 -117
- package/dist/charts/primitives/ChartTooltip.d.ts +0 -59
- package/dist/charts/primitives/ChartTooltip.d.ts.map +0 -1
- package/dist/charts/primitives/ChartTooltip.js +0 -197
- package/dist/charts/primitives/Crosshair.d.ts +0 -26
- package/dist/charts/primitives/Crosshair.d.ts.map +0 -1
- package/dist/charts/primitives/Crosshair.js +0 -23
- package/dist/charts/primitives/HatchPattern.d.ts +0 -31
- package/dist/charts/primitives/HatchPattern.d.ts.map +0 -1
- package/dist/charts/primitives/HatchPattern.js +0 -31
- package/dist/charts/primitives/HoverOverlay.d.ts +0 -45
- package/dist/charts/primitives/HoverOverlay.d.ts.map +0 -1
- package/dist/charts/primitives/HoverOverlay.js +0 -72
- package/dist/charts/primitives/HoverOverlay.module.css +0 -13
- package/dist/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
- package/dist/charts/primitives/XZoneRects.d.ts +0 -54
- package/dist/charts/primitives/XZoneRects.d.ts.map +0 -1
- package/dist/charts/primitives/XZoneRects.js +0 -65
- package/dist/charts/primitives/ZoneRects.d.ts +0 -38
- package/dist/charts/primitives/ZoneRects.d.ts.map +0 -1
- package/dist/charts/primitives/ZoneRects.js +0 -30
- package/dist/charts/primitives/chart-card-context.d.ts +0 -33
- package/dist/charts/primitives/chart-card-context.d.ts.map +0 -1
- package/dist/charts/primitives/chart-card-context.js +0 -18
- package/dist/charts/primitives/chart-frame-layout.d.ts +0 -185
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +0 -1
- package/dist/charts/primitives/chart-frame-layout.js +0 -175
- package/dist/charts/primitives/chart-layout.d.ts +0 -112
- package/dist/charts/primitives/chart-layout.d.ts.map +0 -1
- package/dist/charts/primitives/chart-layout.js +0 -175
- package/dist/charts/primitives/chart-tier.d.ts +0 -16
- package/dist/charts/primitives/chart-tier.d.ts.map +0 -1
- package/dist/charts/primitives/chart-tier.js +0 -44
- package/dist/charts/primitives/legend-disclosure.d.ts +0 -27
- package/dist/charts/primitives/legend-disclosure.d.ts.map +0 -1
- package/dist/charts/primitives/legend-disclosure.js +0 -153
- package/dist/charts/series.d.ts +0 -189
- package/dist/charts/series.d.ts.map +0 -1
- package/dist/charts/series.js +0 -84
- package/dist/charts/sparklines/BarSparkline.d.ts +0 -37
- package/dist/charts/sparklines/BarSparkline.d.ts.map +0 -1
- package/dist/charts/sparklines/BarSparkline.js +0 -53
- package/dist/charts/sparklines/LineSparkline.d.ts +0 -15
- package/dist/charts/sparklines/LineSparkline.d.ts.map +0 -1
- package/dist/charts/sparklines/LineSparkline.js +0 -51
- package/dist/charts/sparklines/index.d.ts +0 -3
- package/dist/charts/sparklines/index.d.ts.map +0 -1
- package/dist/charts/sparklines/index.js +0 -6
- package/dist/charts/utils/domain.d.ts +0 -35
- package/dist/charts/utils/domain.d.ts.map +0 -1
- package/dist/charts/utils/domain.js +0 -12
- package/dist/charts/utils/format.d.ts +0 -8
- package/dist/charts/utils/format.d.ts.map +0 -1
- package/dist/charts/utils/format.js +0 -20
- package/dist/charts/utils/measure-text.d.ts +0 -25
- package/dist/charts/utils/measure-text.d.ts.map +0 -1
- package/dist/charts/utils/measure-text.js +0 -64
- package/dist/charts/utils/ticks.d.ts +0 -103
- package/dist/charts/utils/ticks.d.ts.map +0 -1
- package/dist/charts/utils/ticks.js +0 -92
- package/dist/commands/overlays-mount.d.ts +0 -122
- package/dist/commands/overlays-mount.d.ts.map +0 -1
- package/dist/commands/overlays-mount.js +0 -125
- package/dist/commands/useCommandHotkeys.d.ts +0 -14
- package/dist/commands/useCommandHotkeys.d.ts.map +0 -1
- package/dist/commands/useCommandHotkeys.js +0 -45
- package/dist/content/article-actions.d.ts +0 -39
- package/dist/content/article-actions.d.ts.map +0 -1
- package/dist/content/article-actions.js +0 -28
- package/dist/content/article-card.d.ts +0 -58
- package/dist/content/article-card.d.ts.map +0 -1
- package/dist/content/article-card.js +0 -79
- package/dist/content/article-card.module.css +0 -111
- package/dist/content/mdx.d.ts +0 -20
- package/dist/content/mdx.d.ts.map +0 -1
- package/dist/content/mdx.js +0 -64
- package/dist/controls/compare-filter.d.ts +0 -42
- package/dist/controls/compare-filter.d.ts.map +0 -1
- package/dist/controls/compare-filter.js +0 -38
- package/dist/controls/slider-control.d.ts +0 -36
- package/dist/controls/slider-control.d.ts.map +0 -1
- package/dist/controls/slider-control.js +0 -64
- package/dist/dashboard/widget-grid.d.ts +0 -27
- package/dist/dashboard/widget-grid.d.ts.map +0 -1
- package/dist/dashboard/widget-grid.js +0 -40
- package/dist/dashboard/widget-grid.module.css +0 -69
- package/dist/data/data-table-state.d.ts.map +0 -1
- package/dist/data/data-table.d.ts.map +0 -1
- package/dist/data/query-branch.d.ts.map +0 -1
- package/dist/data/table.d.ts +0 -24
- package/dist/data/table.d.ts.map +0 -1
- package/dist/data/virtual-list.d.ts +0 -133
- package/dist/data/virtual-list.d.ts.map +0 -1
- package/dist/data/virtual-list.js +0 -157
- package/dist/data/virtual.d.ts +0 -21
- package/dist/data/virtual.d.ts.map +0 -1
- package/dist/data/virtual.js +0 -4
- package/dist/forms/FormErrorSummary.d.ts +0 -30
- package/dist/forms/FormErrorSummary.d.ts.map +0 -1
- package/dist/forms/FormErrorSummary.js +0 -30
- package/dist/forms/use-field-array.d.ts +0 -60
- package/dist/forms/use-field-array.d.ts.map +0 -1
- package/dist/forms/use-field-array.js +0 -20
- package/dist/forms/use-form-draft.d.ts +0 -76
- package/dist/forms/use-form-draft.d.ts.map +0 -1
- package/dist/forms/use-form-draft.js +0 -92
- package/dist/forms/use-form-submit.d.ts +0 -88
- package/dist/forms/use-form-submit.d.ts.map +0 -1
- package/dist/forms/use-form-submit.js +0 -63
- package/dist/notifications/bell.d.ts.map +0 -1
- package/dist/notifications/center.d.ts.map +0 -1
- package/dist/notifications/define-notifications.d.ts.map +0 -1
- package/dist/notifications/index.d.ts +0 -40
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/index.js +0 -54
- package/dist/notifications/mount-guard.d.ts +0 -8
- package/dist/notifications/mount-guard.d.ts.map +0 -1
- package/dist/notifications/mount-guard.js +0 -18
- package/dist/notifications/notify.d.ts +0 -149
- package/dist/notifications/notify.d.ts.map +0 -1
- package/dist/notifications/notify.js +0 -171
- package/dist/notifications/store.d.ts.map +0 -1
- package/dist/provider/connectivity-indicator.d.ts +0 -13
- package/dist/provider/connectivity-indicator.d.ts.map +0 -1
- package/dist/provider/connectivity-indicator.js +0 -205
- package/dist/provider/connectivity-provider.d.ts +0 -7
- package/dist/provider/connectivity-provider.d.ts.map +0 -1
- package/dist/provider/connectivity-provider.js +0 -128
- package/dist/provider/connectivity-types.d.ts +0 -36
- package/dist/provider/connectivity-types.d.ts.map +0 -1
- package/dist/provider/use-connectivity.d.ts +0 -3
- package/dist/provider/use-connectivity.d.ts.map +0 -1
- package/dist/provider/use-connectivity.js +0 -14
- package/dist/query-devtools.d.ts +0 -21
- package/dist/query-devtools.d.ts.map +0 -1
- package/dist/query-devtools.js +0 -13
- package/src/agent/adapter.ts +0 -945
- package/src/agent/use-agent-stream.ts +0 -154
- package/src/agent-chat/index.ts +0 -54
- package/src/agent-chat/thread-detail-panel.tsx +0 -201
- package/src/agent-chat/thread-workspace.tsx +0 -279
- package/src/charts/cursor/resolve.ts +0 -198
- package/src/charts/cursor/scope.tsx +0 -35
- package/src/charts/cursor/store.ts +0 -58
- package/src/charts/cursor/touch-pin.ts +0 -138
- package/src/charts/hooks/use-discrete-cursor.ts +0 -335
- package/src/charts/hooks/use-hysteresis.ts +0 -26
- package/src/charts/hooks/useBandPlot.ts +0 -383
- package/src/charts/hooks/useChartCursor.ts +0 -367
- package/src/charts/hooks/useChartSize.ts +0 -35
- package/src/charts/kinds/BandStrip.tsx +0 -449
- package/src/charts/kinds/Bars.tsx +0 -589
- package/src/charts/kinds/Donut.tsx +0 -368
- package/src/charts/kinds/DualPanel.tsx +0 -712
- package/src/charts/kinds/Heatmap.tsx +0 -396
- package/src/charts/kinds/MirroredBars.tsx +0 -469
- package/src/charts/kinds/MultiLine.tsx +0 -322
- package/src/charts/kinds/StackedArea.tsx +0 -267
- package/src/charts/kinds/ZonedLine.tsx +0 -301
- package/src/charts/kinds/band-chrome.tsx +0 -91
- package/src/charts/kinds/donut-layout.ts +0 -82
- package/src/charts/layout/auto-margin.ts +0 -242
- package/src/charts/layout/end-labels.ts +0 -135
- package/src/charts/layout/log-ticks.ts +0 -90
- package/src/charts/primitives/AreaGradient.tsx +0 -26
- package/src/charts/primitives/Axes.tsx +0 -271
- package/src/charts/primitives/CartesianChart.tsx +0 -1002
- package/src/charts/primitives/ChartCard.tsx +0 -280
- package/src/charts/primitives/ChartFrame.tsx +0 -493
- package/src/charts/primitives/ChartLegend.tsx +0 -422
- package/src/charts/primitives/ChartPending.tsx +0 -243
- package/src/charts/primitives/ChartTooltip.tsx +0 -265
- package/src/charts/primitives/Crosshair.tsx +0 -43
- package/src/charts/primitives/HatchPattern.tsx +0 -55
- package/src/charts/primitives/HoverOverlay.module.css +0 -13
- package/src/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
- package/src/charts/primitives/HoverOverlay.tsx +0 -109
- package/src/charts/primitives/XZoneRects.tsx +0 -132
- package/src/charts/primitives/ZoneRects.tsx +0 -67
- package/src/charts/primitives/chart-card-context.ts +0 -46
- package/src/charts/primitives/chart-frame-layout.ts +0 -430
- package/src/charts/primitives/chart-layout.ts +0 -318
- package/src/charts/primitives/chart-tier.tsx +0 -76
- package/src/charts/primitives/legend-disclosure.tsx +0 -178
- package/src/charts/series.ts +0 -276
- package/src/charts/sparklines/BarSparkline.tsx +0 -94
- package/src/charts/sparklines/LineSparkline.tsx +0 -71
- package/src/charts/sparklines/index.ts +0 -2
- package/src/charts/theme.tsx +0 -53
- package/src/charts/utils/domain.ts +0 -41
- package/src/charts/utils/format.ts +0 -19
- package/src/charts/utils/measure-text.ts +0 -109
- package/src/charts/utils/ticks.ts +0 -239
- package/src/commands/overlays-mount.tsx +0 -333
- package/src/commands/useCommandHotkeys.ts +0 -114
- package/src/content/article-actions.ts +0 -72
- package/src/content/article-card.module.css +0 -111
- package/src/content/article-card.tsx +0 -174
- package/src/content/mdx.tsx +0 -127
- package/src/controls/compare-filter.tsx +0 -81
- package/src/controls/slider-control.tsx +0 -159
- package/src/dashboard/widget-grid.module.css +0 -69
- package/src/dashboard/widget-grid.tsx +0 -146
- package/src/data/table.ts +0 -39
- package/src/data/virtual-list.tsx +0 -356
- package/src/data/virtual.ts +0 -26
- package/src/forms/FormErrorSummary.tsx +0 -71
- package/src/forms/use-field-array.ts +0 -94
- package/src/forms/use-form-draft.ts +0 -262
- package/src/forms/use-form-submit.ts +0 -199
- package/src/notifications/index.ts +0 -88
- package/src/notifications/mount-guard.ts +0 -38
- package/src/notifications/notify.tsx +0 -379
- package/src/provider/connectivity-indicator.tsx +0 -267
- package/src/provider/connectivity-provider.tsx +0 -189
- package/src/provider/connectivity-types.ts +0 -42
- package/src/provider/use-connectivity.ts +0 -26
- package/src/query-devtools.tsx +0 -40
- /package/dist/{agent-chat → agent/chat}/message-affordances.js +0 -0
- /package/dist/{agent-chat → agent/chat}/relative-time.d.ts +0 -0
- /package/dist/{agent-chat → agent/chat}/relative-time.js +0 -0
- /package/dist/{agent-chat → agent/chat}/use-keyboard-inset.d.ts +0 -0
- /package/dist/{agent-chat → agent/chat}/virtualize.js +0 -0
- /package/dist/{notifications → commands/notifications}/bell.js +0 -0
- /package/dist/{notifications → commands/notifications}/define-notifications.js +0 -0
- /package/dist/{provider/connectivity-types.js → controls/uncontrolled.js} +0 -0
- /package/dist/{data → table}/data-table.module.css +0 -0
- /package/dist/{data → table}/data-table.module.css.d.ts +0 -0
- /package/dist/{data → table}/query-branch.d.ts +0 -0
- /package/dist/{data → table}/query-branch.js +0 -0
- /package/src/{agent-chat → agent/chat}/relative-time.ts +0 -0
- /package/src/{data → table}/data-table.module.css +0 -0
- /package/src/{data → table}/data-table.module.css.d.ts +0 -0
- /package/src/{data → table}/query-branch.ts +0 -0
|
@@ -15,9 +15,10 @@ import { dirname, resolve } from 'node:path'
|
|
|
15
15
|
import basaltPlugin, {
|
|
16
16
|
CTL_THEME_TAGS,
|
|
17
17
|
WAIVER_ID_ALIASES,
|
|
18
|
-
DEPRECATED_EXPORTS,
|
|
19
18
|
KNOWN_RULE_IDS,
|
|
20
|
-
|
|
19
|
+
CHART_ENGINE_ALLOWED_PREFIXES,
|
|
20
|
+
CHART_ENGINE_ALLOWED_SUBPATHS,
|
|
21
|
+
isAllowedChartEngineSubpath,
|
|
21
22
|
PLUGIN_RULE_GRACE,
|
|
22
23
|
RAW_BREAKPOINT_RECIPE,
|
|
23
24
|
RETIRED_RULE_IDS,
|
|
@@ -45,16 +46,12 @@ beforeEach(() => {
|
|
|
45
46
|
plugins: [],
|
|
46
47
|
jsPlugins: [PLUGIN_PATH],
|
|
47
48
|
rules: {
|
|
48
|
-
'basalt/provider-above-router': 'error',
|
|
49
49
|
'basalt/query-fn-unwrap': 'error',
|
|
50
|
-
'basalt/deprecated-export': 'error',
|
|
51
50
|
'basalt/forms-field-key': 'error',
|
|
52
51
|
'basalt/no-raw-font-size': 'error',
|
|
53
52
|
'basalt/raw-size-literal': 'error',
|
|
54
53
|
'basalt/card-inset': 'error',
|
|
55
54
|
'basalt/chart-in-raw-surface': 'error',
|
|
56
|
-
'basalt/hand-rolled-plot': 'error',
|
|
57
|
-
'basalt/chart-legend-literal': 'error',
|
|
58
55
|
'basalt/shadow-basalt-export': 'error',
|
|
59
56
|
'basalt/hand-rolled-shell': 'error',
|
|
60
57
|
'basalt/raw-scroll-container': 'error',
|
|
@@ -62,18 +59,18 @@ beforeEach(() => {
|
|
|
62
59
|
'basalt/control-outside-home': 'error',
|
|
63
60
|
'basalt/bound-control-outside-home': 'error',
|
|
64
61
|
'basalt/control-size-literal': 'error',
|
|
65
|
-
'basalt/page-bar-budget': 'error',
|
|
66
62
|
'basalt/in-body-page-title': 'error',
|
|
67
63
|
'basalt/responsive-twin': 'error',
|
|
68
64
|
'basalt/search-literal-link': 'error',
|
|
69
65
|
'basalt/use-search-from-literal': 'error',
|
|
70
|
-
'basalt/
|
|
71
|
-
'basalt/
|
|
66
|
+
'basalt/chart-engine-boundary': 'error',
|
|
67
|
+
'basalt/chart-host-single': 'error',
|
|
72
68
|
'basalt/token-layer-boundary': 'error',
|
|
73
|
-
'basalt/agent-resume-guard': 'error',
|
|
74
69
|
'basalt/agent-no-raw-usechat': 'error',
|
|
75
70
|
'basalt/ai-sdk-major': 'error',
|
|
76
71
|
'basalt/raw-breakpoint': 'error',
|
|
72
|
+
'basalt/no-dev-helper': 'error',
|
|
73
|
+
'basalt/route-view-transition': 'error',
|
|
77
74
|
},
|
|
78
75
|
}),
|
|
79
76
|
)
|
|
@@ -245,88 +242,357 @@ describe('basalt/card-inset', () => {
|
|
|
245
242
|
|
|
246
243
|
describe('basalt/chart-in-raw-surface', () => {
|
|
247
244
|
it('flags a chart-kind element inside a raw Card', () => {
|
|
248
|
-
const { code, rules } = run(`export const C = () => <Card><
|
|
245
|
+
const { code, rules } = run(`export const C = () => <Card><Lines data={[]} /></Card>\n`)
|
|
249
246
|
expect(code).toBe(1)
|
|
250
247
|
expect(rules).toContain('chart-in-raw-surface')
|
|
251
248
|
})
|
|
252
249
|
|
|
253
250
|
it('does NOT flag a chart passed as a prop value (not a Card/Paper subtree)', () => {
|
|
254
251
|
const { code, rules } = run(
|
|
255
|
-
`export const C = () => <Card><StatCard sparkline={<
|
|
252
|
+
`export const C = () => <Card><StatCard sparkline={<Sparkline />} /></Card>\n`,
|
|
256
253
|
)
|
|
257
254
|
expect(code).toBe(0)
|
|
258
255
|
expect(rules).not.toContain('chart-in-raw-surface')
|
|
259
256
|
})
|
|
260
257
|
})
|
|
261
258
|
|
|
262
|
-
// ──
|
|
259
|
+
// ── chart-engine-boundary ────────────────────────────────────────────────────
|
|
263
260
|
|
|
264
|
-
describe('basalt/
|
|
265
|
-
it(
|
|
266
|
-
|
|
261
|
+
describe('basalt/chart-engine-boundary', () => {
|
|
262
|
+
it.each([
|
|
263
|
+
['@visx/scale', `import { scaleLinear } from '@visx/scale'\n`],
|
|
264
|
+
['@visx/tooltip', `import { Tooltip } from '@visx/tooltip'\n`],
|
|
265
|
+
['a source-bearing named re-export', `export { scaleLinear } from '@visx/scale'\n`],
|
|
266
|
+
['a wildcard re-export', `export * from '@visx/scale'\n`],
|
|
267
|
+
['a dynamic import', `export const load = () => import('@visx/shape')\n`],
|
|
268
|
+
])('bans %s outside basalt', (_label, source) => {
|
|
269
|
+
const { code, rules } = run(source, 'lib.ts')
|
|
267
270
|
expect(code).toBe(1)
|
|
268
|
-
expect(rules).toContain('
|
|
271
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
269
272
|
})
|
|
270
273
|
|
|
271
|
-
it('
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
it('bans @visx/* inside basalt source and a charts/ directory too', () => {
|
|
275
|
+
for (const file of ['charts/lib.tsx', 'packages/basalt-ui/src/charts/lib.tsx']) {
|
|
276
|
+
const { code, rules } = run(`import { Bar } from '@visx/shape'\n`, file)
|
|
277
|
+
expect([file, code]).toEqual([file, 1])
|
|
278
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
279
|
+
}
|
|
275
280
|
})
|
|
276
281
|
|
|
277
|
-
it('
|
|
278
|
-
const {
|
|
282
|
+
it('still bans @visx/* under a theme-allow comment (no escape hatch)', () => {
|
|
283
|
+
const { rules } = run(
|
|
284
|
+
`// theme-allow chart-engine-boundary — legacy\nimport { scaleLinear } from '@visx/scale'\n`,
|
|
285
|
+
'lib.ts',
|
|
286
|
+
)
|
|
287
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it.each([
|
|
291
|
+
'@tanstack/charts/line',
|
|
292
|
+
'@tanstack/charts/area-x',
|
|
293
|
+
'@tanstack/charts/mark/decorative',
|
|
294
|
+
'@tanstack/charts/d3/shape',
|
|
295
|
+
'@tanstack/charts/scales/linear',
|
|
296
|
+
'@tanstack/charts/transform/bin',
|
|
297
|
+
'@tanstack/charts/types',
|
|
298
|
+
])('allows %s outside basalt', (specifier) => {
|
|
299
|
+
const { code, rules } = run(`import * as m from '${specifier}'\nexport { m }\n`, 'lib.ts')
|
|
300
|
+
expect([specifier, code]).toEqual([specifier, 0])
|
|
301
|
+
expect(rules).not.toContain('chart-engine-boundary')
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it.each([
|
|
305
|
+
'@tanstack/charts',
|
|
306
|
+
'@tanstack/charts/react',
|
|
307
|
+
'@tanstack/charts/react/tooltip',
|
|
308
|
+
'@tanstack/charts/svg',
|
|
309
|
+
'@tanstack/charts/mark/composite',
|
|
310
|
+
'@tanstack/charts/transform',
|
|
311
|
+
'@tanstack/charts/scales/',
|
|
312
|
+
'@tanstack/charts/line/extra',
|
|
313
|
+
])('bans %s outside basalt and names the allowed alternative', (specifier) => {
|
|
314
|
+
const { code, rules, output } = run(
|
|
315
|
+
`import * as m from '${specifier}'\nexport { m }\n`,
|
|
316
|
+
'lib.ts',
|
|
317
|
+
)
|
|
318
|
+
expect([specifier, code]).toEqual([specifier, 1])
|
|
319
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
320
|
+
expect(output).toContain('basalt-ui/charts')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it.each([
|
|
324
|
+
"const m = require('@visx/scale')\n",
|
|
325
|
+
"const m = require('@tanstack/charts')\n",
|
|
326
|
+
"const m = require('@tanstack/charts/react')\n",
|
|
327
|
+
"const m = require('@tanstack/charts/scales/../react')\n",
|
|
328
|
+
])('bans a literal require: %s', (source) => {
|
|
329
|
+
const { code, rules } = run(`${source}export { m }\n`, 'lib.ts')
|
|
330
|
+
expect([source, code]).toEqual([source, 1])
|
|
331
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('allows a literal require of an allowed subpath and ignores computed ones', () => {
|
|
335
|
+
const { code } = run(
|
|
336
|
+
`const a = require('@tanstack/charts/line')\nconst b = require(name)\nexport { a, b }\n`,
|
|
337
|
+
'lib.ts',
|
|
338
|
+
)
|
|
279
339
|
expect(code).toBe(0)
|
|
280
|
-
expect(rules).not.toContain('visx-boundary')
|
|
281
340
|
})
|
|
282
341
|
|
|
283
|
-
it('
|
|
284
|
-
const {
|
|
285
|
-
|
|
342
|
+
it('bans a traversal out of an allowed family', () => {
|
|
343
|
+
const { rules } = run(
|
|
344
|
+
`import * as m from '@tanstack/charts/scales/../react'\nexport { m }\n`,
|
|
286
345
|
'lib.ts',
|
|
287
346
|
)
|
|
288
|
-
expect(
|
|
289
|
-
expect(rules).toContain('visx-boundary')
|
|
347
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
290
348
|
})
|
|
291
349
|
|
|
292
|
-
it('
|
|
293
|
-
const {
|
|
294
|
-
expect(
|
|
295
|
-
expect(rules).toContain('visx-boundary')
|
|
350
|
+
it('bans a re-export of the bare root outside basalt', () => {
|
|
351
|
+
const { rules } = run(`export { defineChart } from '@tanstack/charts'\n`, 'lib.ts')
|
|
352
|
+
expect(rules).toContain('chart-engine-boundary')
|
|
296
353
|
})
|
|
297
354
|
|
|
298
|
-
it('
|
|
299
|
-
const { code, rules } = run(
|
|
300
|
-
|
|
301
|
-
|
|
355
|
+
it('does NOT apply the allowlist inside basalt source', () => {
|
|
356
|
+
const { code, rules } = run(
|
|
357
|
+
`import { ChartRuntime } from '@tanstack/charts/react'\nexport { ChartRuntime }\n`,
|
|
358
|
+
'packages/basalt-ui/src/charts/host.tsx',
|
|
359
|
+
)
|
|
360
|
+
expect(code).toBe(0)
|
|
361
|
+
expect(rules).not.toContain('chart-engine-boundary')
|
|
302
362
|
})
|
|
303
363
|
|
|
304
|
-
it('does
|
|
305
|
-
const { code
|
|
364
|
+
it('does not touch unrelated @tanstack packages', () => {
|
|
365
|
+
const { code } = run(
|
|
366
|
+
`import { useQuery } from '@tanstack/react-query'\nexport { useQuery }\n`,
|
|
367
|
+
'lib.ts',
|
|
368
|
+
)
|
|
306
369
|
expect(code).toBe(0)
|
|
307
|
-
expect(rules).not.toContain('visx-boundary')
|
|
308
370
|
})
|
|
309
371
|
})
|
|
310
372
|
|
|
311
|
-
|
|
373
|
+
describe('isAllowedChartEngineSubpath', () => {
|
|
374
|
+
it.each([...CHART_ENGINE_ALLOWED_SUBPATHS, 'scales/linear', 'transform/bin-time'])(
|
|
375
|
+
'allows %s',
|
|
376
|
+
(subpath) => expect(isAllowedChartEngineSubpath(subpath)).toBe(true),
|
|
377
|
+
)
|
|
312
378
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
379
|
+
it.each([
|
|
380
|
+
'',
|
|
381
|
+
'scales',
|
|
382
|
+
'scales/',
|
|
383
|
+
'transform',
|
|
384
|
+
'transform/',
|
|
385
|
+
'react',
|
|
386
|
+
'line/extra',
|
|
387
|
+
'scales/../react',
|
|
388
|
+
'scales/./linear',
|
|
389
|
+
'scales//linear',
|
|
390
|
+
'../line',
|
|
391
|
+
'line/..',
|
|
392
|
+
])('rejects %j', (subpath) => expect(isAllowedChartEngineSubpath(subpath)).toBe(false))
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// ── chart-host-single ────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
describe('basalt/chart-host-single', () => {
|
|
398
|
+
const REACT_IMPORT = `import { Chart } from '@tanstack/charts/react'\nexport { Chart }\n`
|
|
399
|
+
|
|
400
|
+
it('allows @tanstack/charts/react in packages/basalt-ui/src/charts/host.tsx', () => {
|
|
401
|
+
const { rules } = run(REACT_IMPORT, 'packages/basalt-ui/src/charts/host.tsx')
|
|
402
|
+
expect(rules).not.toContain('chart-host-single')
|
|
318
403
|
})
|
|
319
404
|
|
|
320
|
-
it(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
405
|
+
it.each([
|
|
406
|
+
'packages/basalt-ui/src/charts/kinds/lines.tsx',
|
|
407
|
+
'packages/basalt-ui/src/charts/host.ts',
|
|
408
|
+
'packages/basalt-ui/src/chart.tsx',
|
|
409
|
+
])('flags it in %s', (file) => {
|
|
410
|
+
const { code, rules } = run(REACT_IMPORT, file)
|
|
411
|
+
expect([file, code]).toEqual([file, 1])
|
|
412
|
+
expect(rules).toContain('chart-host-single')
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('stays silent outside basalt source — chart-engine-boundary owns that one diagnostic', () => {
|
|
416
|
+
for (const file of ['apps/playground/src/chart.tsx', 'host.tsx']) {
|
|
417
|
+
const { rules } = run(REACT_IMPORT, file)
|
|
418
|
+
expect([file, rules.has('chart-host-single')]).toEqual([file, false])
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('is not waived by theme-allow', () => {
|
|
423
|
+
const { rules } = run(
|
|
424
|
+
`// theme-allow chart-host-single — just this once\n${REACT_IMPORT}`,
|
|
425
|
+
'packages/basalt-ui/src/charts/kinds/lines.tsx',
|
|
426
|
+
)
|
|
427
|
+
expect(rules).toContain('chart-host-single')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('flags require() of the react entry', () => {
|
|
431
|
+
const { rules } = run(
|
|
432
|
+
`const { Chart } = require('@tanstack/charts/react')\nexport { Chart }\n`,
|
|
433
|
+
'packages/basalt-ui/src/charts/a.ts',
|
|
434
|
+
)
|
|
435
|
+
expect(rules).toContain('chart-host-single')
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
it('flags the react sub-entries and a dynamic import too', () => {
|
|
439
|
+
expect(
|
|
440
|
+
run(
|
|
441
|
+
`import 'x'\nexport * from '@tanstack/charts/react/tooltip'\n`,
|
|
442
|
+
'packages/basalt-ui/src/a.ts',
|
|
443
|
+
).rules,
|
|
444
|
+
).toContain('chart-host-single')
|
|
445
|
+
expect(
|
|
446
|
+
run(
|
|
447
|
+
`export const l = () => import('@tanstack/charts/react')\n`,
|
|
448
|
+
'packages/basalt-ui/src/b.ts',
|
|
449
|
+
).rules,
|
|
450
|
+
).toContain('chart-host-single')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('leaves the mark subpaths alone', () => {
|
|
454
|
+
const { rules } = run(`import { line } from '@tanstack/charts/line'\nexport { line }\n`, 'a.ts')
|
|
455
|
+
expect(rules).not.toContain('chart-host-single')
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// ── chart-engine-boundary ↔ the engine's `exports` (V2-SPEC § 9.8) ──────────────────────────────
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Every `@tanstack/charts` subpath that is deliberately NOT importable outside basalt, classified
|
|
463
|
+
* against 1.1.0's `exports` ('' is the bare root). A new engine subpath is in neither this list nor
|
|
464
|
+
* the allowlist, so the test below fails until someone decides which it is.
|
|
465
|
+
*/
|
|
466
|
+
const BANNED_ENGINE_SUBPATHS: readonly string[] = [
|
|
467
|
+
'',
|
|
468
|
+
'adapter',
|
|
469
|
+
'adapter/renderer',
|
|
470
|
+
'alpine',
|
|
471
|
+
'angular',
|
|
472
|
+
'band',
|
|
473
|
+
'box',
|
|
474
|
+
'canvas',
|
|
475
|
+
'crosshair',
|
|
476
|
+
'cursor',
|
|
477
|
+
'cursor/host',
|
|
478
|
+
'd3/area-x',
|
|
479
|
+
'dodge',
|
|
480
|
+
'dom',
|
|
481
|
+
'export',
|
|
482
|
+
'facet',
|
|
483
|
+
'focus',
|
|
484
|
+
'focus/disabled',
|
|
485
|
+
'focus/guide',
|
|
486
|
+
'focus/mark',
|
|
487
|
+
'frame',
|
|
488
|
+
'geo',
|
|
489
|
+
'group',
|
|
490
|
+
'hexagon',
|
|
491
|
+
'hierarchy/sunburst',
|
|
492
|
+
'hierarchy/tree',
|
|
493
|
+
'hierarchy/treemap',
|
|
494
|
+
'interaction/brush',
|
|
495
|
+
'interaction/cursor',
|
|
496
|
+
'interaction/handle',
|
|
497
|
+
'interaction/signal',
|
|
498
|
+
'interaction/zoom',
|
|
499
|
+
'legend',
|
|
500
|
+
'lit',
|
|
501
|
+
'mark/composite',
|
|
502
|
+
'mark/scale-values',
|
|
503
|
+
'motion',
|
|
504
|
+
'motion/definition',
|
|
505
|
+
'network/force',
|
|
506
|
+
'network/sankey',
|
|
507
|
+
'octane',
|
|
508
|
+
'octane/canvas',
|
|
509
|
+
'octane/core',
|
|
510
|
+
'polar',
|
|
511
|
+
'preact',
|
|
512
|
+
'react',
|
|
513
|
+
'react-native',
|
|
514
|
+
'react-native/tooltip',
|
|
515
|
+
'react/canvas',
|
|
516
|
+
'react/core',
|
|
517
|
+
'react/tooltip',
|
|
518
|
+
'reconcile',
|
|
519
|
+
'regression',
|
|
520
|
+
'renderer',
|
|
521
|
+
'renderer/rect',
|
|
522
|
+
'ridgeline',
|
|
523
|
+
'runtime',
|
|
524
|
+
'scene',
|
|
525
|
+
'selection',
|
|
526
|
+
'solid',
|
|
527
|
+
'spatial/contour',
|
|
528
|
+
'spatial/delaunay',
|
|
529
|
+
'spatial/density',
|
|
530
|
+
'spatial/hexbin',
|
|
531
|
+
'spatial/voronoi',
|
|
532
|
+
'spring',
|
|
533
|
+
'svelte',
|
|
534
|
+
'svg',
|
|
535
|
+
'svg/renderer',
|
|
536
|
+
'svg/resources',
|
|
537
|
+
'tooltip',
|
|
538
|
+
'tooltip/model',
|
|
539
|
+
'tooltip/portal',
|
|
540
|
+
'transform',
|
|
541
|
+
'types/core',
|
|
542
|
+
'universal',
|
|
543
|
+
'view',
|
|
544
|
+
'violin',
|
|
545
|
+
'vue',
|
|
546
|
+
'waffle',
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
// The package that declares the peer: bun's isolated linker installs it there, never at the root.
|
|
550
|
+
const ENGINE_PACKAGE_JSON = resolve(
|
|
551
|
+
import.meta.dirname,
|
|
552
|
+
'..',
|
|
553
|
+
'node_modules',
|
|
554
|
+
'@tanstack',
|
|
555
|
+
'charts',
|
|
556
|
+
'package.json',
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
describe('chart-engine-boundary ↔ @tanstack/charts exports', () => {
|
|
560
|
+
const exportKeys = (): string[] => {
|
|
561
|
+
const manifest = JSON.parse(readFileSync(ENGINE_PACKAGE_JSON, 'utf8')) as {
|
|
562
|
+
exports: Record<string, unknown>
|
|
563
|
+
}
|
|
564
|
+
return Object.keys(manifest.exports).map((key) => (key === '.' ? '' : key.replace(/^\.\//, '')))
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
it('every exact allowlisted subpath exists in the engine', () => {
|
|
568
|
+
const exports = new Set(exportKeys())
|
|
569
|
+
for (const subpath of CHART_ENGINE_ALLOWED_SUBPATHS) {
|
|
570
|
+
expect([subpath, exports.has(subpath)]).toEqual([subpath, true])
|
|
571
|
+
}
|
|
324
572
|
})
|
|
325
573
|
|
|
326
|
-
it('
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
574
|
+
it('every allowlisted family matches at least one engine subpath', () => {
|
|
575
|
+
for (const prefix of CHART_ENGINE_ALLOWED_PREFIXES) {
|
|
576
|
+
expect([prefix, exportKeys().some((key) => key.startsWith(prefix))]).toEqual([prefix, true])
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
it('every engine subpath is allowlisted or deliberately banned', () => {
|
|
581
|
+
const unclassified = exportKeys().filter(
|
|
582
|
+
(key) => !isAllowedChartEngineSubpath(key) && !BANNED_ENGINE_SUBPATHS.includes(key),
|
|
583
|
+
)
|
|
584
|
+
expect(unclassified).toEqual([])
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it('the banned list has no stale or allowlisted entries', () => {
|
|
588
|
+
const exports = new Set(exportKeys())
|
|
589
|
+
for (const subpath of BANNED_ENGINE_SUBPATHS) {
|
|
590
|
+
expect([subpath, exports.has(subpath), isAllowedChartEngineSubpath(subpath)]).toEqual([
|
|
591
|
+
subpath,
|
|
592
|
+
true,
|
|
593
|
+
false,
|
|
594
|
+
])
|
|
595
|
+
}
|
|
330
596
|
})
|
|
331
597
|
})
|
|
332
598
|
|
|
@@ -353,80 +619,29 @@ describe('basalt/token-layer-boundary', () => {
|
|
|
353
619
|
})
|
|
354
620
|
|
|
355
621
|
// ── rule independence ──────────────────────────────────────────────────────────
|
|
356
|
-
//
|
|
357
|
-
// consumer disabling the one check they disagreed with silently dropped the other two. Proves
|
|
358
|
-
// disabling one of the three leaves the other two enforced.
|
|
622
|
+
// Each boundary rule has its own id, so disabling one leaves the others enforced.
|
|
359
623
|
|
|
360
624
|
describe('rule independence', () => {
|
|
361
|
-
it('disabling basalt/
|
|
625
|
+
it('disabling basalt/chart-engine-boundary leaves basalt/chart-host-single enforced', () => {
|
|
362
626
|
writeFileSync(
|
|
363
627
|
resolve(dir, '.oxlintrc.json'),
|
|
364
628
|
JSON.stringify({
|
|
365
629
|
plugins: [],
|
|
366
630
|
jsPlugins: [PLUGIN_PATH],
|
|
367
631
|
rules: {
|
|
368
|
-
'basalt/
|
|
369
|
-
'basalt/
|
|
632
|
+
'basalt/chart-engine-boundary': 'off',
|
|
633
|
+
'basalt/chart-host-single': 'error',
|
|
370
634
|
'basalt/token-layer-boundary': 'error',
|
|
371
635
|
},
|
|
372
636
|
}),
|
|
373
637
|
)
|
|
374
638
|
const { code, rules } = run(
|
|
375
|
-
`import { scaleLinear } from '@visx/scale'\nimport {
|
|
376
|
-
'lib.ts',
|
|
639
|
+
`import { scaleLinear } from '@visx/scale'\nimport { Chart } from '@tanstack/charts/react'\n`,
|
|
640
|
+
'packages/basalt-ui/src/lib.ts',
|
|
377
641
|
)
|
|
378
642
|
expect(code).toBe(1)
|
|
379
|
-
expect(rules).not.toContain('
|
|
380
|
-
expect(rules).toContain('
|
|
381
|
-
})
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
// ── agent-resume-guard ───────────────────────────────────────────────────────
|
|
385
|
-
// The two agent rules honour a SEPARATE escape token, `basalt-agent-allow`, not `theme-allow` — a
|
|
386
|
-
// color exemption must never be able to switch off a streaming guard. The `theme-allow` fixture
|
|
387
|
-
// below proves the wrong token does not suppress the finding.
|
|
388
|
-
|
|
389
|
-
describe('basalt/agent-resume-guard', () => {
|
|
390
|
-
it('flags useChat({ resume: true })', () => {
|
|
391
|
-
const { code, rules } = run(`const c = useChat({ id, resume: true })\n`)
|
|
392
|
-
expect(code).toBe(1)
|
|
393
|
-
expect(rules).toContain('agent-resume-guard')
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
it('does NOT flag useChat({ resume: false })', () => {
|
|
397
|
-
const { code, rules } = run(`const c = useChat({ id, resume: false })\n`)
|
|
398
|
-
expect(code).toBe(0)
|
|
399
|
-
expect(rules).not.toContain('agent-resume-guard')
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
it('does NOT flag a bare useChat({ id }) with no resume key', () => {
|
|
403
|
-
const { code, rules } = run(`const c = useChat({ id })\n`)
|
|
404
|
-
expect(code).toBe(0)
|
|
405
|
-
expect(rules).not.toContain('agent-resume-guard')
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
it('flags chat.resumeStream() called from an effect', () => {
|
|
409
|
-
const { code, rules } = run(`useEffect(() => { chat.resumeStream() }, [])\n`)
|
|
410
|
-
expect(code).toBe(1)
|
|
411
|
-
expect(rules).toContain('agent-resume-guard')
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
it('does NOT flag chat.resumeStream() marked basalt-agent-allow', () => {
|
|
415
|
-
const { code, rules } = run(`// basalt-agent-allow\nchat.resumeStream()\n`)
|
|
416
|
-
expect(code).toBe(0)
|
|
417
|
-
expect(rules).not.toContain('agent-resume-guard')
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
it('STILL flags chat.resumeStream() marked only theme-allow (wrong escape token)', () => {
|
|
421
|
-
const { code, rules } = run(`// theme-allow\nchat.resumeStream()\n`)
|
|
422
|
-
expect(code).toBe(1)
|
|
423
|
-
expect(rules).toContain('agent-resume-guard')
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
it('does NOT flag an unrelated call like runs.start(id, text)', () => {
|
|
427
|
-
const { code, rules } = run(`runs.start(id, text)\n`)
|
|
428
|
-
expect(code).toBe(0)
|
|
429
|
-
expect(rules).not.toContain('agent-resume-guard')
|
|
643
|
+
expect(rules).not.toContain('chart-engine-boundary')
|
|
644
|
+
expect(rules).toContain('chart-host-single')
|
|
430
645
|
})
|
|
431
646
|
})
|
|
432
647
|
|
|
@@ -560,7 +775,7 @@ describe('basalt/ai-sdk-major', () => {
|
|
|
560
775
|
})
|
|
561
776
|
|
|
562
777
|
// Unlike the other rules in this file, ai-sdk-major honours `basalt-agent-allow` — the SAME
|
|
563
|
-
// token as its
|
|
778
|
+
// token as its agent-chat sibling above — not `theme-allow`. A skewed producer/consumer
|
|
564
779
|
// import that is genuinely intentional (see doctor's `aiMajorSkewReason`) can mark the line.
|
|
565
780
|
it('does NOT flag an import marked basalt-agent-allow, even with a skewed nearest package.json', () => {
|
|
566
781
|
writeFileSync(
|
|
@@ -671,14 +886,14 @@ describe('basalt/raw-scroll-container promotion', () => {
|
|
|
671
886
|
})
|
|
672
887
|
|
|
673
888
|
// ── agent rules — promotion (warn → error shipped) ──────────────────────────
|
|
674
|
-
// The
|
|
889
|
+
// The two agent-chat rules were promoted warn → error in the SHIPPED preset alongside
|
|
675
890
|
// raw-scroll-container above, but had no equivalent lock — `scripts/gen-oxlint.ts` only regenerates
|
|
676
891
|
// the `overrides` array; this top-level `rules` block is hand-maintained and
|
|
677
892
|
// `tests/oxlint-preset-sync.test.ts` only checks `overrides` against `projectBanList('shipped')`, so
|
|
678
|
-
// a later accidental revert of one of these
|
|
893
|
+
// a later accidental revert of one of these back to "warn" would pass the suite silently.
|
|
679
894
|
|
|
680
895
|
describe('basalt agent rules promotion', () => {
|
|
681
|
-
it.each(['basalt/agent-
|
|
896
|
+
it.each(['basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
|
|
682
897
|
'%s ships "error" (not "warn"/"off") in the consumer preset',
|
|
683
898
|
(rule) => {
|
|
684
899
|
const shipped = JSON.parse(
|
|
@@ -688,7 +903,7 @@ describe('basalt agent rules promotion', () => {
|
|
|
688
903
|
},
|
|
689
904
|
)
|
|
690
905
|
|
|
691
|
-
it.each(['basalt/agent-
|
|
906
|
+
it.each(['basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
|
|
692
907
|
'%s stays at the shipped level ("error") repo-local',
|
|
693
908
|
(rule) => {
|
|
694
909
|
const repoLocal = JSON.parse(
|
|
@@ -699,189 +914,71 @@ describe('basalt agent rules promotion', () => {
|
|
|
699
914
|
)
|
|
700
915
|
})
|
|
701
916
|
|
|
702
|
-
// ── hand-rolled-plot ─────────────────────────────────────────────────────────
|
|
703
|
-
|
|
704
|
-
const CHARTS_IMPORT = `import { AxisLeftNumeric, CartesianChart, ChartLegend, Crosshair, HoverOverlay } from 'basalt-ui/charts'\n`
|
|
705
|
-
|
|
706
|
-
describe('basalt/hand-rolled-plot', () => {
|
|
707
|
-
it('flags an axis primitive in a file that never composes CartesianChart', () => {
|
|
708
|
-
const { code, rules } = run(
|
|
709
|
-
`${CHARTS_IMPORT}export const C = () => <svg><AxisLeftNumeric scale={s} /></svg>\n`,
|
|
710
|
-
)
|
|
711
|
-
expect(code).toBe(1)
|
|
712
|
-
expect(rules).toContain('hand-rolled-plot')
|
|
713
|
-
})
|
|
714
|
-
|
|
715
|
-
it('does NOT flag the same primitive when CartesianChart is composed', () => {
|
|
716
|
-
const { code, rules } = run(
|
|
717
|
-
`${CHARTS_IMPORT}export const C = () => <CartesianChart>{() => <AxisLeftNumeric scale={s} />}</CartesianChart>\n`,
|
|
718
|
-
)
|
|
719
|
-
expect(code).toBe(0)
|
|
720
|
-
expect(rules).not.toContain('hand-rolled-plot')
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
// `theme-allow-file` is the written declaration about the file, and it covers every assembly node
|
|
724
|
-
// in it — the DualPanel shape.
|
|
725
|
-
it('a theme-allow-file declaration covers the whole file', () => {
|
|
726
|
-
const { code, rules } = run(
|
|
727
|
-
`${CHARTS_IMPORT}export const C = () => (
|
|
728
|
-
<svg>
|
|
729
|
-
{/* theme-allow-file basalt/hand-rolled-plot: multi-pane shape, not a single cartesian plot */}
|
|
730
|
-
<AxisLeftNumeric scale={a} />
|
|
731
|
-
<HoverOverlay width={1} height={1} />
|
|
732
|
-
<Crosshair x={0} top={0} bottom={1} />
|
|
733
|
-
</svg>
|
|
734
|
-
)\n`,
|
|
735
|
-
)
|
|
736
|
-
expect(code).toBe(0)
|
|
737
|
-
expect(rules).not.toContain('hand-rolled-plot')
|
|
738
|
-
})
|
|
739
|
-
|
|
740
|
-
// The other half of the 1.20.0 fix. Naming the rule AND giving a reason used to promote the
|
|
741
|
-
// waiver to the whole file, which made whole-file the ONLY expressible scope: the shape the rule's
|
|
742
|
-
// own message asks for was the shape that granted immunity. It is now what it reads as — one node.
|
|
743
|
-
it('a named-with-a-reason theme-allow waives its own node ONLY, not the file', () => {
|
|
744
|
-
const { code, rules } = run(
|
|
745
|
-
`${CHARTS_IMPORT}export const C = () => (
|
|
746
|
-
<svg>
|
|
747
|
-
{/* theme-allow basalt/hand-rolled-plot — this one axis is drawn against a second scale */}
|
|
748
|
-
<AxisLeftNumeric scale={a} />
|
|
749
|
-
<HoverOverlay width={1} height={1} />
|
|
750
|
-
</svg>
|
|
751
|
-
)\n`,
|
|
752
|
-
)
|
|
753
|
-
expect(code).toBe(1)
|
|
754
|
-
expect(rules).toContain('hand-rolled-plot')
|
|
755
|
-
})
|
|
756
|
-
|
|
757
|
-
// The linewatch defect: a BARE comment used to buy the whole file permanent immunity, because
|
|
758
|
-
// only the first node was reported and the waiver was tested there. It now waives its own node
|
|
759
|
-
// and nothing else, so everything added to the file afterwards is still reported.
|
|
760
|
-
it('a bare theme-allow waives only the node it sits above, not the file', () => {
|
|
761
|
-
const { code, rules } = run(
|
|
762
|
-
`${CHARTS_IMPORT}export const C = () => (
|
|
763
|
-
<svg>
|
|
764
|
-
{/* theme-allow */}
|
|
765
|
-
<AxisLeftNumeric scale={a} />
|
|
766
|
-
<HoverOverlay width={1} height={1} />
|
|
767
|
-
</svg>
|
|
768
|
-
)\n`,
|
|
769
|
-
)
|
|
770
|
-
expect(code).toBe(1)
|
|
771
|
-
expect(rules).toContain('hand-rolled-plot')
|
|
772
|
-
})
|
|
773
|
-
|
|
774
|
-
// A comment written about something else must not double as a licence to hand-assemble a plot.
|
|
775
|
-
it('a theme-allow scoped to a DIFFERENT rule does not waive this one', () => {
|
|
776
|
-
const { code, rules } = run(
|
|
777
|
-
`${CHARTS_IMPORT}export const C = () => (
|
|
778
|
-
<svg>
|
|
779
|
-
{/* theme-allow raw-hex — the swatch below is a measured pixel */}
|
|
780
|
-
<AxisLeftNumeric scale={a} />
|
|
781
|
-
</svg>
|
|
782
|
-
)\n`,
|
|
783
|
-
)
|
|
784
|
-
expect(code).toBe(1)
|
|
785
|
-
expect(rules).toContain('hand-rolled-plot')
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
it('does NOT flag a same-named component that is NOT a basalt chart primitive', () => {
|
|
789
|
-
const { code, rules } = run(
|
|
790
|
-
`import { Crosshair } from './my-own-widgets'
|
|
791
|
-
export const C = () => <svg><Crosshair x={0} /></svg>\n`,
|
|
792
|
-
)
|
|
793
|
-
expect(code).toBe(0)
|
|
794
|
-
expect(rules).not.toContain('hand-rolled-plot')
|
|
795
|
-
})
|
|
796
|
-
|
|
797
|
-
// `code` is 1 in both of these and that is correct: a CONSUMER file declaring `CartesianChart`
|
|
798
|
-
// shadows a basalt export, which `shadow-basalt-export` now sees since it reads the charts barrel
|
|
799
|
-
// too. The assertion under test is that `hand-rolled-plot` stays silent — a rule saying "compose
|
|
800
|
-
// X" cannot fire inside X.
|
|
801
|
-
it('does NOT flag the module that DEFINES CartesianChart (function form)', () => {
|
|
802
|
-
const { rules } = run(
|
|
803
|
-
`import { AxisBottomDate } from '../primitives/Axes'
|
|
804
|
-
export function CartesianChart() {
|
|
805
|
-
return <svg><AxisBottomDate scale={s} top={0} tickValues={[]} /></svg>
|
|
806
|
-
}\n`,
|
|
807
|
-
)
|
|
808
|
-
expect(rules).not.toContain('hand-rolled-plot')
|
|
809
|
-
})
|
|
810
|
-
|
|
811
|
-
it('does NOT flag the module that DEFINES CartesianChart (const form)', () => {
|
|
812
|
-
const { rules } = run(
|
|
813
|
-
`import { AxisBottomDate } from '../primitives/Axes'
|
|
814
|
-
export const CartesianChart = () => <svg><AxisBottomDate scale={s} top={0} tickValues={[]} /></svg>\n`,
|
|
815
|
-
)
|
|
816
|
-
expect(rules).not.toContain('hand-rolled-plot')
|
|
817
|
-
})
|
|
818
|
-
})
|
|
819
|
-
|
|
820
917
|
// ── theme-allow grammar — the accepted and rejected shapes, exhaustively ─────
|
|
821
918
|
//
|
|
822
919
|
// Two rounds produced three holes in this one contract, so it gets a matrix rather than a case.
|
|
823
|
-
// `
|
|
920
|
+
// `card-inset` is the vehicle: a node-level rule honouring both scopes, so one fixture shape
|
|
824
921
|
// exercises the node waiver, the file declaration and the prefix rule at once.
|
|
825
922
|
|
|
826
923
|
describe('theme-allow grammar', () => {
|
|
827
|
-
/** Two
|
|
924
|
+
/** Two flagged nodes, with `annotation` placed as a comment-only line above the first. */
|
|
828
925
|
function twoNodes(annotation: string): { code: number; rules: Set<string> } {
|
|
829
926
|
return run(
|
|
830
|
-
|
|
831
|
-
<
|
|
927
|
+
`export const C = () => (
|
|
928
|
+
<div>
|
|
832
929
|
${annotation}
|
|
833
|
-
<
|
|
834
|
-
<
|
|
835
|
-
</
|
|
930
|
+
<Card p="lg" />
|
|
931
|
+
<Card p="lg" />
|
|
932
|
+
</div>
|
|
836
933
|
)\n`,
|
|
837
934
|
)
|
|
838
935
|
}
|
|
839
936
|
|
|
840
937
|
const waivesFirstNodeOnly = (annotation: string) => {
|
|
841
938
|
const { rules } = twoNodes(annotation)
|
|
842
|
-
expect([annotation, rules.has('
|
|
939
|
+
expect([annotation, rules.has('card-inset')]).toEqual([annotation, true])
|
|
843
940
|
}
|
|
844
941
|
const waivesWholeFile = (annotation: string) => {
|
|
845
942
|
const { rules } = twoNodes(annotation)
|
|
846
|
-
expect([annotation, rules.has('
|
|
943
|
+
expect([annotation, rules.has('card-inset')]).toEqual([annotation, false])
|
|
847
944
|
}
|
|
848
945
|
|
|
849
946
|
// ── ACCEPTED as a NODE waiver (the second node still reports) ──────────────
|
|
850
947
|
|
|
851
948
|
it.each([
|
|
852
949
|
'{/* theme-allow */}',
|
|
853
|
-
'{/* theme-allow
|
|
854
|
-
'{/* theme-allow
|
|
855
|
-
'{/* theme-allow basalt/
|
|
856
|
-
'{/* theme-allow
|
|
857
|
-
'{/*theme-allow
|
|
950
|
+
'{/* theme-allow card-inset */}',
|
|
951
|
+
'{/* theme-allow card-inset — a reason */}',
|
|
952
|
+
'{/* theme-allow basalt/card-inset: a reason */}',
|
|
953
|
+
'{/* theme-allow card-inset, raw-hex — two ids */}',
|
|
954
|
+
'{/*theme-allow card-inset — no space after the opener*/}',
|
|
858
955
|
])('%s waives its own node and leaves the rest policed', waivesFirstNodeOnly)
|
|
859
956
|
|
|
860
957
|
// ── ACCEPTED as a FILE declaration (nothing in the file reports) ───────────
|
|
861
958
|
|
|
862
959
|
it.each([
|
|
863
|
-
'{/* theme-allow-file
|
|
864
|
-
'{/* theme-allow-file basalt/
|
|
865
|
-
'{/* theme-allow-file
|
|
866
|
-
'{/* theme-allow-file
|
|
960
|
+
'{/* theme-allow-file card-inset — a reason */}',
|
|
961
|
+
'{/* theme-allow-file basalt/card-inset: a reason */}',
|
|
962
|
+
'{/* theme-allow-file card-inset */}',
|
|
963
|
+
'{/* theme-allow-file card-inset, raw-hex — two ids */}',
|
|
867
964
|
])('%s declares the whole file', waivesWholeFile)
|
|
868
965
|
|
|
869
966
|
it('a theme-allow-file inside a docblock, detached from any node, declares the file', () => {
|
|
870
967
|
const { code, rules } = run(
|
|
871
|
-
|
|
872
|
-
* A
|
|
968
|
+
`/**
|
|
969
|
+
* A bespoke shape.
|
|
873
970
|
*
|
|
874
|
-
* theme-allow-file
|
|
971
|
+
* theme-allow-file card-inset — the inset is dictated by a third-party embed
|
|
875
972
|
*/
|
|
876
973
|
export const C = () => (
|
|
877
|
-
<
|
|
878
|
-
<
|
|
879
|
-
<
|
|
880
|
-
</
|
|
974
|
+
<div>
|
|
975
|
+
<Card p="lg" />
|
|
976
|
+
<Card p="lg" />
|
|
977
|
+
</div>
|
|
881
978
|
)\n`,
|
|
882
979
|
)
|
|
883
980
|
expect(code).toBe(0)
|
|
884
|
-
expect(rules).not.toContain('
|
|
981
|
+
expect(rules).not.toContain('card-inset')
|
|
885
982
|
})
|
|
886
983
|
|
|
887
984
|
// ── REJECTED — prose that merely MENTIONS the token (the linewatch defect) ─
|
|
@@ -897,14 +994,14 @@ export const C = () => (
|
|
|
897
994
|
])('%s is prose, not an annotation — it waives nothing', (annotation) => {
|
|
898
995
|
const { code, rules } = twoNodes(annotation)
|
|
899
996
|
expect([annotation, code]).toEqual([annotation, 1])
|
|
900
|
-
expect([annotation, rules.has('
|
|
997
|
+
expect([annotation, rules.has('card-inset')]).toEqual([annotation, true])
|
|
901
998
|
})
|
|
902
999
|
|
|
903
1000
|
it('a prose docblock line mentioning theme-allow-file does not declare the file', () => {
|
|
904
1001
|
const { rules } = twoNodes(
|
|
905
|
-
'{/* the shape to write here is a theme-allow-file
|
|
1002
|
+
'{/* the shape to write here is a theme-allow-file card-inset — see the docs */}',
|
|
906
1003
|
)
|
|
907
|
-
expect(rules).toContain('
|
|
1004
|
+
expect(rules).toContain('card-inset')
|
|
908
1005
|
})
|
|
909
1006
|
|
|
910
1007
|
// ── REJECTED — a longer word that merely STARTS with the token ─────────────
|
|
@@ -913,7 +1010,7 @@ export const C = () => (
|
|
|
913
1010
|
// fall through to the empty-`rules` branch, and read as a blanket waiver.
|
|
914
1011
|
it('a comment starting with the kind name `theme-allow-unscoped` is not an annotation', () => {
|
|
915
1012
|
const { rules } = twoNodes('{/* theme-allow-unscoped is what reports a bare waiver */}')
|
|
916
|
-
expect(rules).toContain('
|
|
1013
|
+
expect(rules).toContain('card-inset')
|
|
917
1014
|
})
|
|
918
1015
|
|
|
919
1016
|
// ── REJECTED — a bare file declaration, and a typo'd id ────────────────────
|
|
@@ -921,63 +1018,24 @@ export const C = () => (
|
|
|
921
1018
|
it('a BARE theme-allow-file waives nothing — file scope must name its ids', () => {
|
|
922
1019
|
const { code, rules } = twoNodes('{/* theme-allow-file — everything in here is bespoke */}')
|
|
923
1020
|
expect(code).toBe(1)
|
|
924
|
-
expect(rules).toContain('
|
|
1021
|
+
expect(rules).toContain('card-inset')
|
|
925
1022
|
})
|
|
926
1023
|
|
|
927
1024
|
it("a theme-allow-file naming a typo'd id waives nothing", () => {
|
|
928
|
-
const { rules } = twoNodes('{/* theme-allow-file
|
|
929
|
-
expect(rules).toContain('
|
|
1025
|
+
const { rules } = twoNodes('{/* theme-allow-file card-insett — a reason */}')
|
|
1026
|
+
expect(rules).toContain('card-inset')
|
|
930
1027
|
})
|
|
931
1028
|
|
|
932
1029
|
it('a theme-allow-file scoped to a DIFFERENT rule does not waive this one', () => {
|
|
933
1030
|
const { rules } = twoNodes('{/* theme-allow-file raw-hex — the swatch is a measured pixel */}')
|
|
934
|
-
expect(rules).toContain('
|
|
1031
|
+
expect(rules).toContain('card-inset')
|
|
935
1032
|
})
|
|
936
1033
|
|
|
937
1034
|
// ── REJECTED — the token inside a string literal, not a comment ────────────
|
|
938
1035
|
|
|
939
1036
|
it('the token inside a string literal is code, not an annotation', () => {
|
|
940
|
-
const { rules } = twoNodes("{'theme-allow
|
|
941
|
-
expect(rules).toContain('
|
|
942
|
-
})
|
|
943
|
-
})
|
|
944
|
-
|
|
945
|
-
// ── chart-legend-literal ─────────────────────────────────────────────────────
|
|
946
|
-
|
|
947
|
-
describe('basalt/chart-legend-literal', () => {
|
|
948
|
-
it('flags a hand-authored items array', () => {
|
|
949
|
-
const { code, rules } = run(
|
|
950
|
-
`${CHARTS_IMPORT}export const C = () => <ChartLegend items={[{ key: 'a', label: 'A', color: 'red' }]} />\n`,
|
|
951
|
-
)
|
|
952
|
-
expect(code).toBe(1)
|
|
953
|
-
expect(rules).toContain('chart-legend-literal')
|
|
954
|
-
})
|
|
955
|
-
|
|
956
|
-
it('does NOT flag items derived from the series descriptor', () => {
|
|
957
|
-
const { code, rules } = run(
|
|
958
|
-
`${CHARTS_IMPORT}export const C = () => <ChartLegend items={deriveLegend(series)} />\n`,
|
|
959
|
-
)
|
|
960
|
-
expect(code).toBe(0)
|
|
961
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
962
|
-
})
|
|
963
|
-
|
|
964
|
-
it('does NOT flag a derived-and-extended list — that is legitimate composition', () => {
|
|
965
|
-
const { code, rules } = run(
|
|
966
|
-
`${CHARTS_IMPORT}export const C = () => <ChartLegend items={[...deriveLegend(series), note]} />\n`,
|
|
967
|
-
)
|
|
968
|
-
expect(code).toBe(0)
|
|
969
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
it('honors a theme-allow escape', () => {
|
|
973
|
-
const { code, rules } = run(
|
|
974
|
-
`${CHARTS_IMPORT}export const C = () => (
|
|
975
|
-
// theme-allow: static reference legend, no series behind it
|
|
976
|
-
<ChartLegend items={[{ key: 'a', label: 'A', color: 'red' }]} />
|
|
977
|
-
)\n`,
|
|
978
|
-
)
|
|
979
|
-
expect(code).toBe(0)
|
|
980
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
1037
|
+
const { rules } = twoNodes("{'theme-allow card-inset — a reason'}")
|
|
1038
|
+
expect(rules).toContain('card-inset')
|
|
981
1039
|
})
|
|
982
1040
|
})
|
|
983
1041
|
|
|
@@ -1012,7 +1070,7 @@ describe('basalt/shadow-basalt-export', () => {
|
|
|
1012
1070
|
})
|
|
1013
1071
|
|
|
1014
1072
|
it('sees the content layer too', () => {
|
|
1015
|
-
const { rules } = run(`export function
|
|
1073
|
+
const { rules } = run(`export function Callout() { return null }\n`)
|
|
1016
1074
|
expect(rules).toContain('shadow-basalt-export')
|
|
1017
1075
|
})
|
|
1018
1076
|
|
|
@@ -1413,32 +1471,26 @@ describe('PLUGIN_RULE_GRACE', () => {
|
|
|
1413
1471
|
}
|
|
1414
1472
|
).version
|
|
1415
1473
|
const graceEntries = Object.entries(PLUGIN_RULE_GRACE) as [string, GraceEntry][]
|
|
1416
|
-
const advisoryEntries = Object.entries(PLUGIN_RULE_ADVISORY) as [
|
|
1417
|
-
string,
|
|
1418
|
-
{ since: string; why: string },
|
|
1419
|
-
][]
|
|
1420
1474
|
const graceIds = graceEntries.map(([id]) => id)
|
|
1421
|
-
const advisoryIds = advisoryEntries.map(([id]) => id)
|
|
1422
1475
|
const shippedBasaltIds = Object.keys(shipped.rules).filter((id) => id.startsWith('basalt/'))
|
|
1423
1476
|
|
|
1424
1477
|
// The mechanism whose ABSENCE let three rules sit at `warn` for up to twelve minors with nothing
|
|
1425
1478
|
// tracking them. Deleting a ledger entry IS the promotion, and this test makes flipping the
|
|
1426
1479
|
// shipped level part of the same commit.
|
|
1427
|
-
it('every grace
|
|
1428
|
-
for (const id of
|
|
1429
|
-
expect(shipped.rules[`basalt/${id}`]).toBe('warn')
|
|
1480
|
+
it('every grace entry is warn in the shipped preset', () => {
|
|
1481
|
+
for (const id of graceIds) expect(shipped.rules[`basalt/${id}`]).toBe('warn')
|
|
1430
1482
|
})
|
|
1431
1483
|
|
|
1432
|
-
it('every shipped rule NOT in
|
|
1484
|
+
it('every shipped rule NOT in the ledger is error', () => {
|
|
1433
1485
|
for (const id of shippedBasaltIds) {
|
|
1434
1486
|
const bareId = id.slice('basalt/'.length)
|
|
1435
|
-
if (graceIds.includes(bareId)
|
|
1487
|
+
if (graceIds.includes(bareId)) continue
|
|
1436
1488
|
expect([id, shipped.rules[id]]).toEqual([id, 'error'])
|
|
1437
1489
|
}
|
|
1438
1490
|
})
|
|
1439
1491
|
|
|
1440
1492
|
it('every entry carries a written why', () => {
|
|
1441
|
-
for (const [id, entry] of
|
|
1493
|
+
for (const [id, entry] of graceEntries) {
|
|
1442
1494
|
if (entry.why.length <= 40)
|
|
1443
1495
|
throw new Error(`${id}: \`why\` must be longer than 40 characters.`)
|
|
1444
1496
|
expect(entry.why.length).toBeGreaterThan(40)
|
|
@@ -1446,8 +1498,8 @@ describe('PLUGIN_RULE_GRACE', () => {
|
|
|
1446
1498
|
})
|
|
1447
1499
|
|
|
1448
1500
|
// The C16 version gate, run against the REAL ledger and REAL package version — covers `since` <
|
|
1449
|
-
// `promote` and "no entry has reached its promote version" in one call.
|
|
1450
|
-
//
|
|
1501
|
+
// `promote` and "no entry has reached its promote version" in one call.
|
|
1502
|
+
// (see `assertGraceLedger`'s synthetic coverage above for the gate itself).
|
|
1451
1503
|
it('passes the C16 gate against the real ledger and package version', () => {
|
|
1452
1504
|
expect(() => assertGraceLedger(Object.fromEntries(graceEntries), pkgVersion)).not.toThrow()
|
|
1453
1505
|
})
|
|
@@ -1477,47 +1529,6 @@ describe('PLUGIN_RULE_GRACE', () => {
|
|
|
1477
1529
|
})
|
|
1478
1530
|
})
|
|
1479
1531
|
|
|
1480
|
-
// ── chart-legend-literal: the .map() half ────────────────────────────────────
|
|
1481
|
-
|
|
1482
|
-
describe('basalt/chart-legend-literal — derived-from-series', () => {
|
|
1483
|
-
const IMPORT = `import { ChartLegend } from 'basalt-ui/charts'\n`
|
|
1484
|
-
|
|
1485
|
-
// Deriving from *an* array is not deriving from *the* series: every field of a
|
|
1486
|
-
// `refLines.map(...)` / `PACE_ZONES.map(...)` legend is authored at the call site, and the list
|
|
1487
|
-
// can keep naming a band the plot no longer draws exactly like a `[...]` literal can.
|
|
1488
|
-
it('flags a .map() over a non-series array', () => {
|
|
1489
|
-
const { code, rules } = run(
|
|
1490
|
-
`${IMPORT}export const C = () => <ChartLegend items={refLines.map((r) => ({ key: r.k, label: r.l }))} />\n`,
|
|
1491
|
-
)
|
|
1492
|
-
expect(code).toBe(1)
|
|
1493
|
-
expect(rules).toContain('chart-legend-literal')
|
|
1494
|
-
})
|
|
1495
|
-
|
|
1496
|
-
it('does NOT flag a .map() over the series', () => {
|
|
1497
|
-
const { code, rules } = run(
|
|
1498
|
-
`${IMPORT}export const C = () => <ChartLegend items={series.map((s) => ({ key: s.key }))} />\n`,
|
|
1499
|
-
)
|
|
1500
|
-
expect(code).toBe(0)
|
|
1501
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
1502
|
-
})
|
|
1503
|
-
|
|
1504
|
-
it('does NOT flag a .map() over a derived series binding', () => {
|
|
1505
|
-
const { code, rules } = run(
|
|
1506
|
-
`${IMPORT}export const C = () => <ChartLegend items={visibleSeries.map((s) => ({ key: s.key }))} />\n`,
|
|
1507
|
-
)
|
|
1508
|
-
expect(code).toBe(0)
|
|
1509
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
1510
|
-
})
|
|
1511
|
-
|
|
1512
|
-
it('does NOT flag deriveLegend(series)', () => {
|
|
1513
|
-
const { code, rules } = run(
|
|
1514
|
-
`${IMPORT}export const C = () => <ChartLegend items={deriveLegend(series)} />\n`,
|
|
1515
|
-
)
|
|
1516
|
-
expect(code).toBe(0)
|
|
1517
|
-
expect(rules).not.toContain('chart-legend-literal')
|
|
1518
|
-
})
|
|
1519
|
-
})
|
|
1520
|
-
|
|
1521
1532
|
// ── no-raw-font-size: style-context scoping ──────────────────────────────────
|
|
1522
1533
|
|
|
1523
1534
|
describe('basalt/no-raw-font-size — style context', () => {
|
|
@@ -2089,7 +2100,7 @@ describe('basalt/control-outside-home', () => {
|
|
|
2089
2100
|
})
|
|
2090
2101
|
|
|
2091
2102
|
// The SECOND dialect: a repo mandating `PascalCase.tsx` for component files (basalt's own root
|
|
2092
|
-
//
|
|
2103
|
+
// AGENTS.md included) can never write `foo-panel.tsx`, so the kebab form alone exempted nothing
|
|
2093
2104
|
// there — which is how the CBBI panel collected four warns for a `<PageAside>` in its parent.
|
|
2094
2105
|
it.each(['src/EditSessionModal.tsx', 'src/FiltersDrawer.tsx', 'src/CbbiPanel.tsx'])(
|
|
2095
2106
|
'does NOT flag %s — the PascalCase dialect of the same convention',
|
|
@@ -2602,118 +2613,6 @@ describe('CTL_THEME_TAGS ↔ CTL_THEME', () => {
|
|
|
2602
2613
|
})
|
|
2603
2614
|
})
|
|
2604
2615
|
|
|
2605
|
-
describe('basalt/page-bar-budget', () => {
|
|
2606
|
-
it('flags two PageBars in the SAME returned tree — the pair that mounts together', () => {
|
|
2607
|
-
const { code, rules } = run(
|
|
2608
|
-
`${BASALT_IMPORT}export const C = () => (\n <>\n <PageBar title="A" />\n <PageBar title="B" />\n </>\n)\n`,
|
|
2609
|
-
)
|
|
2610
|
-
expect(code).toBe(1)
|
|
2611
|
-
expect(rules).toContain('page-bar-budget')
|
|
2612
|
-
})
|
|
2613
|
-
|
|
2614
|
-
// The two false positives the per-FILE count produced. Neither renders two bars at once, so
|
|
2615
|
-
// nothing races for the portal node — and the only escape was a theme-allow on correct code.
|
|
2616
|
-
it('does NOT flag an early-return loading state and the loaded bar', () => {
|
|
2617
|
-
const { code, rules } = run(
|
|
2618
|
-
`${BASALT_IMPORT}export const C = ({ data }) => {\n if (!data) return <PageBar title="Jobs" />\n return <PageBar title="Jobs" actions={{ primary: { key: 'r', label: 'Run' } }} />\n}\n`,
|
|
2619
|
-
)
|
|
2620
|
-
expect(code).toBe(0)
|
|
2621
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2622
|
-
})
|
|
2623
|
-
|
|
2624
|
-
it('does NOT flag two page components exported from one file', () => {
|
|
2625
|
-
const { code, rules } = run(
|
|
2626
|
-
`${BASALT_IMPORT}export const ListPage = () => <PageBar title="Jobs" />\nexport const DetailPage = () => <PageBar title="Job" />\n`,
|
|
2627
|
-
)
|
|
2628
|
-
expect(code).toBe(0)
|
|
2629
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2630
|
-
})
|
|
2631
|
-
|
|
2632
|
-
it("does NOT flag a consumer's own PageBar-named component", () => {
|
|
2633
|
-
const { code, rules } = run(
|
|
2634
|
-
`import { PageBar } from './chrome/page-bar'\nexport const C = () => (\n <>\n <PageBar title="A" />\n <PageBar title="B" />\n </>\n)\n`,
|
|
2635
|
-
)
|
|
2636
|
-
expect(code).toBe(0)
|
|
2637
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2638
|
-
})
|
|
2639
|
-
|
|
2640
|
-
it('flags a 5-entry actions.secondary array', () => {
|
|
2641
|
-
const five = [1, 2, 3, 4, 5].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
|
|
2642
|
-
const { code, rules } = run(
|
|
2643
|
-
`${BASALT_IMPORT}export const C = () => <PageBar actions={{ secondary: [${five}] }} />\n`,
|
|
2644
|
-
)
|
|
2645
|
-
expect(code).toBe(1)
|
|
2646
|
-
expect(rules).toContain('page-bar-budget')
|
|
2647
|
-
})
|
|
2648
|
-
|
|
2649
|
-
it('does NOT flag a 4-entry actions.secondary array', () => {
|
|
2650
|
-
const four = [1, 2, 3, 4].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
|
|
2651
|
-
const { code, rules } = run(
|
|
2652
|
-
`${BASALT_IMPORT}export const C = () => <PageBar actions={{ secondary: [${four}] }} />\n`,
|
|
2653
|
-
)
|
|
2654
|
-
expect(code).toBe(0)
|
|
2655
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2656
|
-
})
|
|
2657
|
-
|
|
2658
|
-
it('flags a 4-entry Section actions array (budget 3)', () => {
|
|
2659
|
-
const four = [1, 2, 3, 4].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
|
|
2660
|
-
const { rules } = run(
|
|
2661
|
-
`${BASALT_IMPORT}export const C = () => <Section actions={[${four}]} />\n`,
|
|
2662
|
-
)
|
|
2663
|
-
expect(rules).toContain('page-bar-budget')
|
|
2664
|
-
})
|
|
2665
|
-
|
|
2666
|
-
it('flags a second variant="filled" inside one slot', () => {
|
|
2667
|
-
const { rules } = run(
|
|
2668
|
-
`${BASALT_IMPORT}${MANTINE_IMPORT}export const C = () => (\n <PageBar actions={<><Button variant="filled">A</Button><Button variant="filled">B</Button></>} />\n)\n`,
|
|
2669
|
-
)
|
|
2670
|
-
expect(rules).toContain('page-bar-budget')
|
|
2671
|
-
})
|
|
2672
|
-
|
|
2673
|
-
it('does NOT flag one filled action beside a default one', () => {
|
|
2674
|
-
const { code, rules } = run(
|
|
2675
|
-
`${BASALT_IMPORT}${MANTINE_IMPORT}export const C = () => (\n <PageBar actions={<><Button variant="filled">A</Button><Button variant="default">B</Button></>} />\n)\n`,
|
|
2676
|
-
)
|
|
2677
|
-
expect(code).toBe(0)
|
|
2678
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2679
|
-
})
|
|
2680
|
-
|
|
2681
|
-
// The `filled` count is a PRIMARY-ACTION count: a filled Badge is Mantine's default Badge
|
|
2682
|
-
// variant and the ordinary way a header states a count (law C11 asks for one), and an overlay's
|
|
2683
|
-
// submit button is not in the bar at all.
|
|
2684
|
-
it('does NOT flag a filled Badge beside the one filled Button', () => {
|
|
2685
|
-
const { code, rules } = run(
|
|
2686
|
-
`${BASALT_IMPORT}import { Badge, Button } from '@mantine/core'\n` +
|
|
2687
|
-
`export const C = () => (\n <Section title="T" actions={<><Button variant="filled">Run</Button><Badge variant="filled">3</Badge></>} />\n)\n`,
|
|
2688
|
-
)
|
|
2689
|
-
expect(code).toBe(0)
|
|
2690
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2691
|
-
})
|
|
2692
|
-
|
|
2693
|
-
it('does NOT flag a filled ThemeIcon beside the one filled Button', () => {
|
|
2694
|
-
const { rules } = run(
|
|
2695
|
-
`${BASALT_IMPORT}import { Button, ThemeIcon } from '@mantine/core'\n` +
|
|
2696
|
-
`export const C = () => (\n <Section title="T" actions={<><Button variant="filled">Run</Button><ThemeIcon variant="filled" /></>} />\n)\n`,
|
|
2697
|
-
)
|
|
2698
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2699
|
-
})
|
|
2700
|
-
|
|
2701
|
-
it("does NOT flag a Modal's own filled submit button colocated in the slot", () => {
|
|
2702
|
-
const { rules } = run(
|
|
2703
|
-
`${BASALT_IMPORT}import { Button, Modal } from '@mantine/core'\n` +
|
|
2704
|
-
`export const C = () => (\n <PageBar actions={<><Button variant="filled">New</Button><Modal opened><Button variant="filled">Save</Button></Modal></>} />\n)\n`,
|
|
2705
|
-
)
|
|
2706
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2707
|
-
})
|
|
2708
|
-
|
|
2709
|
-
it('honors a scoped theme-allow', () => {
|
|
2710
|
-
const { rules } = run(
|
|
2711
|
-
`${BASALT_IMPORT}export const C = () => (\n <>\n <PageBar title="A" />\n {/* theme-allow page-bar-budget — the print-only variant */}\n <PageBar title="B" />\n </>\n)\n`,
|
|
2712
|
-
)
|
|
2713
|
-
expect(rules).not.toContain('page-bar-budget')
|
|
2714
|
-
})
|
|
2715
|
-
})
|
|
2716
|
-
|
|
2717
2616
|
describe('basalt/in-body-page-title', () => {
|
|
2718
2617
|
it.each([1, 2])('flags <Title order={%i}> in the body', (order) => {
|
|
2719
2618
|
const { code, rules } = run(`export const C = () => <Title order={${order}}>Page</Title>\n`)
|
|
@@ -3200,95 +3099,6 @@ describe('PLUGIN_RULE_ID_LIST ↔ the plugin', () => {
|
|
|
3200
3099
|
})
|
|
3201
3100
|
})
|
|
3202
3101
|
|
|
3203
|
-
// ── provider-above-router (F5) ───────────────────────────────────────────────
|
|
3204
|
-
|
|
3205
|
-
describe('basalt/provider-above-router', () => {
|
|
3206
|
-
const IMPORTS =
|
|
3207
|
-
`import { BasaltProvider } from 'basalt-ui'\n` +
|
|
3208
|
-
`import { RouterProvider } from '@tanstack/react-router'\n`
|
|
3209
|
-
|
|
3210
|
-
it('flags a BasaltProvider rendered inside a RouterProvider', () => {
|
|
3211
|
-
const { code, rules } = run(
|
|
3212
|
-
`${IMPORTS}export const App = () => (\n` +
|
|
3213
|
-
` <RouterProvider router={r}>\n <BasaltProvider>{null}</BasaltProvider>\n` +
|
|
3214
|
-
` </RouterProvider>\n)\n`,
|
|
3215
|
-
)
|
|
3216
|
-
expect(code).toBe(1)
|
|
3217
|
-
expect(rules).toContain('provider-above-router')
|
|
3218
|
-
})
|
|
3219
|
-
|
|
3220
|
-
it('does NOT flag the correct order — provider above router', () => {
|
|
3221
|
-
const { code, rules } = run(
|
|
3222
|
-
`${IMPORTS}export const App = () => (\n` +
|
|
3223
|
-
` <BasaltProvider>\n <RouterProvider router={r} />\n </BasaltProvider>\n)\n`,
|
|
3224
|
-
)
|
|
3225
|
-
expect(code).toBe(0)
|
|
3226
|
-
expect(rules).not.toContain('provider-above-router')
|
|
3227
|
-
})
|
|
3228
|
-
|
|
3229
|
-
// Provenance both ways: the tag names alone are not the finding.
|
|
3230
|
-
it("does NOT flag a consumer's own BasaltProvider (no basalt import)", () => {
|
|
3231
|
-
const { code, rules } = run(
|
|
3232
|
-
`import { RouterProvider } from '@tanstack/react-router'\n` +
|
|
3233
|
-
`import { BasaltProvider } from './my-provider'\n` +
|
|
3234
|
-
`export const App = () => (\n <RouterProvider router={r}>\n` +
|
|
3235
|
-
` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
|
|
3236
|
-
)
|
|
3237
|
-
expect(code).toBe(0)
|
|
3238
|
-
expect(rules).not.toContain('provider-above-router')
|
|
3239
|
-
})
|
|
3240
|
-
|
|
3241
|
-
it('does NOT flag a RouterProvider that is not TanStack s', () => {
|
|
3242
|
-
const { code, rules } = run(
|
|
3243
|
-
`import { BasaltProvider } from 'basalt-ui'\n` +
|
|
3244
|
-
`import { RouterProvider } from 'other-router'\n` +
|
|
3245
|
-
`export const App = () => (\n <RouterProvider router={r}>\n` +
|
|
3246
|
-
` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
|
|
3247
|
-
)
|
|
3248
|
-
expect(code).toBe(0)
|
|
3249
|
-
expect(rules).not.toContain('provider-above-router')
|
|
3250
|
-
})
|
|
3251
|
-
|
|
3252
|
-
// The alias is why the ancestry is read by BINDING and not by written tag name.
|
|
3253
|
-
it('flags through an aliased RouterProvider import', () => {
|
|
3254
|
-
const { code, rules } = run(
|
|
3255
|
-
`import { BasaltProvider } from 'basalt-ui'\n` +
|
|
3256
|
-
`import { RouterProvider as Router } from '@tanstack/react-router'\n` +
|
|
3257
|
-
`export const App = () => (\n <Router router={r}>\n` +
|
|
3258
|
-
` <BasaltProvider>{null}</BasaltProvider>\n </Router>\n)\n`,
|
|
3259
|
-
)
|
|
3260
|
-
expect(code).toBe(1)
|
|
3261
|
-
expect(rules).toContain('provider-above-router')
|
|
3262
|
-
})
|
|
3263
|
-
|
|
3264
|
-
it('honours a theme-allow naming the rule', () => {
|
|
3265
|
-
const { code, rules } = run(
|
|
3266
|
-
`${IMPORTS}export const App = () => (\n <RouterProvider router={r}>\n` +
|
|
3267
|
-
` {/* theme-allow provider-above-router — legacy bootstrap, ported next minor */}\n` +
|
|
3268
|
-
` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
|
|
3269
|
-
)
|
|
3270
|
-
expect(code).toBe(0)
|
|
3271
|
-
expect(rules).not.toContain('provider-above-router')
|
|
3272
|
-
})
|
|
3273
|
-
|
|
3274
|
-
// The documented limit of the heuristic — stated in the rule's own JSDoc, pinned here so a
|
|
3275
|
-
// future widening has to move this test rather than discover the gap.
|
|
3276
|
-
it('does NOT see a cross-file composition (static JSX ancestry only)', () => {
|
|
3277
|
-
const { code, rules } = run(
|
|
3278
|
-
`import { BasaltProvider } from 'basalt-ui'\n` +
|
|
3279
|
-
`import { Routes } from './routes'\n` +
|
|
3280
|
-
`export const App = () => (\n <Routes>\n <BasaltProvider>{null}</BasaltProvider>\n` +
|
|
3281
|
-
` </Routes>\n)\n`,
|
|
3282
|
-
)
|
|
3283
|
-
expect(code).toBe(0)
|
|
3284
|
-
expect(rules).not.toContain('provider-above-router')
|
|
3285
|
-
})
|
|
3286
|
-
})
|
|
3287
|
-
|
|
3288
|
-
// `basalt/query-dual-import` is RETIRED (`RETIRED_RULE_IDS`) — its own describe block was removed
|
|
3289
|
-
// along with the rule; see `KNOWN_RULE_IDS` below for the coverage that remains: it is still a
|
|
3290
|
-
// valid (dead) `theme-allow` id, never a typo.
|
|
3291
|
-
|
|
3292
3102
|
// ── query-fn-unwrap (F5) ─────────────────────────────────────────────────────
|
|
3293
3103
|
|
|
3294
3104
|
describe('basalt/query-fn-unwrap', () => {
|
|
@@ -3358,71 +3168,6 @@ describe('basalt/query-fn-unwrap', () => {
|
|
|
3358
3168
|
})
|
|
3359
3169
|
})
|
|
3360
3170
|
|
|
3361
|
-
// ── deprecated-export (B4) ───────────────────────────────────────────────────
|
|
3362
|
-
|
|
3363
|
-
describe('basalt/deprecated-export', () => {
|
|
3364
|
-
it('reports a removed row as gone, never as "still resolves"', () => {
|
|
3365
|
-
const { code, rules, output } = run(
|
|
3366
|
-
`import { useBreakpoint } from 'basalt-ui'\nexport const x = useBreakpoint\n`,
|
|
3367
|
-
)
|
|
3368
|
-
expect(code).toBe(1)
|
|
3369
|
-
expect(rules).toContain('deprecated-export')
|
|
3370
|
-
expect(output).toContain('was removed in 1.34.0')
|
|
3371
|
-
expect(output).not.toContain('still resolves')
|
|
3372
|
-
})
|
|
3373
|
-
|
|
3374
|
-
// The row no longer says `fix: false`; `removed` alone must suppress the rewrite, because a
|
|
3375
|
-
// removed row's replacement is prose (`useSizeClass() for shell chrome; …`), not a name.
|
|
3376
|
-
it('never autofixes a removed row', () => {
|
|
3377
|
-
const source = `import { useBreakpoint } from 'basalt-ui'\nexport const x = useBreakpoint\n`
|
|
3378
|
-
writeFileSync(resolve(dir, 'removed-fix.tsx'), source)
|
|
3379
|
-
Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'removed-fix.tsx'], { cwd: dir })
|
|
3380
|
-
expect(readFileSync(resolve(dir, 'removed-fix.tsx'), 'utf8')).toBe(source)
|
|
3381
|
-
})
|
|
3382
|
-
})
|
|
3383
|
-
|
|
3384
|
-
// 1.36.0: the form layout moved `basalt-ui/forms` → `basalt-ui/controls`. A move BETWEEN subpaths is
|
|
3385
|
-
// not a drop-in rename — the autofix only rewrites the specifier inside its own import statement —
|
|
3386
|
-
// so these rows carry `fix: false` and the message names the new home.
|
|
3387
|
-
describe('basalt/deprecated-export — the form layout move', () => {
|
|
3388
|
-
it.each(['FormSection', 'FormRow', 'FormGroup', 'FormActions'])(
|
|
3389
|
-
'reports %s from basalt-ui/forms and names basalt-ui/controls',
|
|
3390
|
-
(name) => {
|
|
3391
|
-
const { rules, output } = run(
|
|
3392
|
-
`import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
|
|
3393
|
-
)
|
|
3394
|
-
expect(rules).toContain('deprecated-export')
|
|
3395
|
-
expect(output).toContain(`'basalt-ui/controls'`)
|
|
3396
|
-
expect(output).toContain('was removed in 1.39.0')
|
|
3397
|
-
},
|
|
3398
|
-
)
|
|
3399
|
-
|
|
3400
|
-
it.each(['useForm', 'schemaResolver'])('reports %s from basalt-ui/forms', (name) => {
|
|
3401
|
-
const { rules, output } = run(
|
|
3402
|
-
`import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
|
|
3403
|
-
)
|
|
3404
|
-
expect(rules).toContain('deprecated-export')
|
|
3405
|
-
expect(output).toContain(`'@mantine/form'`)
|
|
3406
|
-
expect(output).toContain('was removed in 1.39.0')
|
|
3407
|
-
})
|
|
3408
|
-
|
|
3409
|
-
it('does NOT report FormRow imported from basalt-ui/controls', () => {
|
|
3410
|
-
const { rules } = run(
|
|
3411
|
-
`import { FormRow } from 'basalt-ui/controls'\nexport const x = FormRow\n`,
|
|
3412
|
-
)
|
|
3413
|
-
expect(rules).not.toContain('deprecated-export')
|
|
3414
|
-
})
|
|
3415
|
-
|
|
3416
|
-
it('never autofixes a cross-subpath row', () => {
|
|
3417
|
-
const source = `import { FormRow } from 'basalt-ui/forms'\nexport const x = FormRow\n`
|
|
3418
|
-
writeFileSync(resolve(dir, 'form-layout-fix.tsx'), source)
|
|
3419
|
-
Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'form-layout-fix.tsx'], {
|
|
3420
|
-
cwd: dir,
|
|
3421
|
-
})
|
|
3422
|
-
expect(readFileSync(resolve(dir, 'form-layout-fix.tsx'), 'utf8')).toBe(source)
|
|
3423
|
-
})
|
|
3424
|
-
})
|
|
3425
|
-
|
|
3426
3171
|
// ── forms-field-key ──────────────────────────────────────────────────────────
|
|
3427
3172
|
|
|
3428
3173
|
describe('basalt/forms-field-key', () => {
|
|
@@ -4069,90 +3814,302 @@ describe('control homes — lane parity', () => {
|
|
|
4069
3814
|
})
|
|
4070
3815
|
})
|
|
4071
3816
|
|
|
4072
|
-
// ── the
|
|
3817
|
+
// ── agent-no-raw-usechat — the `Chat` class ──────────────────────────────────
|
|
4073
3818
|
|
|
4074
|
-
describe('
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
)
|
|
3819
|
+
describe('basalt/agent-no-raw-usechat — Chat', () => {
|
|
3820
|
+
it.each(['@ai-sdk/react', 'ai/react'])('flags `Chat` imported from %s', (source) => {
|
|
3821
|
+
const { code, rules } = run(`import { Chat } from '${source}'\n`, 'lib.ts')
|
|
3822
|
+
expect(code).toBe(1)
|
|
3823
|
+
expect(rules).toContain('agent-no-raw-usechat')
|
|
3824
|
+
})
|
|
4080
3825
|
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
readFileSync(resolve(import.meta.dirname, '..', 'scripts', 'export-surface.json'), 'utf8'),
|
|
4085
|
-
) as Record<string, string[]>
|
|
4086
|
-
const shipped = (row: (typeof DEPRECATED_EXPORTS)[number]) =>
|
|
4087
|
-
surface[row.subpath === 'basalt-ui' ? '.' : row.subpath.replace('basalt-ui', '.')]?.includes(
|
|
4088
|
-
row.name,
|
|
4089
|
-
) === true
|
|
4090
|
-
|
|
4091
|
-
it('carries a replacement and a well-formed removeIn on every row', () => {
|
|
4092
|
-
for (const row of DEPRECATED_EXPORTS) {
|
|
4093
|
-
expect([row.name, row.replacement.length > 0]).toEqual([row.name, true])
|
|
4094
|
-
expect([row.name, /^\d+\.\d+\.\d+$/.test(row.removeIn)]).toEqual([row.name, true])
|
|
4095
|
-
}
|
|
3826
|
+
it('does NOT flag a type-only `Chat` import', () => {
|
|
3827
|
+
const { code } = run(`import type { Chat } from '@ai-sdk/react'\n`, 'lib.ts')
|
|
3828
|
+
expect(code).toBe(0)
|
|
4096
3829
|
})
|
|
4097
3830
|
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
3831
|
+
it.each([
|
|
3832
|
+
['AI.useChat()', `const c = AI.useChat({ id })\n`],
|
|
3833
|
+
['AI.Chat as a value', `const C = AI.Chat\n`],
|
|
3834
|
+
['new AI.Chat()', `const c = new AI.Chat({ id })\n`],
|
|
3835
|
+
])('flags %s through a namespace import', (_name, use) => {
|
|
3836
|
+
const { code, rules } = run(`import * as AI from '@ai-sdk/react'\n${use}`, 'lib.ts')
|
|
3837
|
+
expect(code).toBe(1)
|
|
3838
|
+
expect(rules).toContain('agent-no-raw-usechat')
|
|
3839
|
+
})
|
|
3840
|
+
|
|
3841
|
+
it('flags a namespace import of ai/react', () => {
|
|
3842
|
+
const { code } = run(`import * as AI from 'ai/react'\nAI.useChat()\n`, 'lib.ts')
|
|
3843
|
+
expect(code).toBe(1)
|
|
3844
|
+
})
|
|
3845
|
+
|
|
3846
|
+
it('does NOT flag a namespace member that is not a chat hook, a local namespace, or a type import', () => {
|
|
3847
|
+
expect(run(`import * as AI from '@ai-sdk/react'\nAI.somethingElse()\n`, 'lib.ts').code).toBe(0)
|
|
3848
|
+
expect(run(`import * as AI from './mine'\nAI.useChat()\n`, 'lib.ts').code).toBe(0)
|
|
3849
|
+
expect(run(`import type * as AI from '@ai-sdk/react'\nAI.useChat()\n`, 'lib.ts').code).toBe(0)
|
|
3850
|
+
})
|
|
3851
|
+
|
|
3852
|
+
it.each([
|
|
3853
|
+
`export { Chat } from '@ai-sdk/react'\n`,
|
|
3854
|
+
`export { useChat as chat } from 'ai/react'\n`,
|
|
3855
|
+
`export * from '@ai-sdk/react'\n`,
|
|
3856
|
+
])('flags the re-export %s', (source) => {
|
|
3857
|
+
const { code, rules } = run(source, 'lib.ts')
|
|
3858
|
+
expect(code).toBe(1)
|
|
3859
|
+
expect(rules).toContain('agent-no-raw-usechat')
|
|
3860
|
+
})
|
|
3861
|
+
|
|
3862
|
+
it('does NOT flag a type re-export or an unrelated name', () => {
|
|
3863
|
+
expect(run(`export type { UIMessage } from '@ai-sdk/react'\n`, 'lib.ts').code).toBe(0)
|
|
3864
|
+
expect(run(`export { Other } from '@ai-sdk/react'\n`, 'lib.ts').code).toBe(0)
|
|
3865
|
+
})
|
|
3866
|
+
})
|
|
3867
|
+
|
|
3868
|
+
// ── no-dev-helper ────────────────────────────────────────────────────────────
|
|
3869
|
+
|
|
3870
|
+
describe('basalt/no-dev-helper', () => {
|
|
3871
|
+
const LAZY = `const Dock = lazy(() => import('../dev-dock'))\n`
|
|
3872
|
+
it.each([
|
|
3873
|
+
['an unguarded devtools import()', `const m = import('@tanstack/react-router-devtools')\n`],
|
|
3874
|
+
['an unguarded theme-lab import()', `export const load = () => import('./theme-lab')\n`],
|
|
3875
|
+
['an isDev() gate', `if (isDev()) import('./dev-dock')\n`],
|
|
3876
|
+
['an eager const import() with no gate', `const m = import('./theme-lab')\n`],
|
|
3877
|
+
[
|
|
3878
|
+
'an eager const import() even when a later use is gated',
|
|
3879
|
+
`const m = import('./theme-lab')\nif (process.env.NODE_ENV !== 'production') use(m)\n`,
|
|
3880
|
+
],
|
|
3881
|
+
[
|
|
3882
|
+
'an import() inside an anonymous callback',
|
|
3883
|
+
`useEffect(() => { void import('./dev-dock') }, [])\n`,
|
|
3884
|
+
],
|
|
3885
|
+
['a lazy binding rendered without a gate', `${LAZY}export const C = () => <Dock />\n`],
|
|
3886
|
+
[
|
|
3887
|
+
'a lazy binding with one ungated use',
|
|
3888
|
+
`${LAZY}export const C = () => (\n <>\n {process.env.NODE_ENV !== 'production' && <Dock />}\n <Dock />\n </>\n)\n`,
|
|
3889
|
+
],
|
|
3890
|
+
['a TS-wrapped ungated use', `const load = () => import('./dev-dock')\nload!()\n`],
|
|
3891
|
+
[
|
|
3892
|
+
'a gate that only holds in production',
|
|
3893
|
+
`const load = () => import('./dev-dock')\nif (ok && process.env.NODE_ENV === 'production') load()\n`,
|
|
3894
|
+
],
|
|
3895
|
+
[
|
|
3896
|
+
'a component that merely contains the import (its uses are not the dev uses)',
|
|
3897
|
+
`const App = () => { useEffect(() => { void import('./dev-dock') }, []); return null }\nexport const C = () => <App />\n`,
|
|
3898
|
+
],
|
|
3899
|
+
])('flags %s', (_name, source) => {
|
|
3900
|
+
const { code, rules } = run(source, 'lib.tsx')
|
|
3901
|
+
expect(code).toBe(1)
|
|
3902
|
+
expect(rules).toContain('no-dev-helper')
|
|
3903
|
+
})
|
|
3904
|
+
|
|
3905
|
+
it.each([
|
|
3906
|
+
[
|
|
3907
|
+
'an if-gate',
|
|
3908
|
+
`if (process.env.NODE_ENV !== 'production') {\n void import('@tanstack/react-query-devtools')\n}\n`,
|
|
3909
|
+
],
|
|
3910
|
+
[
|
|
3911
|
+
'the bracketed spelling',
|
|
3912
|
+
`if (process.env['NODE_ENV'] !== 'production') {\n void import('./dev-dock')\n}\n`,
|
|
3913
|
+
],
|
|
3914
|
+
[
|
|
3915
|
+
'=== development',
|
|
3916
|
+
`const m = process.env.NODE_ENV === 'development' ? import('./theme-lab') : null\n`,
|
|
3917
|
+
],
|
|
3918
|
+
[
|
|
3919
|
+
'the production alternate',
|
|
3920
|
+
`const m = process.env.NODE_ENV === 'production' ? null : import('./dev-dock')\n`,
|
|
3921
|
+
],
|
|
3922
|
+
[
|
|
3923
|
+
'a lazy binding used only under the gate',
|
|
3924
|
+
`${LAZY}export const C = () => process.env.NODE_ENV !== 'production' && <Dock />\n`,
|
|
3925
|
+
],
|
|
3926
|
+
[
|
|
3927
|
+
'a lazy binding whose name also appears as a property key, attribute and type',
|
|
3928
|
+
`${LAZY}type T = typeof Dock\nconst o = { Dock: 1 }\nexport const C = () => <X Dock="a" y={o.Dock}>{process.env.NODE_ENV !== 'production' && <Dock />}</X>\n`,
|
|
3929
|
+
],
|
|
3930
|
+
[
|
|
3931
|
+
'another binding that shadows the lazy name',
|
|
3932
|
+
`${LAZY}export const A = () => process.env.NODE_ENV !== 'production' && <Dock />\nexport const B = (Dock: unknown) => String(Dock)\n`,
|
|
3933
|
+
],
|
|
3934
|
+
[
|
|
3935
|
+
'a plain loader called under the gate',
|
|
3936
|
+
`const load = () => import('./dev-dock')\nif (process.env.NODE_ENV !== 'production') load()\n`,
|
|
3937
|
+
],
|
|
3938
|
+
[
|
|
3939
|
+
'a gate inside an && chain',
|
|
3940
|
+
`const load = () => import('./dev-dock')\nexport const f = (ok: boolean) => ok && process.env.NODE_ENV !== 'production' && load()\n`,
|
|
3941
|
+
],
|
|
3942
|
+
[
|
|
3943
|
+
'a negated production test',
|
|
3944
|
+
`const load = () => import('./dev-dock')\nif (!(process.env.NODE_ENV === 'production')) load()\n`,
|
|
3945
|
+
],
|
|
3946
|
+
[
|
|
3947
|
+
'a negated || test',
|
|
3948
|
+
`const load = () => import('./dev-dock')\nif (!(ok || process.env.NODE_ENV === 'production')) load()\n`,
|
|
3949
|
+
],
|
|
3950
|
+
[
|
|
3951
|
+
'the literal on the left',
|
|
3952
|
+
`const load = () => import('./dev-dock')\nif ('production' !== process.env.NODE_ENV) load()\n`,
|
|
3953
|
+
],
|
|
3954
|
+
[
|
|
3955
|
+
'TS-wrapped uses under the gate plus a class member of the same name',
|
|
3956
|
+
`${LAZY}class K { Dock = 1 }\nexport const C = () => process.env.NODE_ENV !== 'production' && (Dock as unknown as () => null)\n`,
|
|
3957
|
+
],
|
|
3958
|
+
['an unrelated import()', `const m = import('rehype-sanitize')\n`],
|
|
3959
|
+
])('does NOT flag %s', (_name, source) => {
|
|
3960
|
+
const { code, rules } = run(source, 'lib.tsx')
|
|
3961
|
+
expect(code).toBe(0)
|
|
3962
|
+
expect(rules).not.toContain('no-dev-helper')
|
|
4103
3963
|
})
|
|
4104
3964
|
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
3965
|
+
it('exempts the dev modules themselves', () => {
|
|
3966
|
+
const { code } = run(`const m = import('./theme-lab')\n`, 'src/dev-dock.tsx')
|
|
3967
|
+
expect(code).toBe(0)
|
|
3968
|
+
})
|
|
3969
|
+
})
|
|
3970
|
+
|
|
3971
|
+
// ── no-import-meta-env — scopes itself to basalt's own source ────────────────
|
|
3972
|
+
|
|
3973
|
+
describe('basalt/no-import-meta-env', () => {
|
|
3974
|
+
const PKG_ROOT = resolve(import.meta.dirname, '..')
|
|
3975
|
+
const visit = (filename: string): number => {
|
|
3976
|
+
const reports: unknown[] = []
|
|
3977
|
+
const visitor = basaltPlugin.rules['no-import-meta-env'].create({
|
|
3978
|
+
filename,
|
|
3979
|
+
sourceCode: { getAllComments: () => [] },
|
|
3980
|
+
report: (r: unknown) => reports.push(r),
|
|
3981
|
+
})
|
|
3982
|
+
const node = {
|
|
3983
|
+
type: 'MemberExpression',
|
|
3984
|
+
computed: false,
|
|
3985
|
+
property: { name: 'env' },
|
|
3986
|
+
object: { type: 'MetaProperty', meta: { name: 'import' }, property: { name: 'meta' } },
|
|
3987
|
+
loc: { start: { line: 1 } },
|
|
4111
3988
|
}
|
|
3989
|
+
visitor.MemberExpression?.(node)
|
|
3990
|
+
return reports.length
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
it('reports inside packages/basalt-ui/src', () => {
|
|
3994
|
+
expect(visit(`${PKG_ROOT}/src/x.ts`)).toBe(1)
|
|
3995
|
+
})
|
|
3996
|
+
|
|
3997
|
+
it('is silent elsewhere (the playground is a consumer) and in tests', () => {
|
|
3998
|
+
expect(visit(`${PKG_ROOT}/../../apps/playground/src/main.tsx`)).toBe(0)
|
|
3999
|
+
expect(visit(`${PKG_ROOT}/src/x.test.ts`)).toBe(0)
|
|
4000
|
+
})
|
|
4001
|
+
})
|
|
4002
|
+
|
|
4003
|
+
// ── route-view-transition ────────────────────────────────────────────────────
|
|
4004
|
+
|
|
4005
|
+
describe('basalt/route-view-transition', () => {
|
|
4006
|
+
const RR = `import { Outlet } from '@tanstack/react-router'\n`
|
|
4007
|
+
it.each([
|
|
4008
|
+
[
|
|
4009
|
+
'a named import',
|
|
4010
|
+
`import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
|
|
4011
|
+
],
|
|
4012
|
+
[
|
|
4013
|
+
'an aliased import',
|
|
4014
|
+
`import { ViewTransition as VT } from 'react'\n${RR}export const C = () => <VT><Outlet /></VT>\n`,
|
|
4015
|
+
],
|
|
4016
|
+
[
|
|
4017
|
+
'React.ViewTransition',
|
|
4018
|
+
`import React from 'react'\n${RR}export const C = () => <React.ViewTransition><Outlet /></React.ViewTransition>\n`,
|
|
4019
|
+
],
|
|
4020
|
+
[
|
|
4021
|
+
'a namespace import',
|
|
4022
|
+
`import * as R from 'react'\n${RR}export const C = () => <R.ViewTransition><Outlet /></R.ViewTransition>\n`,
|
|
4023
|
+
],
|
|
4024
|
+
[
|
|
4025
|
+
'an aliased Outlet, nested deep',
|
|
4026
|
+
`import { ViewTransition } from 'react'\nimport { Outlet as Page } from '@tanstack/react-router'\nexport const C = () => <ViewTransition><div><main><Page /></main></div></ViewTransition>\n`,
|
|
4027
|
+
],
|
|
4028
|
+
[
|
|
4029
|
+
'a nested ViewTransition (both are inert)',
|
|
4030
|
+
`import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><section><ViewTransition><Outlet /></ViewTransition></section></ViewTransition>\n`,
|
|
4031
|
+
],
|
|
4032
|
+
[
|
|
4033
|
+
'a namespace Outlet',
|
|
4034
|
+
`import { ViewTransition } from 'react'\nimport * as Router from '@tanstack/react-router'\nexport const C = () => <ViewTransition><Router.Outlet /></ViewTransition>\n`,
|
|
4035
|
+
],
|
|
4036
|
+
])('flags %s', (_name, source) => {
|
|
4037
|
+
const { code, rules } = run(source, 'lib.tsx')
|
|
4038
|
+
expect(code).toBe(1)
|
|
4039
|
+
expect(rules).toContain('route-view-transition')
|
|
4040
|
+
})
|
|
4041
|
+
|
|
4042
|
+
it.each([
|
|
4043
|
+
[
|
|
4044
|
+
'a ViewTransition with no Outlet',
|
|
4045
|
+
`import { ViewTransition } from 'react'\nexport const C = () => <ViewTransition><p /></ViewTransition>\n`,
|
|
4046
|
+
],
|
|
4047
|
+
[
|
|
4048
|
+
'an Outlet beside, not inside, a ViewTransition',
|
|
4049
|
+
`import { ViewTransition } from 'react'\n${RR}export const C = () => <><ViewTransition><p /></ViewTransition><Outlet /></>\n`,
|
|
4050
|
+
],
|
|
4051
|
+
[
|
|
4052
|
+
'a ViewTransition not from react',
|
|
4053
|
+
`import { ViewTransition } from './mine'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
|
|
4054
|
+
],
|
|
4055
|
+
[
|
|
4056
|
+
'an Outlet not from the router',
|
|
4057
|
+
`import { ViewTransition } from 'react'\nimport { Outlet } from './mine'\nexport const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
|
|
4058
|
+
],
|
|
4059
|
+
[
|
|
4060
|
+
'a type-only Outlet import',
|
|
4061
|
+
`import { ViewTransition } from 'react'\nimport type { Outlet } from '@tanstack/react-router'\nexport const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
|
|
4062
|
+
],
|
|
4063
|
+
])('does NOT flag %s', (_name, source) => {
|
|
4064
|
+
const { rules } = run(source, 'lib.tsx')
|
|
4065
|
+
expect(rules).not.toContain('route-view-transition')
|
|
4112
4066
|
})
|
|
4113
4067
|
|
|
4114
|
-
it('
|
|
4115
|
-
const
|
|
4116
|
-
(
|
|
4117
|
-
|
|
4118
|
-
exports: Record<string, unknown>
|
|
4119
|
-
}
|
|
4120
|
-
).exports,
|
|
4068
|
+
it('reports each enclosing ViewTransition once', () => {
|
|
4069
|
+
const { output } = run(
|
|
4070
|
+
`import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><ViewTransition><Outlet /></ViewTransition></ViewTransition>\n`,
|
|
4071
|
+
'lib.tsx',
|
|
4121
4072
|
)
|
|
4122
|
-
|
|
4123
|
-
const subpath = row.subpath === 'basalt-ui' ? '.' : row.subpath.replace('basalt-ui', '.')
|
|
4124
|
-
expect([row.subpath, exports.includes(subpath)]).toEqual([row.subpath, true])
|
|
4125
|
-
}
|
|
4073
|
+
expect(output.match(/basalt\/route-view-transition/g)).toHaveLength(2)
|
|
4126
4074
|
})
|
|
4127
4075
|
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4076
|
+
it('does not apply in test files', () => {
|
|
4077
|
+
const source = `import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`
|
|
4078
|
+
expect(run(source, 'lib.test.tsx').rules).not.toContain('route-view-transition')
|
|
4079
|
+
})
|
|
4080
|
+
|
|
4081
|
+
it('honours a theme-allow annotation naming the rule', () => {
|
|
4082
|
+
const source = `import { ViewTransition } from 'react'\n${RR}export const C = () => (\n // theme-allow route-view-transition: legacy page under migration\n <ViewTransition><Outlet /></ViewTransition>\n)\n`
|
|
4083
|
+
expect(run(source, 'lib.tsx').rules).not.toContain('route-view-transition')
|
|
4084
|
+
})
|
|
4085
|
+
})
|
|
4086
|
+
|
|
4087
|
+
describe('rule level locks', () => {
|
|
4088
|
+
const levels = (path: string) =>
|
|
4089
|
+
(JSON.parse(readFileSync(path, 'utf8')) as { rules: Record<string, unknown> }).rules
|
|
4090
|
+
const shipped = levels(resolve(import.meta.dirname, 'oxlint-basalt.json'))
|
|
4091
|
+
const repo = levels(resolve(import.meta.dirname, '..', '..', '..', '.oxlintrc.json'))
|
|
4092
|
+
|
|
4093
|
+
it('no-dev-helper is repo-local only, at error', () => {
|
|
4094
|
+
expect(shipped).not.toHaveProperty('basalt/no-dev-helper')
|
|
4095
|
+
expect(repo['basalt/no-dev-helper']).toBe('error')
|
|
4096
|
+
})
|
|
4097
|
+
})
|
|
4098
|
+
|
|
4099
|
+
// ── dogfood — /.oxlintrc.json ⊇ the shipped basalt/* levels ──────────────────
|
|
4100
|
+
|
|
4101
|
+
describe('/.oxlintrc.json dogfoods oxlint-basalt.json', () => {
|
|
4102
|
+
const rulesOf = (path: string) =>
|
|
4103
|
+
(JSON.parse(readFileSync(path, 'utf8')) as { rules: Record<string, unknown> }).rules
|
|
4104
|
+
const shipped = rulesOf(resolve(import.meta.dirname, 'oxlint-basalt.json'))
|
|
4105
|
+
const repo = rulesOf(resolve(import.meta.dirname, '..', '..', '..', '.oxlintrc.json'))
|
|
4106
|
+
|
|
4107
|
+
// Repo-local is the strict side: `error` for every shipped id, grace entries included (those
|
|
4108
|
+
// ship `warn`); every other id ships the very level the repo runs.
|
|
4109
|
+
it.each(Object.entries(shipped))('%s is error repo-locally', (id, level) => {
|
|
4110
|
+
expect([id, repo[id]]).toEqual([id, 'error'])
|
|
4111
|
+
if (!Object.hasOwn(PLUGIN_RULE_GRACE, id.slice('basalt/'.length))) {
|
|
4112
|
+
expect([id, level]).toEqual([id, repo[id]])
|
|
4156
4113
|
}
|
|
4157
4114
|
})
|
|
4158
4115
|
})
|
|
@@ -4198,11 +4155,6 @@ describe('the `Ships:` line above each rule', () => {
|
|
|
4198
4155
|
expect([id, level]).toEqual([id, 'warn'])
|
|
4199
4156
|
return
|
|
4200
4157
|
}
|
|
4201
|
-
if (Object.hasOwn(PLUGIN_RULE_ADVISORY, id)) {
|
|
4202
|
-
expect([id, claim]).toEqual([id, 'warn (advisory)'])
|
|
4203
|
-
expect([id, level]).toEqual([id, 'warn'])
|
|
4204
|
-
return
|
|
4205
|
-
}
|
|
4206
4158
|
expect([id, claim]).toEqual([id, 'error'])
|
|
4207
4159
|
expect([id, level]).toEqual([id, 'error'])
|
|
4208
4160
|
})
|