basalt-ui 1.8.0 → 1.10.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.
Files changed (99) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +64 -34
  4. package/agent/rules/basalt-agent.md +49 -6
  5. package/agent/rules/basalt-charts.md +74 -5
  6. package/configs/oxlint-plugin.js +279 -0
  7. package/configs/oxlint-plugin.test.ts +216 -1
  8. package/configs/oxlint.json +5 -2
  9. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  10. package/dist/agent/use-agent-thread-runs.js +1 -0
  11. package/dist/agent-chat/index.d.ts +4 -3
  12. package/dist/agent-chat/index.d.ts.map +1 -1
  13. package/dist/charts/hooks/useHoverSync.d.ts +18 -2
  14. package/dist/charts/hooks/useHoverSync.d.ts.map +1 -1
  15. package/dist/charts/hooks/useHoverSync.js +12 -7
  16. package/dist/charts/index.d.ts +1 -0
  17. package/dist/charts/index.d.ts.map +1 -1
  18. package/dist/charts/index.js +6 -0
  19. package/dist/charts/kinds/Bars.d.ts +2 -0
  20. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  21. package/dist/charts/kinds/Bars.js +3 -1
  22. package/dist/charts/kinds/Donut.d.ts +2 -0
  23. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  24. package/dist/charts/kinds/Donut.js +8 -4
  25. package/dist/charts/kinds/DualPanel.d.ts +2 -0
  26. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  27. package/dist/charts/kinds/DualPanel.js +2 -1
  28. package/dist/charts/kinds/Heatmap.d.ts +8 -0
  29. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  30. package/dist/charts/kinds/Heatmap.js +7 -3
  31. package/dist/charts/kinds/MultiLine.d.ts +2 -0
  32. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  33. package/dist/charts/kinds/MultiLine.js +2 -1
  34. package/dist/charts/kinds/StackedArea.d.ts +2 -0
  35. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  36. package/dist/charts/kinds/StackedArea.js +2 -1
  37. package/dist/charts/kinds/ZonedLine.d.ts +2 -0
  38. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  39. package/dist/charts/kinds/ZonedLine.js +2 -1
  40. package/dist/charts/primitives/Axes.d.ts +6 -1
  41. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  42. package/dist/charts/primitives/Axes.js +3 -2
  43. package/dist/charts/primitives/ChartFrame.d.ts +8 -1
  44. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  45. package/dist/charts/primitives/ChartFrame.js +8 -4
  46. package/dist/charts/primitives/ChartPending.d.ts +34 -0
  47. package/dist/charts/primitives/ChartPending.d.ts.map +1 -0
  48. package/dist/charts/primitives/ChartPending.js +23 -0
  49. package/dist/charts/primitives/ChartTooltip.d.ts +5 -2
  50. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  51. package/dist/charts/primitives/ChartTooltip.js +6 -1
  52. package/dist/charts/primitives/HoverOverlay.d.ts +5 -4
  53. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  54. package/dist/charts/primitives/HoverOverlay.js +4 -2
  55. package/dist/cli/index.d.ts +55 -1
  56. package/dist/cli/index.d.ts.map +1 -1
  57. package/dist/cli/index.js +217 -5
  58. package/dist/dashboard/stat-card.d.ts.map +1 -1
  59. package/dist/guard/index.d.ts.map +1 -1
  60. package/dist/guard/index.js +13 -5
  61. package/dist/surfaces.d.ts +10 -0
  62. package/dist/surfaces.d.ts.map +1 -1
  63. package/dist/surfaces.js +21 -0
  64. package/llms.txt +9 -2
  65. package/package.json +8 -5
  66. package/src/agent/ai-sdk-transport.test.ts +269 -0
  67. package/src/agent/parts.test.ts +261 -0
  68. package/src/agent/thread.test.ts +147 -0
  69. package/src/agent/use-agent-stream.test.tsx +231 -0
  70. package/src/agent/use-agent-thread-runs.resume.test.tsx +261 -0
  71. package/src/agent/use-agent-thread-runs.test.tsx +398 -0
  72. package/src/agent/use-agent-thread-runs.ts +7 -1
  73. package/src/agent/use-agent-thread-runs.wedge.test.tsx +299 -0
  74. package/src/agent-chat/index.ts +4 -3
  75. package/src/charts/hooks/useHoverSync.ts +43 -7
  76. package/src/charts/index.ts +6 -0
  77. package/src/charts/kinds/Bars.tsx +4 -0
  78. package/src/charts/kinds/Donut.tsx +10 -4
  79. package/src/charts/kinds/DualPanel.tsx +5 -1
  80. package/src/charts/kinds/Heatmap.tsx +19 -2
  81. package/src/charts/kinds/MultiLine.tsx +4 -1
  82. package/src/charts/kinds/StackedArea.tsx +4 -1
  83. package/src/charts/kinds/ZonedLine.tsx +4 -1
  84. package/src/charts/primitives/Axes.tsx +7 -1
  85. package/src/charts/primitives/ChartFrame.test.tsx +67 -0
  86. package/src/charts/primitives/ChartFrame.tsx +17 -4
  87. package/src/charts/primitives/ChartPending.tsx +59 -0
  88. package/src/charts/primitives/ChartTooltip.tsx +15 -3
  89. package/src/charts/primitives/HoverOverlay.tsx +12 -6
  90. package/src/cli/index.ts +439 -5
  91. package/src/cli/placement-engine.test.ts +104 -0
  92. package/src/dashboard/stat-card.test.tsx +5 -3
  93. package/src/dashboard/stat-card.tsx +12 -0
  94. package/src/guard/check-source.test.ts +20 -0
  95. package/src/guard/index.ts +20 -9
  96. package/src/provider/index.test.tsx +6 -5
  97. package/src/surfaces.ts +22 -0
  98. package/src/theme/use-basalt-spacing.test.tsx +7 -4
  99. package/src/tokens/build-fonts-css.test.ts +3 -2
