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
package/src/agent/adapter.ts
DELETED
|
@@ -1,945 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ThreadsStoreAdapter — the async, server-backed seam behind ThreadsStore.
|
|
3
|
-
*
|
|
4
|
-
* `createThreadsStore` (./thread) is localStorage with silent ring-buffer caps. That is the right
|
|
5
|
-
* default for a demo and the wrong one for a real transcript: a consumer whose threads live in
|
|
6
|
-
* Postgres has, until now, had to fork the store outright. This module opens that seam.
|
|
7
|
-
*
|
|
8
|
-
* Three pieces:
|
|
9
|
-
* 1. `ThreadsStoreAdapter` — the async contract a consumer implements against its own backend.
|
|
10
|
-
* 2. `createAdapterThreadsStore` — wraps one into the SYNCHRONOUS `ThreadsStore` that every
|
|
11
|
-
* component in `basalt-ui/agent-chat` already takes. Optimistic local state, revalidate on
|
|
12
|
-
* success, roll back on rejection.
|
|
13
|
-
* 3. `threadsStoreAdapterContract` — a test-runner-agnostic conformance suite the consumer runs
|
|
14
|
-
* against its own adapter. It ships in the published package, so it imports NO test framework.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* import { createAdapterThreadsStore } from 'basalt-ui/agent'
|
|
18
|
-
*
|
|
19
|
-
* // Call once at module scope, exactly like createThreadsStore:
|
|
20
|
-
* export const useThreads = createAdapterThreadsStore(postgresThreadsAdapter(api))
|
|
21
|
-
*
|
|
22
|
-
* // In a component — same shape as the localStorage store, plus hydrated/error:
|
|
23
|
-
* const { threads, hydrated, error, create, appendMessage } = useThreads()
|
|
24
|
-
*/
|
|
25
|
-
import { useEffect, useSyncExternalStore } from 'react'
|
|
26
|
-
import type { ChatMessage } from './history'
|
|
27
|
-
import { mintThreadId } from './id'
|
|
28
|
-
import type { AgentOutcome } from './outcome'
|
|
29
|
-
import type { AgentPart } from './parts'
|
|
30
|
-
import type { AgentThread, ThreadStatus, ThreadsStore } from './thread'
|
|
31
|
-
|
|
32
|
-
// ── ThreadsStoreAdapter ───────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The async persistence contract behind a `ThreadsStore`. Every method may reject; the wrapping
|
|
36
|
-
* store treats a rejection as "the optimistic edit did not happen" and rolls it back.
|
|
37
|
-
*
|
|
38
|
-
* Implement this against whatever owns the transcript (Postgres, an HTTP API, IndexedDB) and
|
|
39
|
-
* verify it with `threadsStoreAdapterContract` — the suite pins the parts of the contract that
|
|
40
|
-
* prose cannot enforce, chiefly `appendMessage`'s idempotency.
|
|
41
|
-
*
|
|
42
|
-
* ## What `createAdapterThreadsStore` guarantees you
|
|
43
|
-
*
|
|
44
|
-
* **Per-thread write ordering.** Writes carrying the same thread id reach this adapter strictly in
|
|
45
|
-
* issue order, each one awaiting the previous, so a write that depends on a row existing is never
|
|
46
|
-
* issued before that row's `createThread` has RESOLVED. This matters because the primary send path
|
|
47
|
-
* is one synchronous block — `create()`, `select()`, `markRead()`, `appendMessage()`,
|
|
48
|
-
* `setStatus()` — which without ordering would hand you three dependent writes against a row you
|
|
49
|
-
* have not created yet. You therefore never have to tolerate (or invent an upsert for) an
|
|
50
|
-
* out-of-order write. Writes for DIFFERENT thread ids stay concurrent and carry no ordering
|
|
51
|
-
* guarantee relative to each other.
|
|
52
|
-
*
|
|
53
|
-
* ## What it requires of you
|
|
54
|
-
*
|
|
55
|
-
* **Read-after-write consistency on `listThreads`.** A `listThreads` STARTED after a write has
|
|
56
|
-
* resolved must reflect that write. The store keeps a write's optimistic patch applied until such
|
|
57
|
-
* a list lands, then drops it; an eventually-consistent backend (a stale read replica, a cached
|
|
58
|
-
* list) will therefore flash the pre-write state back into the UI. `threadsStoreAdapterContract`
|
|
59
|
-
* pins this.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* const postgresThreadsAdapter = (api: Api): ThreadsStoreAdapter => ({
|
|
63
|
-
* listThreads: (signal) => api.threads.get({ fetch: { signal } }),
|
|
64
|
-
* loadThread: (id, signal) => api.threads({ id }).get({ fetch: { signal } }),
|
|
65
|
-
* createThread: ({ id, meta }) => api.threads.post({ id, meta }),
|
|
66
|
-
* // ...
|
|
67
|
-
* })
|
|
68
|
-
*/
|
|
69
|
-
export type ThreadsStoreAdapter<TPart = AgentPart> = {
|
|
70
|
-
/** All threads, NEWEST-FIRST (same ordering contract as `ThreadsStore.threads`). */
|
|
71
|
-
readonly listThreads: (signal?: AbortSignal) => Promise<readonly AgentThread<TPart>[]>
|
|
72
|
-
/** One thread by id, or `null` when it does not exist. Must not throw on an unknown id. */
|
|
73
|
-
readonly loadThread: (id: string, signal?: AbortSignal) => Promise<AgentThread<TPart> | null>
|
|
74
|
-
/**
|
|
75
|
-
* Materialize a new thread under the CLIENT-MINTED `id`. The id is minted client-side because
|
|
76
|
-
* `ThreadsStore.create()` returns synchronously — there is no await to hand a server id back
|
|
77
|
-
* through. The created thread starts empty: `messages: []`, `outcome: null`,
|
|
78
|
-
* `status: 'pending'`, `read: false`, with `createdAt`/`updatedAt` set.
|
|
79
|
-
*/
|
|
80
|
-
readonly createThread: (i: {
|
|
81
|
-
readonly id: string
|
|
82
|
-
readonly meta?: Record<string, unknown>
|
|
83
|
-
}) => Promise<void>
|
|
84
|
-
/**
|
|
85
|
-
* CONTRACT: idempotent on `message.id`. The id is client-minted and is the ONLY idempotency
|
|
86
|
-
* key — a retried or double-fired write with the same id must be a no-op, not a second row.
|
|
87
|
-
*/
|
|
88
|
-
readonly appendMessage: (i: {
|
|
89
|
-
readonly threadId: string
|
|
90
|
-
readonly message: ChatMessage<TPart>
|
|
91
|
-
}) => Promise<void>
|
|
92
|
-
readonly setStatus: (i: {
|
|
93
|
-
readonly threadId: string
|
|
94
|
-
readonly status: ThreadStatus
|
|
95
|
-
}) => Promise<void>
|
|
96
|
-
readonly setOutcome: (i: {
|
|
97
|
-
readonly threadId: string
|
|
98
|
-
readonly outcome: AgentOutcome
|
|
99
|
-
}) => Promise<void>
|
|
100
|
-
/** `token: undefined` CLEARS the token — the stored thread must then read `resumeToken` as absent. */
|
|
101
|
-
readonly setResumeToken: (i: {
|
|
102
|
-
readonly threadId: string
|
|
103
|
-
readonly token: string | undefined
|
|
104
|
-
}) => Promise<void>
|
|
105
|
-
readonly markRead: (threadId: string) => Promise<void>
|
|
106
|
-
/** Removing an unknown id is a no-op, not an error. */
|
|
107
|
-
readonly removeThread: (threadId: string) => Promise<void>
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── createAdapterThreadsStore ─────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
export type AdapterThreadsStoreOptions = {
|
|
113
|
-
/**
|
|
114
|
-
* Re-list threads whenever the window regains focus. Off by default — a background tab that
|
|
115
|
-
* refocuses should not surprise a consumer with a network round-trip it did not ask for.
|
|
116
|
-
* @default false
|
|
117
|
-
*/
|
|
118
|
-
readonly revalidateOnFocus?: boolean
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* An optimistic patch over the last-confirmed server list.
|
|
123
|
-
*
|
|
124
|
-
* Every patch MUST be idempotent: on a successful write the patch stays applied while the
|
|
125
|
-
* follow-up `listThreads` lands, so it is briefly re-applied on top of a server list that already
|
|
126
|
-
* contains its effect. `appendMessage`'s patch therefore appends-if-absent, `create`'s
|
|
127
|
-
* prepends-if-absent, and the setters are plain assignments. Without that property the store
|
|
128
|
-
* would double-render a message for one frame after every successful append.
|
|
129
|
-
*
|
|
130
|
-
* Idempotent means TIME-INDEPENDENT too: a patch is re-applied on every store event, including
|
|
131
|
-
* ones it has nothing to do with (a `select()` while a write is in flight), so it must never read
|
|
132
|
-
* the clock inside `apply`. Each action captures `Date.now()` once, at issue time, and closes over
|
|
133
|
-
* it — otherwise an unrelated `select()` would re-stamp `updatedAt`. `apply` is additionally
|
|
134
|
-
* memoized on input identity (see `memoizeApply`) so re-application is referentially stable and
|
|
135
|
-
* cannot defeat a consumer's `React.memo`/`useMemo` over `threads`.
|
|
136
|
-
*/
|
|
137
|
-
type ThreadPatch<TPart> = {
|
|
138
|
-
readonly apply: (threads: readonly AgentThread<TPart>[]) => AgentThread<TPart>[]
|
|
139
|
-
/**
|
|
140
|
-
* The patch's effect on the LOCAL selection, when it has one (`remove`/`clear`). Kept in the
|
|
141
|
-
* patch rather than assigned eagerly so it rolls back with the rows on a rejection.
|
|
142
|
-
*/
|
|
143
|
-
readonly applyActive?: (activeId: string | null) => string | null
|
|
144
|
-
/**
|
|
145
|
-
* The load sequence number that must land in `base` before this patch may be dropped, or `null`
|
|
146
|
-
* while the write is still in flight. See `prune` — this is the whole "no gap" invariant.
|
|
147
|
-
*/
|
|
148
|
-
confirmSeq: number | null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Wraps an async `ThreadsStoreAdapter` into the synchronous `ThreadsStore` every component takes.
|
|
153
|
-
*
|
|
154
|
-
* Call this ONCE per module (not inside a component), exactly like `createThreadsStore`. All
|
|
155
|
-
* components calling the returned hook share one registry, backed by `useSyncExternalStore`.
|
|
156
|
-
*
|
|
157
|
-
* Semantics:
|
|
158
|
-
* - **Optimistic.** Every mutation applies locally first, so `create()` can return an id
|
|
159
|
-
* synchronously and the UI never waits on a round-trip.
|
|
160
|
-
* - **Ordered per thread.** Writes for one thread id reach the adapter in issue order, each
|
|
161
|
-
* awaiting the previous — so the dependent writes of a `create()`-then-send land after
|
|
162
|
-
* `createThread` has resolved. Different threads stay concurrent. See `ThreadsStoreAdapter`.
|
|
163
|
-
* - **Revalidate on success, coalesced.** When a write resolves the store re-lists; the patch is
|
|
164
|
-
* dropped only once a list has actually refreshed the confirmed base, so server-side derivations
|
|
165
|
-
* win over the local guess with no gap in between. Writes that resolve while a list is already in
|
|
166
|
-
* flight share ONE follow-up instead of each restarting it, so a burst of writes costs at most
|
|
167
|
-
* two round trips (the in-flight one, plus one follow-up), not one per write — see `revalidate`.
|
|
168
|
-
* - **Roll back on rejection.** A rejected write's patch is discarded, the rejection is surfaced
|
|
169
|
-
* on `error`, and the store re-lists to converge on whatever the server really did. Patches are
|
|
170
|
-
* tracked individually, so a failing write rolls back only itself and not whatever else was in
|
|
171
|
-
* flight beside it. A rejected `create()` additionally drops every later single-thread write for
|
|
172
|
-
* that id (`markRead`/`appendMessage`/`setStatus`/… — both the ones chained directly behind it and
|
|
173
|
-
* the run's completion writes at stream end) instead of letting each fail independently against a
|
|
174
|
-
* row that no longer exists — see `failedCreateIds`. Without that, the create's own error is
|
|
175
|
-
* overwritten by a run of "unknown thread" rejections that name nothing a caller can act on.
|
|
176
|
-
* - **`hydrated` means "a `listThreads` has succeeded"**, not "a load has been attempted". A
|
|
177
|
-
* store that is `!hydrated` with a non-undefined `error` failed to load; check both.
|
|
178
|
-
* - **`error` latches the most recent failure until something disproves it.** A successful
|
|
179
|
-
* `listThreads` clears a LOAD failure; it leaves a WRITE failure alone, since a working list is
|
|
180
|
-
* no evidence the rejected write landed. A write clears a latched write failure only if it was
|
|
181
|
-
* issued after that failure (a retry) — never the writes that were already queued behind it.
|
|
182
|
-
*
|
|
183
|
-
* `select` is local-only — which thread is active is UI state, not something the adapter owns.
|
|
184
|
-
* `clear()` maps onto `removeThread` for each known thread, since the adapter has no bulk delete.
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* export const useThreads = createAdapterThreadsStore(myAdapter, { revalidateOnFocus: true })
|
|
188
|
-
*/
|
|
189
|
-
export function createAdapterThreadsStore<TPart = AgentPart>(
|
|
190
|
-
adapter: ThreadsStoreAdapter<TPart>,
|
|
191
|
-
opts?: AdapterThreadsStoreOptions,
|
|
192
|
-
): () => ThreadsStore<TPart> {
|
|
193
|
-
const revalidateOnFocus = opts?.revalidateOnFocus ?? false
|
|
194
|
-
|
|
195
|
-
type Snapshot = {
|
|
196
|
-
readonly threads: AgentThread<TPart>[]
|
|
197
|
-
readonly activeId: string | null
|
|
198
|
-
readonly hydrated: boolean
|
|
199
|
-
readonly error: unknown
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const EMPTY: AgentThread<TPart>[] = []
|
|
203
|
-
// Stable identity for the SSR snapshot — useSyncExternalStore requires getServerSnapshot to be
|
|
204
|
-
// referentially stable across calls or React loops on it.
|
|
205
|
-
const SERVER_SNAPSHOT: Snapshot = {
|
|
206
|
-
threads: EMPTY,
|
|
207
|
-
activeId: null,
|
|
208
|
-
hydrated: false,
|
|
209
|
-
error: undefined,
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** Last confirmed server list. Optimistic patches are layered on top, never merged into it. */
|
|
213
|
-
let base: AgentThread<TPart>[] = EMPTY
|
|
214
|
-
/**
|
|
215
|
-
* The `loadSeq` of the `listThreads` that produced the current `base`. Patch lifetime is keyed
|
|
216
|
-
* to this number rather than to "a revalidate returned" — see `prune`.
|
|
217
|
-
*/
|
|
218
|
-
let baseSeq = 0
|
|
219
|
-
/** Confirmed selection. The patched value (see `applyActive`) is what the snapshot exposes. */
|
|
220
|
-
let activeId: string | null = null
|
|
221
|
-
let hydrated = false
|
|
222
|
-
let error: unknown
|
|
223
|
-
/**
|
|
224
|
-
* Bumped on every failure. A write clears `error` only when this token still matches the value
|
|
225
|
-
* it captured at ISSUE time — i.e. only when the write was issued AFTER the latched failure
|
|
226
|
-
* happened. Without that, the writes queued behind a failing one (the send path issues four in
|
|
227
|
-
* one block) would clear the failure the consumer never got to see; with it, a genuine retry
|
|
228
|
-
* still clears it.
|
|
229
|
-
*/
|
|
230
|
-
let errorToken = 0
|
|
231
|
-
/**
|
|
232
|
-
* Whether the latched `error` came from a WRITE. A successful `listThreads` is evidence the load
|
|
233
|
-
* path works, not that the rejected write landed — so it clears a load error and leaves a write
|
|
234
|
-
* error alone.
|
|
235
|
-
*/
|
|
236
|
-
let errorFromWrite = false
|
|
237
|
-
const pending: ThreadPatch<TPart>[] = []
|
|
238
|
-
let snapshot: Snapshot = SERVER_SNAPSHOT
|
|
239
|
-
|
|
240
|
-
const listeners = new Set<() => void>()
|
|
241
|
-
|
|
242
|
-
function recompute(): void {
|
|
243
|
-
let threads: AgentThread<TPart>[] = base
|
|
244
|
-
let active = activeId
|
|
245
|
-
for (const patch of pending) {
|
|
246
|
-
threads = patch.apply(threads)
|
|
247
|
-
if (patch.applyActive !== undefined) active = patch.applyActive(active)
|
|
248
|
-
}
|
|
249
|
-
snapshot = { threads, activeId: active, hydrated, error }
|
|
250
|
-
for (const listener of listeners) listener()
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Memoizes a patch's `apply` on INPUT IDENTITY. `recompute` re-runs the whole pending stack on
|
|
255
|
-
* every store event, so without this an unrelated `select()` would mint fresh thread objects for
|
|
256
|
-
* every in-flight write and defeat referential-equality memoization downstream. Sound because
|
|
257
|
-
* `apply` is pure: the actions capture their timestamp at issue time, never inside `apply`.
|
|
258
|
-
*/
|
|
259
|
-
function memoizeApply(apply: ThreadPatch<TPart>['apply']): ThreadPatch<TPart>['apply'] {
|
|
260
|
-
let lastIn: readonly AgentThread<TPart>[] | undefined
|
|
261
|
-
let lastOut: AgentThread<TPart>[] | undefined
|
|
262
|
-
return (threads): AgentThread<TPart>[] => {
|
|
263
|
-
if (lastOut !== undefined && threads === lastIn) return lastOut
|
|
264
|
-
lastIn = threads
|
|
265
|
-
lastOut = apply(threads)
|
|
266
|
-
return lastOut
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function subscribe(listener: () => void): () => void {
|
|
271
|
-
listeners.add(listener)
|
|
272
|
-
return (): void => {
|
|
273
|
-
listeners.delete(listener)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function getSnapshot(): Snapshot {
|
|
278
|
-
return snapshot
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function getServerSnapshot(): Snapshot {
|
|
282
|
-
return SERVER_SNAPSHOT
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ── revalidation ────────────────────────────────────────────────────────────
|
|
286
|
-
|
|
287
|
-
let loadController: AbortController | null = null
|
|
288
|
-
let loadSeq = 0
|
|
289
|
-
/**
|
|
290
|
-
* A follow-up load already earmarked to start the moment the in-flight one finishes, or `null`
|
|
291
|
-
* when none is queued. See `revalidate` — this is the coalescing slot every write-triggered
|
|
292
|
-
* revalidate() call shares while a load is in flight, instead of each minting (and aborting) its
|
|
293
|
-
* own. INVARIANT: non-null only while a load is in flight (`loadController !== null`) — every
|
|
294
|
-
* place that ends the in-flight load (a landing, in `startLoad`'s `finally`; a forced supersede,
|
|
295
|
-
* in `forceRevalidate`; unmount, in `release`) also clears this, so a stale seq can never be
|
|
296
|
-
* handed out to a write that arrives after the load it was queued behind is long gone.
|
|
297
|
-
*/
|
|
298
|
-
let queuedSeq: number | null = null
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Drops every patch whose proving load has landed in `base`.
|
|
302
|
-
*
|
|
303
|
-
* INVARIANT: apart from a rollback, this is the ONLY place a patch is ever dropped — a patch
|
|
304
|
-
* outlives its write until a `listThreads` ISSUED AFTER that write resolved has actually
|
|
305
|
-
* refreshed `base`. Dropping on "some revalidate returned" is not enough: with two writes in
|
|
306
|
-
* flight (the normal case on the send path) the earlier revalidate is SUPERSEDED and returns
|
|
307
|
-
* without touching `base`, so dropping there would blank the confirmed write's effect out of
|
|
308
|
-
* the UI for the whole of the surviving round-trip. Because `loadSeq` is global and monotonic,
|
|
309
|
-
* `baseSeq >= confirmSeq` proves the landed list was started after the write resolved, and
|
|
310
|
-
* `ThreadsStoreAdapter`'s read-after-write clause makes that list contain the write.
|
|
311
|
-
*/
|
|
312
|
-
function prune(): void {
|
|
313
|
-
const kept: ThreadPatch<TPart>[] = []
|
|
314
|
-
for (const patch of pending) {
|
|
315
|
-
if (patch.confirmSeq !== null && patch.confirmSeq <= baseSeq) {
|
|
316
|
-
// Retiring a confirmed patch COMMITS its local-only effect: the rows come back from the
|
|
317
|
-
// server, but the selection does not, so dropping a confirmed `remove`/`clear` without
|
|
318
|
-
// folding `applyActive` in would resurrect a selection pointing at a deleted thread.
|
|
319
|
-
if (patch.applyActive !== undefined) activeId = patch.applyActive(activeId)
|
|
320
|
-
continue
|
|
321
|
-
}
|
|
322
|
-
kept.push(patch)
|
|
323
|
-
}
|
|
324
|
-
if (kept.length !== pending.length) pending.splice(0, pending.length, ...kept)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Runs one `listThreads` tagged with `seq` and installs it as `base` unless something has
|
|
329
|
-
* superseded it in the meantime (`forceRevalidate` swapping `loadController` out from under it).
|
|
330
|
-
* On landing (success, failure, or discard), starts the coalesced follow-up in `queuedSeq` — if
|
|
331
|
-
* any writes arrived while this load was in flight, exactly one more load runs for all of them.
|
|
332
|
-
*/
|
|
333
|
-
function startLoad(seq: number): void {
|
|
334
|
-
const controller = new AbortController()
|
|
335
|
-
loadController = controller
|
|
336
|
-
void (async (): Promise<void> => {
|
|
337
|
-
try {
|
|
338
|
-
const next = await adapter.listThreads(controller.signal)
|
|
339
|
-
// A forced revalidate replaced `loadController` while this was in flight — stale, ignore.
|
|
340
|
-
if (loadController !== controller) return
|
|
341
|
-
base = [...next]
|
|
342
|
-
baseSeq = seq
|
|
343
|
-
hydrated = true
|
|
344
|
-
// A working list says nothing about a rejected WRITE — see `errorFromWrite`.
|
|
345
|
-
if (!errorFromWrite) error = undefined
|
|
346
|
-
prune()
|
|
347
|
-
recompute()
|
|
348
|
-
} catch (cause) {
|
|
349
|
-
// An abort is a deliberate cancellation (unmount, or a forced supersede) — not a store
|
|
350
|
-
// error; nor is a stale rejection landing after something else already replaced us.
|
|
351
|
-
if (controller.signal.aborted || loadController !== controller) return
|
|
352
|
-
error = cause
|
|
353
|
-
errorToken += 1
|
|
354
|
-
errorFromWrite = false
|
|
355
|
-
recompute()
|
|
356
|
-
} finally {
|
|
357
|
-
if (loadController === controller) {
|
|
358
|
-
loadController = null
|
|
359
|
-
if (queuedSeq !== null) {
|
|
360
|
-
const next = queuedSeq
|
|
361
|
-
queuedSeq = null
|
|
362
|
-
startLoad(next)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
})()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Requests a revalidate and returns the seq of the load that will PROVE it — the load a caller
|
|
371
|
-
* must wait for (`baseSeq >= this seq`) before treating its write as confirmed, per `ThreadPatch`.
|
|
372
|
-
*
|
|
373
|
-
* COALESCES rather than restarts. A `listThreads` already in flight started before this call, so
|
|
374
|
-
* per `ThreadsStoreAdapter`'s read-after-write clause it is not guaranteed to reflect a write
|
|
375
|
-
* that only just resolved — aborting it and starting an identical one is therefore not a
|
|
376
|
-
* shortcut, and under back-to-back writes (a streaming append firing every few ms) it means NO
|
|
377
|
-
* load ever survives long enough to land: `base` never refreshes and no patch ever retires (this
|
|
378
|
-
* was the bug). Instead, while a load is in flight, every caller shares the ONE follow-up load
|
|
379
|
-
* already earmarked to start the instant the in-flight one finishes (`queuedSeq`) — that
|
|
380
|
-
* follow-up necessarily starts after every write that arrived while the in-flight load was
|
|
381
|
-
* running, so a single round trip proves all of them at once.
|
|
382
|
-
*/
|
|
383
|
-
function revalidate(): number {
|
|
384
|
-
if (loadController !== null) {
|
|
385
|
-
queuedSeq ??= ++loadSeq
|
|
386
|
-
return queuedSeq
|
|
387
|
-
}
|
|
388
|
-
const seq = ++loadSeq
|
|
389
|
-
startLoad(seq)
|
|
390
|
-
return seq
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Like `revalidate`, but ABORTS an in-flight load instead of coalescing behind it — for the two
|
|
395
|
-
* cases that genuinely want the freshest list right now rather than whatever the in-flight load
|
|
396
|
-
* happens to land: the initial mount (via `revalidate`, where nothing is in flight yet so the two
|
|
397
|
-
* are equivalent) and `revalidateOnFocus`. Drops any coalesced follow-up too: the fresh load this
|
|
398
|
-
* starts necessarily begins after every write that follow-up would have proven, so `baseSeq`
|
|
399
|
-
* still ends up `>=` their `confirmSeq` once it lands — `prune`'s check was always `>=`, not `==`.
|
|
400
|
-
*/
|
|
401
|
-
function forceRevalidate(): number {
|
|
402
|
-
loadController?.abort()
|
|
403
|
-
queuedSeq = null
|
|
404
|
-
const seq = ++loadSeq
|
|
405
|
-
startLoad(seq)
|
|
406
|
-
return seq
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ── per-thread write ordering ───────────────────────────────────────────────
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Tail of the in-flight write chain per thread id. `ThreadsStore`'s send path issues
|
|
413
|
-
* `create` → `markRead` → `appendMessage` → `setStatus` in ONE synchronous block, so without a
|
|
414
|
-
* chain a server-backed adapter would get three writes against a row whose `createThread` has
|
|
415
|
-
* not resolved yet, reject them, and roll the user's own message back out of the transcript.
|
|
416
|
-
*/
|
|
417
|
-
const chains = new Map<string, Promise<void>>()
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Thread ids whose `createThread` has rejected and been rolled back. The send path issues
|
|
421
|
-
* `create()` → `markRead()` → `appendMessage()` → `setStatus()` in one synchronous block, all
|
|
422
|
-
* chained behind the same per-thread queue above — so when `createThread` rejects, the three
|
|
423
|
-
* dependent writes queued right behind it are guaranteed to reach the adapter next, each against
|
|
424
|
-
* a row that was never materialized. Left alone, each of those independently rejects with its own
|
|
425
|
-
* "unknown thread" error and overwrites the one that actually explains what happened (the
|
|
426
|
-
* create's), so the user ends up staring at the last cascade error instead of the root cause.
|
|
427
|
-
*
|
|
428
|
-
* Recording the id here lets `mutate` recognize a single-thread write against a rolled-back
|
|
429
|
-
* create and drop it silently instead of running a doomed commit — see `mutate`'s `isCreate`
|
|
430
|
-
* guard.
|
|
431
|
-
*
|
|
432
|
-
* Entries are NEVER removed, deliberately. The cascade is not confined to the synchronous block:
|
|
433
|
-
* `useAgentThreadRuns`' COMPLETION path (`appendMessage(assistant)` → `setOutcome` → `setStatus`
|
|
434
|
-
* → `setResumeToken`) fires when the stream ends, long after this thread's write chain has
|
|
435
|
-
* drained — same rolled-back row, same "unknown thread" rejections, and landing at exactly the
|
|
436
|
-
* moment the user is watching the thread. Clearing the entry when the chain drains would fix the
|
|
437
|
-
* first few hundred milliseconds and hand the cascade straight back at stream end. Retaining it
|
|
438
|
-
* is safe and bounded: `create()` always mints its own id (`mintThreadId`, never caller-supplied),
|
|
439
|
-
* so an id in here can never be re-created and the set only grows by one per FAILED create in a
|
|
440
|
-
* session.
|
|
441
|
-
*/
|
|
442
|
-
const failedCreateIds = new Set<string>()
|
|
443
|
-
|
|
444
|
-
/** `task` must never reject, so a failed write cannot poison the chain behind it. */
|
|
445
|
-
function enqueue(threadIds: readonly string[], task: () => Promise<void>): void {
|
|
446
|
-
const ids = [...new Set(threadIds)]
|
|
447
|
-
const prior = ids.map((id) => chains.get(id)).filter((p): p is Promise<void> => p !== undefined)
|
|
448
|
-
const after = prior.length === 0 ? Promise.resolve() : Promise.all(prior).then(() => undefined)
|
|
449
|
-
const run = after.then(task)
|
|
450
|
-
for (const id of ids) chains.set(id, run)
|
|
451
|
-
void run.finally(() => {
|
|
452
|
-
// Only the tail clears its own entry, so a later write chained behind it is not orphaned.
|
|
453
|
-
// `failedCreateIds` is deliberately NOT cleared here — see its doc.
|
|
454
|
-
for (const id of ids) if (chains.get(id) === run) chains.delete(id)
|
|
455
|
-
})
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// ── optimistic mutation pipeline ────────────────────────────────────────────
|
|
459
|
-
|
|
460
|
-
function drop(patch: ThreadPatch<TPart>): void {
|
|
461
|
-
const index = pending.indexOf(patch)
|
|
462
|
-
if (index !== -1) pending.splice(index, 1)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function mutate(i: {
|
|
466
|
-
/** Threads this write touches — its ordering key(s). Empty only for a `clear()` of nothing. */
|
|
467
|
-
readonly threadIds: readonly string[]
|
|
468
|
-
readonly apply: ThreadPatch<TPart>['apply']
|
|
469
|
-
readonly applyActive?: (activeId: string | null) => string | null
|
|
470
|
-
readonly commit: () => Promise<void>
|
|
471
|
-
/**
|
|
472
|
-
* True only for `create()`'s own write. On rejection, marks its thread id as failed so the
|
|
473
|
-
* dependent writes chained behind it in the same synchronous send-path block recognize the row
|
|
474
|
-
* was rolled back — see `failedCreateIds`.
|
|
475
|
-
*/
|
|
476
|
-
readonly isCreate?: boolean
|
|
477
|
-
}): void {
|
|
478
|
-
const patch: ThreadPatch<TPart> = {
|
|
479
|
-
apply: memoizeApply(i.apply),
|
|
480
|
-
...(i.applyActive !== undefined ? { applyActive: i.applyActive } : {}),
|
|
481
|
-
confirmSeq: null,
|
|
482
|
-
}
|
|
483
|
-
pending.push(patch)
|
|
484
|
-
const tokenAtIssue = errorToken
|
|
485
|
-
recompute()
|
|
486
|
-
enqueue(i.threadIds, async (): Promise<void> => {
|
|
487
|
-
// A single-thread write against a create that already rolled back cannot succeed — the row
|
|
488
|
-
// was never materialized, and running it only manufactures a second "unknown thread" error
|
|
489
|
-
// that would bury the create's own. This covers both the writes chained directly behind the
|
|
490
|
-
// create and the run's later completion writes (see `failedCreateIds`). Scoped to
|
|
491
|
-
// single-thread writes deliberately:
|
|
492
|
-
// `clear()`'s multi-id commit already has its own per-id fan-out (`Promise.allSettled`) and
|
|
493
|
-
// `removeThread` is contractually a no-op on an unknown id anyway, so folding this check into
|
|
494
|
-
// a multi-id write would risk dropping legitimate deletes for OTHER, healthy threads in the
|
|
495
|
-
// same batch just because one id in it happened to be orphaned.
|
|
496
|
-
const [soleThreadId] = i.threadIds
|
|
497
|
-
if (
|
|
498
|
-
!i.isCreate &&
|
|
499
|
-
i.threadIds.length === 1 &&
|
|
500
|
-
soleThreadId !== undefined &&
|
|
501
|
-
failedCreateIds.has(soleThreadId)
|
|
502
|
-
) {
|
|
503
|
-
drop(patch)
|
|
504
|
-
recompute()
|
|
505
|
-
return
|
|
506
|
-
}
|
|
507
|
-
try {
|
|
508
|
-
await i.commit()
|
|
509
|
-
} catch (cause) {
|
|
510
|
-
if (i.isCreate) for (const threadId of i.threadIds) failedCreateIds.add(threadId)
|
|
511
|
-
// Roll back: discard THIS patch only, leaving any other in-flight patch applied.
|
|
512
|
-
drop(patch)
|
|
513
|
-
error = cause
|
|
514
|
-
errorToken += 1
|
|
515
|
-
errorFromWrite = true
|
|
516
|
-
recompute()
|
|
517
|
-
// Converge on the server: a rejection does not say how much of the write landed (a
|
|
518
|
-
// partially-applied `clear()` fan-out, a write whose response was lost), and rolling back
|
|
519
|
-
// to a stale `base` would resurrect rows that really were deleted.
|
|
520
|
-
revalidate()
|
|
521
|
-
return
|
|
522
|
-
}
|
|
523
|
-
// Only a write issued AFTER the latched failure is evidence of recovery — see `errorToken`.
|
|
524
|
-
if (errorFromWrite && errorToken === tokenAtIssue) {
|
|
525
|
-
error = undefined
|
|
526
|
-
errorFromWrite = false
|
|
527
|
-
recompute()
|
|
528
|
-
}
|
|
529
|
-
// Keep the patch applied ACROSS the revalidate; `prune` drops it once that list has actually
|
|
530
|
-
// refreshed `base`. Dropping on return would flash the pre-mutation state. This is why every
|
|
531
|
-
// patch has to be idempotent.
|
|
532
|
-
patch.confirmSeq = revalidate()
|
|
533
|
-
})
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/** Rewrites one thread in place, preserving order; a no-op when the id is unknown. */
|
|
537
|
-
function patchThread(
|
|
538
|
-
id: string,
|
|
539
|
-
update: (thread: AgentThread<TPart>) => AgentThread<TPart>,
|
|
540
|
-
): ThreadPatch<TPart>['apply'] {
|
|
541
|
-
return (threads): AgentThread<TPart>[] =>
|
|
542
|
-
threads.map((thread) => (thread.id === id ? update(thread) : thread))
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// ── mount lifecycle (shared across every component using the hook) ──────────
|
|
546
|
-
|
|
547
|
-
let mounted = 0
|
|
548
|
-
|
|
549
|
-
function onFocus(): void {
|
|
550
|
-
// A refocus genuinely wants the freshest list now, not whatever a stale in-flight load lands.
|
|
551
|
-
forceRevalidate()
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function retain(): void {
|
|
555
|
-
mounted += 1
|
|
556
|
-
if (mounted !== 1) return
|
|
557
|
-
revalidate()
|
|
558
|
-
if (revalidateOnFocus && typeof window !== 'undefined') {
|
|
559
|
-
window.addEventListener('focus', onFocus)
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function release(): void {
|
|
564
|
-
mounted -= 1
|
|
565
|
-
if (mounted !== 0) return
|
|
566
|
-
loadController?.abort()
|
|
567
|
-
loadController = null
|
|
568
|
-
// Nothing is left to prove it, and a stale seq handed to a future write would let that write's
|
|
569
|
-
// patch retire prematurely the moment ANY later load lands — see the `queuedSeq` invariant.
|
|
570
|
-
queuedSeq = null
|
|
571
|
-
if (revalidateOnFocus && typeof window !== 'undefined') {
|
|
572
|
-
window.removeEventListener('focus', onFocus)
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// ── actions (factory-scope, hence referentially stable across renders) ──────
|
|
577
|
-
|
|
578
|
-
function select(id: string | null): void {
|
|
579
|
-
activeId = id
|
|
580
|
-
recompute()
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function create(createOpts?: { readonly meta?: Record<string, unknown> }): string {
|
|
584
|
-
// Client-minted: `create()` is synchronous, so there is no await to carry a server id back.
|
|
585
|
-
const id = mintThreadId()
|
|
586
|
-
const now = Date.now()
|
|
587
|
-
const thread: AgentThread<TPart> = {
|
|
588
|
-
id,
|
|
589
|
-
messages: [],
|
|
590
|
-
outcome: null,
|
|
591
|
-
status: 'pending',
|
|
592
|
-
read: false,
|
|
593
|
-
createdAt: now,
|
|
594
|
-
updatedAt: now,
|
|
595
|
-
...(createOpts?.meta !== undefined ? { meta: createOpts.meta } : {}),
|
|
596
|
-
}
|
|
597
|
-
mutate({
|
|
598
|
-
threadIds: [id],
|
|
599
|
-
apply: (threads) => (threads.some((t) => t.id === id) ? [...threads] : [thread, ...threads]),
|
|
600
|
-
isCreate: true,
|
|
601
|
-
commit: () =>
|
|
602
|
-
adapter.createThread({
|
|
603
|
-
id,
|
|
604
|
-
...(createOpts?.meta !== undefined ? { meta: createOpts.meta } : {}),
|
|
605
|
-
}),
|
|
606
|
-
})
|
|
607
|
-
return id
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function appendMessage(id: string, message: ChatMessage<TPart>): void {
|
|
611
|
-
// Captured at ISSUE time, never read inside `apply` — see ThreadPatch. Same for every setter
|
|
612
|
-
// below.
|
|
613
|
-
const now = Date.now()
|
|
614
|
-
mutate({
|
|
615
|
-
threadIds: [id],
|
|
616
|
-
apply: patchThread(id, (thread) =>
|
|
617
|
-
thread.messages.some((m) => m.id === message.id)
|
|
618
|
-
? thread
|
|
619
|
-
: { ...thread, messages: [...thread.messages, message], updatedAt: now },
|
|
620
|
-
),
|
|
621
|
-
commit: () => adapter.appendMessage({ threadId: id, message }),
|
|
622
|
-
})
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function setOutcome(id: string, outcome: AgentOutcome): void {
|
|
626
|
-
const now = Date.now()
|
|
627
|
-
mutate({
|
|
628
|
-
threadIds: [id],
|
|
629
|
-
apply: patchThread(id, (thread) => ({ ...thread, outcome, updatedAt: now })),
|
|
630
|
-
commit: () => adapter.setOutcome({ threadId: id, outcome }),
|
|
631
|
-
})
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function setStatus(id: string, status: ThreadStatus): void {
|
|
635
|
-
const now = Date.now()
|
|
636
|
-
mutate({
|
|
637
|
-
threadIds: [id],
|
|
638
|
-
apply: patchThread(id, (thread) => ({ ...thread, status, updatedAt: now })),
|
|
639
|
-
commit: () => adapter.setStatus({ threadId: id, status }),
|
|
640
|
-
})
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function setResumeToken(id: string, token: string | undefined): void {
|
|
644
|
-
const now = Date.now()
|
|
645
|
-
mutate({
|
|
646
|
-
threadIds: [id],
|
|
647
|
-
apply: patchThread(id, (thread) => {
|
|
648
|
-
// exactOptionalPropertyTypes: drop the key entirely to clear it rather than assigning
|
|
649
|
-
// `resumeToken: undefined` (same idiom as thread.ts).
|
|
650
|
-
const { resumeToken: _resumeToken, ...rest } = thread
|
|
651
|
-
return {
|
|
652
|
-
...rest,
|
|
653
|
-
...(token !== undefined ? { resumeToken: token } : {}),
|
|
654
|
-
updatedAt: now,
|
|
655
|
-
}
|
|
656
|
-
}),
|
|
657
|
-
commit: () => adapter.setResumeToken({ threadId: id, token }),
|
|
658
|
-
})
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function markRead(id: string): void {
|
|
662
|
-
mutate({
|
|
663
|
-
threadIds: [id],
|
|
664
|
-
// Identity-preserving when the server already agrees — markRead does not bump updatedAt.
|
|
665
|
-
apply: patchThread(id, (thread) => (thread.read ? thread : { ...thread, read: true })),
|
|
666
|
-
commit: () => adapter.markRead(id),
|
|
667
|
-
})
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function remove(id: string): void {
|
|
671
|
-
mutate({
|
|
672
|
-
threadIds: [id],
|
|
673
|
-
apply: (threads) => threads.filter((thread) => thread.id !== id),
|
|
674
|
-
// Inside the patch, so a rejected delete restores the SELECTION along with the row. Written
|
|
675
|
-
// as a conditional clear rather than a flat `null` so a `select()` issued while the delete
|
|
676
|
-
// is in flight survives the patch being re-applied on top of it.
|
|
677
|
-
applyActive: (active) => (active === id ? null : active),
|
|
678
|
-
commit: () => adapter.removeThread(id),
|
|
679
|
-
})
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function clear(): void {
|
|
683
|
-
// The adapter has no bulk delete, so clear() fans out over the threads we currently know
|
|
684
|
-
// about. Threads created concurrently by another tab survive — they are picked up by the
|
|
685
|
-
// revalidate that follows.
|
|
686
|
-
const ids = snapshot.threads.map((thread) => thread.id)
|
|
687
|
-
const clearedIds = new Set(ids)
|
|
688
|
-
mutate({
|
|
689
|
-
threadIds: ids,
|
|
690
|
-
apply: () => [],
|
|
691
|
-
// Same idiom as remove(): only null a selection pointing at one of the threads THIS clear
|
|
692
|
-
// captured, so a select() issued while the clear is in flight — e.g. into a thread created
|
|
693
|
-
// concurrently by create() — survives the patch being re-applied on top of it.
|
|
694
|
-
applyActive: (active) => (active !== null && clearedIds.has(active) ? null : active),
|
|
695
|
-
commit: async (): Promise<void> => {
|
|
696
|
-
// allSettled, not all: `all` rejects on the FIRST failure while the other deletes are
|
|
697
|
-
// still in flight, so the convergence load would race them and re-list rows that were
|
|
698
|
-
// about to disappear. Settling everything first makes the follow-up list authoritative.
|
|
699
|
-
const results = await Promise.allSettled(ids.map((id) => adapter.removeThread(id)))
|
|
700
|
-
const failures = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
|
|
701
|
-
if (failures.length === 0) return
|
|
702
|
-
throw new AggregateError(
|
|
703
|
-
failures.map((f): unknown => f.reason),
|
|
704
|
-
`removeThread rejected for ${failures.length} of ${ids.length} threads`,
|
|
705
|
-
)
|
|
706
|
-
},
|
|
707
|
-
})
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
return function useAdapterThreadsStore(): ThreadsStore<TPart> {
|
|
711
|
-
const current = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
712
|
-
|
|
713
|
-
useEffect(() => {
|
|
714
|
-
retain()
|
|
715
|
-
return (): void => {
|
|
716
|
-
release()
|
|
717
|
-
}
|
|
718
|
-
}, [])
|
|
719
|
-
|
|
720
|
-
return {
|
|
721
|
-
threads: current.threads,
|
|
722
|
-
activeId: current.activeId,
|
|
723
|
-
hydrated: current.hydrated,
|
|
724
|
-
error: current.error,
|
|
725
|
-
select,
|
|
726
|
-
create,
|
|
727
|
-
appendMessage,
|
|
728
|
-
setOutcome,
|
|
729
|
-
setStatus,
|
|
730
|
-
setResumeToken,
|
|
731
|
-
markRead,
|
|
732
|
-
remove,
|
|
733
|
-
clear,
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// ── threadsStoreAdapterContract ───────────────────────────────────────────────
|
|
739
|
-
|
|
740
|
-
/** One conformance case. `run` rejects with a plain Error describing the violation. */
|
|
741
|
-
export type ThreadsStoreAdapterContractCase = {
|
|
742
|
-
readonly name: string
|
|
743
|
-
readonly run: () => Promise<void>
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function assert(condition: boolean, message: string): asserts condition {
|
|
747
|
-
if (!condition) throw new Error(`ThreadsStoreAdapter contract: ${message}`)
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function message<TPart>(id: string): ChatMessage<TPart> {
|
|
751
|
-
// `parts: []` keeps the suite fully generic — there is no way to mint a TPart from here, and
|
|
752
|
-
// the contract is about ROW IDENTITY (the id), not part payloads.
|
|
753
|
-
return { id, role: 'user', parts: [], createdAt: Date.now() }
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* A test-runner-agnostic conformance suite for a `ThreadsStoreAdapter` implementation.
|
|
758
|
-
*
|
|
759
|
-
* This ships in the published package, so it deliberately imports NO test framework and depends
|
|
760
|
-
* on no assertion library — each case throws a plain `Error`. Drive it from whatever runner the
|
|
761
|
-
* consumer uses.
|
|
762
|
-
*
|
|
763
|
-
* `makeAdapter` is called ONCE PER CASE and must hand back a fresh, empty backend: the cases are
|
|
764
|
-
* independent and none of them cleans up after itself.
|
|
765
|
-
*
|
|
766
|
-
* @example
|
|
767
|
-
* import { threadsStoreAdapterContract } from 'basalt-ui/agent'
|
|
768
|
-
*
|
|
769
|
-
* for (const c of threadsStoreAdapterContract(() => postgresThreadsAdapter(freshDb()))) {
|
|
770
|
-
* test(c.name, () => c.run())
|
|
771
|
-
* }
|
|
772
|
-
*/
|
|
773
|
-
export function threadsStoreAdapterContract<TPart>(
|
|
774
|
-
makeAdapter: () => ThreadsStoreAdapter<TPart> | Promise<ThreadsStoreAdapter<TPart>>,
|
|
775
|
-
): readonly ThreadsStoreAdapterContractCase[] {
|
|
776
|
-
function define(
|
|
777
|
-
name: string,
|
|
778
|
-
run: (adapter: ThreadsStoreAdapter<TPart>) => Promise<void>,
|
|
779
|
-
): ThreadsStoreAdapterContractCase {
|
|
780
|
-
return {
|
|
781
|
-
name,
|
|
782
|
-
run: async (): Promise<void> => {
|
|
783
|
-
await run(await makeAdapter())
|
|
784
|
-
},
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
async function loadOrThrow(
|
|
789
|
-
adapter: ThreadsStoreAdapter<TPart>,
|
|
790
|
-
id: string,
|
|
791
|
-
): Promise<AgentThread<TPart>> {
|
|
792
|
-
const thread = await adapter.loadThread(id)
|
|
793
|
-
assert(thread !== null, `loadThread('${id}') returned null for a thread that was created`)
|
|
794
|
-
return thread
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return [
|
|
798
|
-
define('createThread then listThreads returns the thread with empty defaults', async (a) => {
|
|
799
|
-
const id = mintThreadId()
|
|
800
|
-
await a.createThread({ id })
|
|
801
|
-
const threads = await a.listThreads()
|
|
802
|
-
const found = threads.filter((t) => t.id === id)
|
|
803
|
-
assert(found.length === 1, `listThreads returned ${found.length} threads for one create`)
|
|
804
|
-
const thread = found[0]
|
|
805
|
-
assert(thread !== undefined, 'listThreads entry was undefined')
|
|
806
|
-
assert(thread.messages.length === 0, 'a new thread must start with no messages')
|
|
807
|
-
assert(thread.outcome === null, "a new thread's outcome must be null")
|
|
808
|
-
assert(thread.status === 'pending', `a new thread's status must be 'pending'`)
|
|
809
|
-
assert(thread.read === false, 'a new thread must be unread')
|
|
810
|
-
assert(typeof thread.createdAt === 'number', 'createdAt must be a number')
|
|
811
|
-
assert(typeof thread.updatedAt === 'number', 'updatedAt must be a number')
|
|
812
|
-
}),
|
|
813
|
-
|
|
814
|
-
define('createThread persists meta', async (a) => {
|
|
815
|
-
const id = mintThreadId()
|
|
816
|
-
await a.createThread({ id, meta: { source: 'contract' } })
|
|
817
|
-
const thread = await loadOrThrow(a, id)
|
|
818
|
-
assert(thread.meta?.['source'] === 'contract', 'meta did not round-trip')
|
|
819
|
-
}),
|
|
820
|
-
|
|
821
|
-
define('loadThread returns null for an unknown id', async (a) => {
|
|
822
|
-
const thread = await a.loadThread(mintThreadId())
|
|
823
|
-
assert(thread === null, 'loadThread must return null (not throw, not undefined) when absent')
|
|
824
|
-
}),
|
|
825
|
-
|
|
826
|
-
define('appendMessage is idempotent on message.id', async (a) => {
|
|
827
|
-
const id = mintThreadId()
|
|
828
|
-
await a.createThread({ id })
|
|
829
|
-
const msg = message<TPart>('contract-msg-1')
|
|
830
|
-
await a.appendMessage({ threadId: id, message: msg })
|
|
831
|
-
await a.appendMessage({ threadId: id, message: msg })
|
|
832
|
-
const thread = await loadOrThrow(a, id)
|
|
833
|
-
assert(
|
|
834
|
-
thread.messages.length === 1,
|
|
835
|
-
`appending the same message.id twice produced ${thread.messages.length} rows — the id is the only idempotency key`,
|
|
836
|
-
)
|
|
837
|
-
assert(thread.messages[0]?.id === 'contract-msg-1', 'the retained message has the wrong id')
|
|
838
|
-
}),
|
|
839
|
-
|
|
840
|
-
define('appendMessage preserves order for distinct ids', async (a) => {
|
|
841
|
-
const id = mintThreadId()
|
|
842
|
-
await a.createThread({ id })
|
|
843
|
-
await a.appendMessage({ threadId: id, message: message<TPart>('contract-msg-1') })
|
|
844
|
-
await a.appendMessage({ threadId: id, message: message<TPart>('contract-msg-2') })
|
|
845
|
-
const thread = await loadOrThrow(a, id)
|
|
846
|
-
assert(
|
|
847
|
-
thread.messages.map((m) => m.id).join(',') === 'contract-msg-1,contract-msg-2',
|
|
848
|
-
'messages must read back oldest-first in append order',
|
|
849
|
-
)
|
|
850
|
-
}),
|
|
851
|
-
|
|
852
|
-
define('setStatus round-trips', async (a) => {
|
|
853
|
-
const id = mintThreadId()
|
|
854
|
-
await a.createThread({ id })
|
|
855
|
-
await a.setStatus({ threadId: id, status: 'streaming' })
|
|
856
|
-
assert((await loadOrThrow(a, id)).status === 'streaming', "status did not become 'streaming'")
|
|
857
|
-
await a.setStatus({ threadId: id, status: 'error' })
|
|
858
|
-
assert((await loadOrThrow(a, id)).status === 'error', "status did not become 'error'")
|
|
859
|
-
}),
|
|
860
|
-
|
|
861
|
-
define('setOutcome round-trips', async (a) => {
|
|
862
|
-
const id = mintThreadId()
|
|
863
|
-
await a.createThread({ id })
|
|
864
|
-
const outcome: AgentOutcome = { title: 'Title', summary: 'Summary', status: 'done' }
|
|
865
|
-
await a.setOutcome({ threadId: id, outcome })
|
|
866
|
-
const thread = await loadOrThrow(a, id)
|
|
867
|
-
assert(thread.outcome?.title === 'Title', 'outcome.title did not round-trip')
|
|
868
|
-
assert(thread.outcome?.summary === 'Summary', 'outcome.summary did not round-trip')
|
|
869
|
-
assert(thread.outcome?.status === 'done', 'outcome.status did not round-trip')
|
|
870
|
-
}),
|
|
871
|
-
|
|
872
|
-
define('setResumeToken round-trips and clears with undefined', async (a) => {
|
|
873
|
-
const id = mintThreadId()
|
|
874
|
-
await a.createThread({ id })
|
|
875
|
-
await a.setResumeToken({ threadId: id, token: 'tok-1' })
|
|
876
|
-
assert((await loadOrThrow(a, id)).resumeToken === 'tok-1', 'resumeToken did not round-trip')
|
|
877
|
-
await a.setResumeToken({ threadId: id, token: undefined })
|
|
878
|
-
assert(
|
|
879
|
-
(await loadOrThrow(a, id)).resumeToken === undefined,
|
|
880
|
-
'setResumeToken(undefined) must CLEAR the token',
|
|
881
|
-
)
|
|
882
|
-
}),
|
|
883
|
-
|
|
884
|
-
define('markRead marks the thread read', async (a) => {
|
|
885
|
-
const id = mintThreadId()
|
|
886
|
-
await a.createThread({ id })
|
|
887
|
-
assert((await loadOrThrow(a, id)).read === false, 'a new thread must start unread')
|
|
888
|
-
await a.markRead(id)
|
|
889
|
-
assert((await loadOrThrow(a, id)).read === true, 'markRead did not set read')
|
|
890
|
-
}),
|
|
891
|
-
|
|
892
|
-
define('listThreads reflects a write that has already resolved', async (a) => {
|
|
893
|
-
// The store keeps a write's optimistic patch applied until a listThreads STARTED after that
|
|
894
|
-
// write resolved lands in the confirmed base. An eventually-consistent list (a stale read
|
|
895
|
-
// replica, a cached response) breaks that and flashes the pre-write state back into the UI.
|
|
896
|
-
const id = mintThreadId()
|
|
897
|
-
await a.createThread({ id })
|
|
898
|
-
await a.appendMessage({ threadId: id, message: message<TPart>('contract-msg-raw') })
|
|
899
|
-
await a.setStatus({ threadId: id, status: 'streaming' })
|
|
900
|
-
const found = (await a.listThreads()).find((t) => t.id === id)
|
|
901
|
-
assert(found !== undefined, 'listThreads omitted a thread created before the call')
|
|
902
|
-
assert(
|
|
903
|
-
found.status === 'streaming',
|
|
904
|
-
'listThreads returned a STALE status — a list started after a write resolves must reflect it',
|
|
905
|
-
)
|
|
906
|
-
assert(
|
|
907
|
-
found.messages.length === 1,
|
|
908
|
-
`listThreads returned a STALE message list (${found.messages.length} rows) — a list started after a write resolves must reflect it`,
|
|
909
|
-
)
|
|
910
|
-
}),
|
|
911
|
-
|
|
912
|
-
define(
|
|
913
|
-
'dependent writes issued straight after createThread resolves are honoured',
|
|
914
|
-
async (a) => {
|
|
915
|
-
// The store serializes writes per thread id, so this exact sequence — the send path's
|
|
916
|
-
// create-then-first-message — is what a real adapter receives. Each write must find the row.
|
|
917
|
-
const id = mintThreadId()
|
|
918
|
-
await a.createThread({ id })
|
|
919
|
-
await a.markRead(id)
|
|
920
|
-
await a.appendMessage({ threadId: id, message: message<TPart>('contract-msg-dep') })
|
|
921
|
-
await a.setStatus({ threadId: id, status: 'streaming' })
|
|
922
|
-
const thread = await loadOrThrow(a, id)
|
|
923
|
-
assert(thread.read === true, 'markRead did not apply to a just-created thread')
|
|
924
|
-
assert(
|
|
925
|
-
thread.messages.length === 1,
|
|
926
|
-
`appendMessage did not apply to a just-created thread (${thread.messages.length} rows)`,
|
|
927
|
-
)
|
|
928
|
-
assert(thread.status === 'streaming', 'setStatus did not apply to a just-created thread')
|
|
929
|
-
},
|
|
930
|
-
),
|
|
931
|
-
|
|
932
|
-
define('removeThread removes the thread', async (a) => {
|
|
933
|
-
const id = mintThreadId()
|
|
934
|
-
await a.createThread({ id })
|
|
935
|
-
await a.removeThread(id)
|
|
936
|
-
assert(await a.loadThread(id).then((t) => t === null), 'loadThread must be null after remove')
|
|
937
|
-
const threads = await a.listThreads()
|
|
938
|
-
assert(!threads.some((t) => t.id === id), 'listThreads still contains the removed thread')
|
|
939
|
-
}),
|
|
940
|
-
|
|
941
|
-
define('removeThread of an unknown id does not throw', async (a) => {
|
|
942
|
-
await a.removeThread(mintThreadId())
|
|
943
|
-
}),
|
|
944
|
-
]
|
|
945
|
-
}
|