@@ -14,6 +14,19 @@
14
14
  * immediately above it. The three boundary rules deliberately do NOT — see their own comment for
15
15
  * why.
16
16
  *
17
+ * ── The two agent-chat guard rules (`agent-resume-guard` / `agent-no-raw-usechat`) + `ai-sdk-major` ──
18
+ * These are also correctness/architecture boundaries, not design guidance, so they do NOT honour
19
+ * `theme-allow`. All three instead honour their own line-comment escape, `basalt-agent-allow`
20
+ * (`hasAgentAllow`, a copy of `hasThemeAllow` with a different needle) — deliberately a separate
21
+ * token, so a color exemption can never double as a license to switch off a streaming guard or an
22
+ * intentional `ai` major skew. `ai-sdk-major`'s escape is line-level and per-import, distinct from
23
+ * `basalt.aiMajorSkewReason` — the repo-wide, mandatory-reason config exemption for the SAME skew
24
+ * read by `doctor`'s `ai-major-parity` check (`src/cli/index.ts`): a lint run only ever sees one
25
+ * file, so there's no reason string to echo here, just a marker on the import that intentionally
26
+ * crosses the declared skew. `ai-sdk-major` is the one rule in this file that touches the
27
+ * filesystem (reads ancestor `package.json`s, memoized per directory so a lint run does one walk
28
+ * per directory, not per file) — every other rule here is pure AST + filename-string matching.
29
+ *
17
30
  * ── The three boundary rules (`visx-boundary` / `visx-tooltip` / `token-layer-boundary`) ─────────
18
31
  * These used to be one bundled `import-boundary` rule sharing a single on/off toggle — that meant
19
32
  * a consumer disabling the one check they disagreed with silently dropped the other two as well.
@@ -58,6 +71,9 @@
58
71
  * oxlint's own parser accepts (see `ALLOWED_TOP_LEVEL_KEYS` in that script); do not add ad hoc keys
59
72
  * to that file.
60
73
  */
74
+ import { existsSync, readFileSync } from 'node:fs'
75
+ import { dirname, resolve as resolvePath } from 'node:path'
76
+ import { fileURLToPath } from 'node:url'
61
77
 
62
78
  // ── Shared helpers ──────────────────────────────────────────────────────────────────────────────
63
79
 
@@ -73,6 +89,23 @@ function hasThemeAllow(context, node) {
73
89
  )
74
90
  }
75
91
 
92
+ /**
93
+ * True when a `basalt-agent-allow` line comment sits on `node`'s own source line or the line above
94
+ * it. A copy of `hasThemeAllow` with a different needle — deliberately NOT the same token, so a
95
+ * color exemption can never double as a license to switch off a streaming guard (see the file-header
96
+ * comment for the two agent-chat rules).
97
+ */
98
+ function hasAgentAllow(context, node) {
99
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.()
100
+ const comments = sourceCode?.getAllComments?.() ?? []
101
+ const nodeLine = node.loc.start.line
102
+ return comments.some(
103
+ (comment) =>
104
+ comment.value.includes('basalt-agent-allow') &&
105
+ (comment.loc.end.line === nodeLine || comment.loc.end.line === nodeLine - 1),
106
+ )
107
+ }
108
+
76
109
  function getFilename(context) {
77
110
  return context.filename ?? context.getFilename?.() ?? ''
78
111
  }
@@ -523,6 +556,249 @@ const tokenLayerBoundary = {
523
556
  },
524
557
  }
525
558
 
559
+ // ── Rule 8 — agent-resume-guard ─────────────────────────────────────────────────────────────────
560
+
561
+ const AGENT_RESUME_GUARD_MESSAGE =
562
+ 'Unguarded stream resume — useAgentThreadRuns owns single-consumer discipline and ' +
563
+ 'StrictMode-safe reconnection; a raw resume re-fires on every effect re-run (vercel/ai#7891, no ' +
564
+ "merged fix). Mark the line 'basalt-agent-allow' if you own the guard. (basalt/agent-resume-guard)"
565
+
566
+ /** True for `resumeStream()` and `<anything>.resumeStream()` call expressions. */
567
+ function isResumeStreamCall(callee) {
568
+ if (callee.type === 'Identifier') return callee.name === 'resumeStream'
569
+ return (
570
+ callee.type === 'MemberExpression' &&
571
+ callee.property.type === 'Identifier' &&
572
+ callee.property.name === 'resumeStream'
573
+ )
574
+ }
575
+
576
+ /** The `key` of an object Property as a string, covering both Identifier and Literal keys. */
577
+ function propertyKeyName(node) {
578
+ const key = node.key
579
+ if (key.type === 'Identifier') return key.name
580
+ if (key.type === 'Literal') return key.value
581
+ return undefined
582
+ }
583
+
584
+ // Deliberately NO `hasThemeAllow` escape — see basalt/visx-boundary's comment for why these
585
+ // guards use their own token (`basalt-agent-allow`, see the file-header comment) instead.
586
+ const agentResumeGuard = {
587
+ meta: {
588
+ type: 'problem',
589
+ docs: {
590
+ description:
591
+ 'Disallow unguarded useChat({ resume: true }) / resumeStream() calls outside useAgentThreadRuns.',
592
+ },
593
+ schema: [],
594
+ },
595
+ create(context) {
596
+ return {
597
+ CallExpression(node) {
598
+ const callee = node.callee
599
+
600
+ if (callee.type === 'Identifier' && callee.name === 'useChat') {
601
+ const arg0 = node.arguments[0]
602
+ if (arg0 === undefined || arg0.type !== 'ObjectExpression') return
603
+ for (const prop of arg0.properties) {
604
+ if (prop.type !== 'Property') continue
605
+ if (propertyKeyName(prop) !== 'resume') continue
606
+ if (prop.value.type !== 'Literal' || prop.value.value !== true) continue
607
+ if (hasAgentAllow(context, prop)) continue
608
+ context.report({ node: prop, message: AGENT_RESUME_GUARD_MESSAGE })
609
+ }
610
+ return
611
+ }
612
+
613
+ if (!isResumeStreamCall(callee)) return
614
+ if (hasAgentAllow(context, node)) return
615
+ context.report({ node, message: AGENT_RESUME_GUARD_MESSAGE })
616
+ },
617
+ }
618
+ },
619
+ }
620
+
621
+ // ── Rule 9 — agent-no-raw-usechat ───────────────────────────────────────────────────────────────
622
+
623
+ const AGENT_NO_RAW_USECHAT_MESSAGE =
624
+ 'Raw @ai-sdk/react useChat — use useAgentStream / useAgentThreadRuns over aiSdkTransport ' +
625
+ "(unmount abort, supersede guards, single-consumer resume). Mark the line 'basalt-agent-allow' to " +
626
+ 'opt out. (basalt/agent-no-raw-usechat)'
627
+
628
+ const RAW_USE_CHAT_SOURCES = new Set(['@ai-sdk/react', 'ai/react'])
629
+ const RAW_USE_CHAT_NAMES = new Set(['useChat', 'useCompletion'])
630
+
631
+ // Deliberately NO `hasThemeAllow` escape — see the file-header comment.
632
+ const agentNoRawUseChat = {
633
+ meta: {
634
+ type: 'problem',
635
+ docs: {
636
+ description:
637
+ 'Disallow importing useChat/useCompletion directly from @ai-sdk/react or ai/react.',
638
+ },
639
+ schema: [],
640
+ },
641
+ create(context) {
642
+ return {
643
+ ImportDeclaration(node) {
644
+ const source = node.source?.value
645
+ if (typeof source !== 'string' || !RAW_USE_CHAT_SOURCES.has(source)) return
646
+ // A whole `import type { … } from '…'` is erased at compile time — nothing to guard.
647
+ if (node.importKind === 'type') return
648
+
649
+ for (const specifier of node.specifiers) {
650
+ if (specifier.type !== 'ImportSpecifier') continue
651
+ // `import { type useChat } from '…'` — the individual specifier is type-only.
652
+ if (specifier.importKind === 'type') continue
653
+ const imported = specifier.imported
654
+ const importedName = imported.type === 'Identifier' ? imported.name : imported.value
655
+ if (typeof importedName !== 'string' || !RAW_USE_CHAT_NAMES.has(importedName)) continue
656
+ if (hasAgentAllow(context, specifier)) continue
657
+ context.report({ node: specifier, message: AGENT_NO_RAW_USECHAT_MESSAGE })
658
+ }
659
+ },
660
+ }
661
+ },
662
+ }
663
+
664
+ // ── Rule 10 — ai-sdk-major ──────────────────────────────────────────────────────────────────────
665
+
666
+ /**
667
+ * First run of digits in a semver range string (`^7.0.15` → `7`), or null if there is none.
668
+ *
669
+ * Duplicated verbatim in `src/cli/index.ts` — kept separate deliberately: this file must stay
670
+ * import-free from the package (it loads via `jsPlugins` out of a consumer's node_modules), so it
671
+ * cannot import the CLI's copy. Keep both copies in sync by hand; a future parsing fix (pre-release
672
+ * suffixes, say) must land in both.
673
+ */
674
+ function majorOf(range) {
675
+ if (typeof range !== 'string') return null
676
+ const match = range.match(/\d+/)
677
+ return match === null ? null : Number(match[0])
678
+ }
679
+
680
+ /**
681
+ * `ai`'s declared major off one parsed package.json, checked in ONE fixed order shared by every
682
+ * reader in this file (basalt's own major below, and the linted file's nearest package.json in
683
+ * `aiMajorAtPackageJson`) — `dependencies` → `devDependencies` → `peerDependencies`. Chosen
684
+ * deliberately: `dependencies` is what actually gets installed and resolved at runtime, so it wins
685
+ * over a `devDependencies` pin (test-only) or a `peerDependencies` range (a floor, not a pin). Two
686
+ * readers disagreeing on this order is exactly the kind of skew this rule exists to catch — see
687
+ * `cli/index.ts`'s `aiMajorAt`, which uses the same order for doctor's cross-package walk.
688
+ */
689
+ function aiMajorFromPkg(pkg) {
690
+ return majorOf(pkg.dependencies?.ai ?? pkg.devDependencies?.ai ?? pkg.peerDependencies?.ai)
691
+ }
692
+
693
+ /**
694
+ * basalt-ui's OWN declared `ai` peer major, read once at module load from the package.json
695
+ * sitting next to this file's `configs/` directory — `../package.json` relative to this module,
696
+ * which resolves to `packages/basalt-ui/package.json` repo-locally AND to
697
+ * `node_modules/basalt-ui/package.json` when this file runs from a consumer's installed copy
698
+ * (`configs/oxlint-plugin.js` is one directory below the package root in both places). `null` when
699
+ * unreadable or undeclared — the rule then has nothing to compare against and stays silent.
700
+ */
701
+ const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url))
702
+ const BASALT_AI_MAJOR = (() => {
703
+ try {
704
+ const pkg = JSON.parse(readFileSync(resolvePath(PLUGIN_DIR, '..', 'package.json'), 'utf8'))
705
+ // Peer-first, unlike aiMajorFromPkg's dependencies-first order: `ai` is an optional PEER of
706
+ // basalt-ui, never its runtime dependency, so the "dependencies wins because runtime" rationale
707
+ // above does not apply to this self-read — a devDependencies test pin must not masquerade as
708
+ // the declared peer major.
709
+ return majorOf(pkg.peerDependencies?.ai ?? pkg.devDependencies?.ai ?? pkg.dependencies?.ai)
710
+ } catch {
711
+ return null
712
+ }
713
+ })()
714
+
715
+ // One JSON.parse per package.json path, and one directory-walk result per starting directory — so
716
+ // a lint run over N files in the same package does one filesystem walk total, not N.
717
+ const packageJsonMajorCache = new Map()
718
+ const nearestAiMajorCache = new Map()
719
+
720
+ function aiMajorAtPackageJson(pkgPath) {
721
+ if (packageJsonMajorCache.has(pkgPath)) return packageJsonMajorCache.get(pkgPath)
722
+ let major = null
723
+ try {
724
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
725
+ major = aiMajorFromPkg(pkg)
726
+ } catch {
727
+ major = null
728
+ }
729
+ packageJsonMajorCache.set(pkgPath, major)
730
+ return major
731
+ }
732
+
733
+ /** Walks up from `startDir` to the nearest ancestor `package.json`'s declared `ai` major. */
734
+ function nearestAiMajor(startDir) {
735
+ if (nearestAiMajorCache.has(startDir)) return nearestAiMajorCache.get(startDir)
736
+
737
+ const visited = []
738
+ let dir = startDir
739
+ let result = null
740
+ for (;;) {
741
+ if (nearestAiMajorCache.has(dir)) {
742
+ result = nearestAiMajorCache.get(dir)
743
+ break
744
+ }
745
+ visited.push(dir)
746
+ const pkgPath = resolvePath(dir, 'package.json')
747
+ if (existsSync(pkgPath)) {
748
+ result = aiMajorAtPackageJson(pkgPath)
749
+ break
750
+ }
751
+ const parent = dirname(dir)
752
+ if (parent === dir) break // filesystem root — no package.json found
753
+ dir = parent
754
+ }
755
+ for (const d of visited) nearestAiMajorCache.set(d, result)
756
+ return result
757
+ }
758
+
759
+ function aiSdkMajorMessage(consumerMajor) {
760
+ return (
761
+ `This file's nearest package.json declares ai@${consumerMajor}, but basalt-ui declares the ` +
762
+ `ai@${BASALT_AI_MAJOR} peer major — a producer/consumer pair on different ai majors can throw ` +
763
+ "'Unknown chunk type' at runtime (argo defect 1: apps/api on ai@5 vs apps/dashboard on ai@7). " +
764
+ '(basalt/ai-sdk-major)'
765
+ )
766
+ }
767
+
768
+ // Honours `basalt-agent-allow` (NOT `theme-allow`) like its two agent-chat siblings above — a
769
+ // version skew is still a fact about two package.json files, but a written, intentional
770
+ // producer/consumer pair (see `doctor`'s `aiMajorSkewReason`) needs a way to say so at the import
771
+ // site too, not just be permanently blocked.
772
+ const aiSdkMajor = {
773
+ meta: {
774
+ type: 'problem',
775
+ docs: {
776
+ description:
777
+ "Disallow importing 'ai'/'@ai-sdk/*' from a package whose declared ai major differs from basalt-ui's.",
778
+ },
779
+ schema: [],
780
+ },
781
+ create(context) {
782
+ return {
783
+ ImportDeclaration(node) {
784
+ if (BASALT_AI_MAJOR === null) return
785
+ const source = node.source?.value
786
+ if (typeof source !== 'string') return
787
+ if (source !== 'ai' && !source.startsWith('@ai-sdk/')) return
788
+
789
+ const filename = getFilename(context)
790
+ if (filename.length === 0) return
791
+ const consumerMajor = nearestAiMajor(dirname(filename))
792
+ if (consumerMajor === null) return // no `ai` in the nearest package.json — nothing to compare
793
+ if (consumerMajor === BASALT_AI_MAJOR) return
794
+ if (hasAgentAllow(context, node)) return
795
+
796
+ context.report({ node, message: aiSdkMajorMessage(consumerMajor) })
797
+ },
798
+ }
799
+ },
800
+ }
801
+
526
802
  // ── Plugin export ───────────────────────────────────────────────────────────────────────────────
527
803
 
528
804
  export default {
@@ -536,5 +812,8 @@ export default {
536
812
  'visx-boundary': visxBoundary,
537
813
  'visx-tooltip': visxTooltip,
538
814
  'token-layer-boundary': tokenLayerBoundary,
815
+ 'agent-resume-guard': agentResumeGuard,
816
+ 'agent-no-raw-usechat': agentNoRawUseChat,
817
+ 'ai-sdk-major': aiSdkMajor,
539
818
  },
540
819
  }
@@ -8,7 +8,7 @@
8
8
  * binary and returns the parsed set of `basalt/<rule>` diagnostics it printed.
9
9
  */
10
10
  import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
11
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
11
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
12
12
  import { tmpdir } from 'node:os'
13
13
  import { dirname, resolve } from 'node:path'
14
14
 
@@ -31,6 +31,9 @@ beforeEach(() => {
31
31
  'basalt/visx-boundary': 'error',
32
32
  'basalt/visx-tooltip': 'error',
33
33
  'basalt/token-layer-boundary': 'error',
34
+ 'basalt/agent-resume-guard': 'error',
35
+ 'basalt/agent-no-raw-usechat': 'error',
36
+ 'basalt/ai-sdk-major': 'error',
34
37
  },
35
38
  }),
36
39
  )
@@ -232,3 +235,215 @@ describe('rule independence', () => {
232
235
  expect(rules).toContain('visx-tooltip')
233
236
  })
234
237
  })
238
+
239
+ // ── agent-resume-guard ───────────────────────────────────────────────────────
240
+ // The two agent rules honour a SEPARATE escape token, `basalt-agent-allow`, not `theme-allow` — a
241
+ // color exemption must never be able to switch off a streaming guard. The `theme-allow` fixture
242
+ // below proves the wrong token does not suppress the finding.
243
+
244
+ describe('basalt/agent-resume-guard', () => {
245
+ it('flags useChat({ resume: true })', () => {
246
+ const { code, rules } = run(`const c = useChat({ id, resume: true })\n`)
247
+ expect(code).toBe(1)
248
+ expect(rules).toContain('agent-resume-guard')
249
+ })
250
+
251
+ it('does NOT flag useChat({ resume: false })', () => {
252
+ const { code, rules } = run(`const c = useChat({ id, resume: false })\n`)
253
+ expect(code).toBe(0)
254
+ expect(rules).not.toContain('agent-resume-guard')
255
+ })
256
+
257
+ it('does NOT flag a bare useChat({ id }) with no resume key', () => {
258
+ const { code, rules } = run(`const c = useChat({ id })\n`)
259
+ expect(code).toBe(0)
260
+ expect(rules).not.toContain('agent-resume-guard')
261
+ })
262
+
263
+ it('flags chat.resumeStream() called from an effect', () => {
264
+ const { code, rules } = run(`useEffect(() => { chat.resumeStream() }, [])\n`)
265
+ expect(code).toBe(1)
266
+ expect(rules).toContain('agent-resume-guard')
267
+ })
268
+
269
+ it('does NOT flag chat.resumeStream() marked basalt-agent-allow', () => {
270
+ const { code, rules } = run(`// basalt-agent-allow\nchat.resumeStream()\n`)
271
+ expect(code).toBe(0)
272
+ expect(rules).not.toContain('agent-resume-guard')
273
+ })
274
+
275
+ it('STILL flags chat.resumeStream() marked only theme-allow (wrong escape token)', () => {
276
+ const { code, rules } = run(`// theme-allow\nchat.resumeStream()\n`)
277
+ expect(code).toBe(1)
278
+ expect(rules).toContain('agent-resume-guard')
279
+ })
280
+
281
+ it('does NOT flag an unrelated call like runs.start(id, text)', () => {
282
+ const { code, rules } = run(`runs.start(id, text)\n`)
283
+ expect(code).toBe(0)
284
+ expect(rules).not.toContain('agent-resume-guard')
285
+ })
286
+ })
287
+
288
+ // ── agent-no-raw-usechat ─────────────────────────────────────────────────────
289
+
290
+ describe('basalt/agent-no-raw-usechat', () => {
291
+ it('flags importing useChat from @ai-sdk/react', () => {
292
+ const { code, rules } = run(`import { useChat } from '@ai-sdk/react'\n`, 'lib.ts')
293
+ expect(code).toBe(1)
294
+ expect(rules).toContain('agent-no-raw-usechat')
295
+ })
296
+
297
+ it('flags importing useCompletion from @ai-sdk/react', () => {
298
+ const { code, rules } = run(`import { useCompletion } from '@ai-sdk/react'\n`, 'lib.ts')
299
+ expect(code).toBe(1)
300
+ expect(rules).toContain('agent-no-raw-usechat')
301
+ })
302
+
303
+ it('does NOT flag a type-only import of UIMessage from the same module', () => {
304
+ const { code, rules } = run(`import type { UIMessage } from '@ai-sdk/react'\n`, 'lib.ts')
305
+ expect(code).toBe(0)
306
+ expect(rules).not.toContain('agent-no-raw-usechat')
307
+ })
308
+
309
+ it('does NOT flag an unrelated import from ai', () => {
310
+ const { code, rules } = run(`import { DefaultChatTransport } from 'ai'\n`, 'lib.ts')
311
+ expect(code).toBe(0)
312
+ expect(rules).not.toContain('agent-no-raw-usechat')
313
+ })
314
+
315
+ it('does NOT flag useChat imported from a local module', () => {
316
+ const { code, rules } = run(`import { useChat } from './my-hooks'\n`, 'lib.ts')
317
+ expect(code).toBe(0)
318
+ expect(rules).not.toContain('agent-no-raw-usechat')
319
+ })
320
+
321
+ // The matcher covers TWO source specifiers (RAW_USE_CHAT_SOURCES: '@ai-sdk/react' AND
322
+ // 'ai/react') — every fixture above only exercises '@ai-sdk/react'; this pins the other half.
323
+ it("flags importing useChat from 'ai/react' (the other matched source)", () => {
324
+ const { code, rules } = run(`import { useChat } from 'ai/react'\n`, 'lib.ts')
325
+ expect(code).toBe(1)
326
+ expect(rules).toContain('agent-no-raw-usechat')
327
+ })
328
+
329
+ it('does NOT flag an import marked basalt-agent-allow', () => {
330
+ const { code, rules } = run(
331
+ `// basalt-agent-allow\nimport { useChat } from '@ai-sdk/react'\n`,
332
+ 'lib.ts',
333
+ )
334
+ expect(code).toBe(0)
335
+ expect(rules).not.toContain('agent-no-raw-usechat')
336
+ })
337
+
338
+ it('STILL flags an import marked only theme-allow (wrong escape token)', () => {
339
+ const { code, rules } = run(
340
+ `// theme-allow\nimport { useChat } from '@ai-sdk/react'\n`,
341
+ 'lib.ts',
342
+ )
343
+ expect(code).toBe(1)
344
+ expect(rules).toContain('agent-no-raw-usechat')
345
+ })
346
+ })
347
+
348
+ // ── ai-sdk-major ─────────────────────────────────────────────────────────────
349
+ // The lint rule compares the LINTED FILE's nearest package.json against basalt-ui's own declared
350
+ // `ai` peer major (read from packages/basalt-ui/package.json — the real file, since the plugin
351
+ // resolves it relative to its own module location regardless of where it's invoked from). basalt-ui
352
+ // declares "ai": "^7.0.15" (major 7) in both devDependencies and peerDependencies.
353
+
354
+ describe('basalt/ai-sdk-major', () => {
355
+ it("flags when the nearest package.json declares a different ai major (5 vs basalt's 7)", () => {
356
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '5.0.196' } }))
357
+ const { code, rules } = run(`import { streamText } from 'ai'\n`, 'lib.ts')
358
+ expect(code).toBe(1)
359
+ expect(rules).toContain('ai-sdk-major')
360
+ })
361
+
362
+ it('does NOT flag when the nearest package.json declares the same major (^7.0.18)', () => {
363
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '^7.0.18' } }))
364
+ const { code, rules } = run(`import { streamText } from 'ai'\n`, 'lib.ts')
365
+ expect(code).toBe(0)
366
+ expect(rules).not.toContain('ai-sdk-major')
367
+ })
368
+
369
+ it('does NOT flag when there is no ai in the nearest package.json', () => {
370
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: {} }))
371
+ const { code, rules } = run(`import { streamText } from 'ai'\n`, 'lib.ts')
372
+ expect(code).toBe(0)
373
+ expect(rules).not.toContain('ai-sdk-major')
374
+ })
375
+
376
+ it('does NOT flag an unrelated import even with a skewed nearest package.json', () => {
377
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '5.0.196' } }))
378
+ const { code, rules } = run(`import { useState } from 'react'\n`, 'lib.ts')
379
+ expect(code).toBe(0)
380
+ expect(rules).not.toContain('ai-sdk-major')
381
+ })
382
+
383
+ // The matcher covers TWO forms (source === 'ai' OR source.startsWith('@ai-sdk/')) — every
384
+ // fixture above only exercises the bare 'ai' specifier; this pins the scoped-package half.
385
+ it("flags a scoped '@ai-sdk/*' import when the nearest package.json declares a different ai major", () => {
386
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '5.0.196' } }))
387
+ const { code, rules } = run(`import { openai } from '@ai-sdk/openai'\n`, 'lib.ts')
388
+ expect(code).toBe(1)
389
+ expect(rules).toContain('ai-sdk-major')
390
+ })
391
+
392
+ it("does NOT flag a scoped '@ai-sdk/*' import when the nearest package.json declares the same major", () => {
393
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '^7.0.18' } }))
394
+ const { code, rules } = run(`import { openai } from '@ai-sdk/openai'\n`, 'lib.ts')
395
+ expect(code).toBe(0)
396
+ expect(rules).not.toContain('ai-sdk-major')
397
+ })
398
+
399
+ // Unlike the other rules in this file, ai-sdk-major honours `basalt-agent-allow` — the SAME
400
+ // token as its two agent-chat siblings above — not `theme-allow`. A skewed producer/consumer
401
+ // import that is genuinely intentional (see doctor's `aiMajorSkewReason`) can mark the line.
402
+ it('does NOT flag an import marked basalt-agent-allow, even with a skewed nearest package.json', () => {
403
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '5.0.196' } }))
404
+ const { code, rules } = run(
405
+ `// basalt-agent-allow\nimport { streamText } from 'ai'\n`,
406
+ 'lib.ts',
407
+ )
408
+ expect(code).toBe(0)
409
+ expect(rules).not.toContain('ai-sdk-major')
410
+ })
411
+
412
+ it('STILL flags an import marked only theme-allow (wrong escape token)', () => {
413
+ writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ dependencies: { ai: '5.0.196' } }))
414
+ const { code, rules } = run(`// theme-allow\nimport { streamText } from 'ai'\n`, 'lib.ts')
415
+ expect(code).toBe(1)
416
+ expect(rules).toContain('ai-sdk-major')
417
+ })
418
+ })
419
+
420
+ // ── raw-scroll-container — promotion (off → warn shipped) ────────────────────
421
+
422
+ describe('basalt/raw-scroll-container promotion', () => {
423
+ it('ships "warn" (not "off") in the consumer preset', () => {
424
+ const shipped = JSON.parse(
425
+ readFileSync(resolve(import.meta.dirname, 'oxlint.json'), 'utf8'),
426
+ ) as { rules: Record<string, unknown> }
427
+ expect(shipped.rules['basalt/raw-scroll-container']).toBe('warn')
428
+ })
429
+
430
+ it('stays "warn" repo-local', () => {
431
+ const repoLocal = JSON.parse(
432
+ readFileSync(resolve(import.meta.dirname, '..', '..', '..', '.oxlintrc.json'), 'utf8'),
433
+ ) as { rules: Record<string, unknown> }
434
+ expect(repoLocal.rules['basalt/raw-scroll-container']).toBe('warn')
435
+ })
436
+
437
+ it('still flags a raw overflow:auto style property at the shipped level', () => {
438
+ writeFileSync(
439
+ resolve(dir, '.oxlintrc.json'),
440
+ JSON.stringify({
441
+ plugins: [],
442
+ jsPlugins: [PLUGIN_PATH],
443
+ rules: { 'basalt/raw-scroll-container': 'warn' },
444
+ }),
445
+ )
446
+ const { rules } = run(`const s = { overflowY: 'auto' }\n`, 'lib.ts')
447
+ expect(rules).toContain('raw-scroll-container')
448
+ })
449
+ })
@@ -20,9 +20,12 @@
20
20
  "basalt/raw-size-literal": "warn",
21
21
  "basalt/card-inset": "error",
22
22
  "basalt/chart-in-raw-surface": "error",
23
- "basalt/raw-scroll-container": "off",
23
+ "basalt/raw-scroll-container": "warn",
24
24
  "basalt/visx-boundary": "error",
25
- "basalt/visx-tooltip": "error"
25
+ "basalt/visx-tooltip": "error",
26
+ "basalt/agent-resume-guard": "warn",
27
+ "basalt/agent-no-raw-usechat": "warn",
28
+ "basalt/ai-sdk-major": "warn"
26
29
  },
27
30
  "overrides": [
28
31
  {
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-thread-runs.d.ts","sourceRoot":"","sources":["../../src/agent/use-agent-thread-runs.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,WAAW,CAAA;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,KAAK,EAA6B,YAAY,EAAE,MAAM,UAAU,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,SAAS,IAAI;IAC9C,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;CACxB,CAAA;AAID,MAAM,MAAM,sBAAsB,CAAC,KAAK,GAAG,SAAS,IAAI;IACtD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,EACd,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAC7B,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IACzD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IACnC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IAC/C;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,EAAE,CAAA;CAClD,CAAA;AAID,MAAM,MAAM,wBAAwB,CAAC,KAAK,GAAG,SAAS,IAAI;IACxD,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;IACzD,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,CAAA;AA0ID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAG,SAAS,EAAE,EACpD,SAAS,EACT,KAAK,EACL,cAAc,EACd,WAAW,GACZ,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAAG,wBAAwB,CAAC,KAAK,CAAC,CA6KjE"}
1
+ {"version":3,"file":"use-agent-thread-runs.d.ts","sourceRoot":"","sources":["../../src/agent/use-agent-thread-runs.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,WAAW,CAAA;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,KAAK,EAA6B,YAAY,EAAE,MAAM,UAAU,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,SAAS,IAAI;IAC9C,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;CACxB,CAAA;AAID,MAAM,MAAM,sBAAsB,CAAC,KAAK,GAAG,SAAS,IAAI;IACtD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,EACd,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAC7B,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IACzD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IACnC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;IAC/C;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,EAAE,CAAA;CAClD,CAAA;AAID,MAAM,MAAM,wBAAwB,CAAC,KAAK,GAAG,SAAS,IAAI;IACxD,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;IACzD,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,CAAA;AA0ID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAG,SAAS,EAAE,EACpD,SAAS,EACT,KAAK,EACL,cAAc,EACd,WAAW,GACZ,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAmLjE"}
@@ -117,6 +117,7 @@ function useAgentThreadRuns({
117
117
  useEffect(
118
118
  () => () => {
119
119
  controllersRef.current.forEach((controller) => controller.abort());
120
+ controllersRef.current.clear();
120
121
  },
121
122
  []
122
123
  );
@@ -2,11 +2,12 @@
2
2
  * Mantine-styled thread-chat components over the headless `basalt-ui/agent` layer, capped by the
3
3
  * flagship `ThreadWorkspace` composite.
4
4
  *
5
- * This module is Mantine-coupled by design and lives under the root entry (unlike `basalt-ui/agent`,
6
- * which stays headless so it can be styled by any consumer).
5
+ * This module is Mantine-coupled by design and ships its own `basalt-ui/agent-chat` subpath
6
+ * (also re-exported from the root entry, unchanged for existing consumers) — unlike
7
+ * `basalt-ui/agent`, which stays headless so it can be styled by any consumer.
7
8
  *
8
9
  * @example
9
- * import { ThreadWorkspace } from 'basalt-ui'
10
+ * import { ThreadWorkspace } from 'basalt-ui/agent-chat'
10
11
  * import { createThreadsStore, edenTransport, heuristicOutcome } from 'basalt-ui/agent'
11
12
  */
12
13
  export { ThreadWorkspace } from './thread-workspace.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent-chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAGpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxE,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent-chat/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAGpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxE,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA"}
@@ -12,16 +12,32 @@ type XScale = (x: string) => number | undefined;
12
12
  * closest-point loop inline — drift across 15+ charts is the exact problem
13
13
  * this hook prevents.
14
14
  */
15
- export declare function useHoverSync<T>({ data, chartId, getKey, xScale, marginLeft, }: {
15
+ export declare function useHoverSync<T>({ data, chartId, getKey, xScale, marginLeft, resolveKey, }: {
16
16
  data: T[];
17
17
  chartId: string;
18
18
  getKey: (d: T) => string;
19
19
  xScale: XScale;
20
20
  marginLeft: number;
21
+ /**
22
+ * Overrides how a SIBLING's broadcast key resolves to one of THIS chart's own points. Only
23
+ * affects reading another chart's hover — this chart's own hover (via `getKey`/`xScale`) is
24
+ * untouched.
25
+ *
26
+ * Cross-chart sync is exact string match by default (`pointByKey.get(ctx.key)`), so two charts
27
+ * share a cursor only if they emit identical key strings. A chart that downsamples/folds its
28
+ * domain to fit a narrow viewport (e.g. ~288 buckets folded into ~97 columns at 390px) no longer
29
+ * owns most of the keys its unfolded siblings broadcast — the shared crosshair then appears on
30
+ * roughly one hover in three, with no rule a reader can infer, which is worse than no shared
31
+ * cursor at all. Pass `resolveKey` to map a foreign key onto whichever of this chart's own points
32
+ * swallowed it (e.g. the folded bucket the key falls into).
33
+ *
34
+ * Omit it and behaviour is byte-identical to today.
35
+ */
36
+ resolveKey?: (key: string) => T | null;
21
37
  }): {
22
38
  tip: import("./useChartTooltip.js").TooltipState<T>;
23
39
  tooltipRef: import("react").RefObject<HTMLDivElement | null>;
24
- syncedPoint: NonNullable<T> | null;
40
+ syncedPoint: T | null;
25
41
  isDirectHover: boolean;
26
42
  handleMouse: (event: React.MouseEvent<SVGRectElement>) => void;
27
43
  handleLeave: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useHoverSync.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useHoverSync.ts"],"names":[],"mappings":"AAKA,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAC9B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,GACX,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB;;;;;yBAgCW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC;;EAmD3C"}
1
+ {"version":3,"file":"useHoverSync.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useHoverSync.ts"],"names":[],"mappings":"AAKA,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAC9B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,EACV,UAAU,GACX,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,CAAA;CACvC;;;;;yBA6CW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC;;EAyD3C"}