@agent-native/toolkit 0.6.0 → 0.8.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 (174) hide show
  1. package/README.md +17 -1
  2. package/agent-native.eject.json +411 -0
  3. package/dist/chat-history/ChatHistoryList.d.ts +103 -0
  4. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
  5. package/dist/chat-history/ChatHistoryList.js +109 -0
  6. package/dist/chat-history/ChatHistoryList.js.map +1 -0
  7. package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
  8. package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
  9. package/dist/chat-history/ChatHistoryList.spec.js +283 -0
  10. package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
  11. package/dist/chat-history/index.d.ts +2 -0
  12. package/dist/chat-history/index.d.ts.map +1 -0
  13. package/dist/chat-history/index.js +2 -0
  14. package/dist/chat-history/index.js.map +1 -0
  15. package/dist/chat-history.css +314 -0
  16. package/package.json +20 -1
  17. package/src/app-shell/header-actions.tsx +84 -0
  18. package/src/app-shell/index.ts +1 -0
  19. package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
  20. package/src/chat-history/ChatHistoryList.tsx +469 -0
  21. package/src/chat-history/index.ts +7 -0
  22. package/src/chat-history.css +314 -0
  23. package/src/collab-ui/AgentPresenceChip.tsx +69 -0
  24. package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  25. package/src/collab-ui/PresenceBar.tsx +346 -0
  26. package/src/collab-ui/RecentEditHighlights.tsx +225 -0
  27. package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  28. package/src/collab-ui/agent-identity.ts +16 -0
  29. package/src/collab-ui/index.ts +39 -0
  30. package/src/collab-ui/lead-client.ts +32 -0
  31. package/src/collab-ui/types.spec.ts +18 -0
  32. package/src/collab-ui/types.ts +96 -0
  33. package/src/composer/AgentComposerFrame.tsx +59 -0
  34. package/src/composer/ComposerPlusMenu.tsx +983 -0
  35. package/src/composer/MentionPopover.tsx +412 -0
  36. package/src/composer/PastedTextChip.tsx +150 -0
  37. package/src/composer/PromptComposer.spec.ts +73 -0
  38. package/src/composer/PromptComposer.tsx +718 -0
  39. package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
  40. package/src/composer/RealtimeVoiceMode.tsx +856 -0
  41. package/src/composer/TiptapComposer.spec.ts +390 -0
  42. package/src/composer/TiptapComposer.tsx +2936 -0
  43. package/src/composer/VoiceButton.spec.ts +33 -0
  44. package/src/composer/VoiceButton.tsx +255 -0
  45. package/src/composer/asset-picker-url.spec.ts +56 -0
  46. package/src/composer/asset-picker-url.ts +80 -0
  47. package/src/composer/attachment-accept.spec.ts +22 -0
  48. package/src/composer/attachment-accept.ts +71 -0
  49. package/src/composer/draft-key.spec.ts +19 -0
  50. package/src/composer/draft-key.ts +7 -0
  51. package/src/composer/extensions/FileReference.tsx +55 -0
  52. package/src/composer/extensions/MentionReference.tsx +93 -0
  53. package/src/composer/extensions/SkillReference.tsx +56 -0
  54. package/src/composer/index.ts +79 -0
  55. package/src/composer/pasted-text.spec.ts +162 -0
  56. package/src/composer/pasted-text.ts +160 -0
  57. package/src/composer/prompt-attachments.spec.ts +80 -0
  58. package/src/composer/prompt-attachments.ts +93 -0
  59. package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
  60. package/src/composer/realtime-voice-audio-level.ts +64 -0
  61. package/src/composer/realtime-voice-transcript.spec.ts +95 -0
  62. package/src/composer/realtime-voice-transcript.ts +134 -0
  63. package/src/composer/runtime-adapters.tsx +343 -0
  64. package/src/composer/types.ts +66 -0
  65. package/src/composer/use-file-search.ts +64 -0
  66. package/src/composer/use-mention-search.ts +90 -0
  67. package/src/composer/use-skills.ts +45 -0
  68. package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
  69. package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  70. package/src/composer/useVoiceDictation.spec.ts +45 -0
  71. package/src/composer/useVoiceDictation.ts +1191 -0
  72. package/src/context-ui/ContextMeter.tsx +101 -0
  73. package/src/context-ui/ContextSegmentRow.tsx +184 -0
  74. package/src/context-ui/ContextTreemap.tsx +125 -0
  75. package/src/context-ui/ContextXRayPanel.tsx +377 -0
  76. package/src/context-ui/format.ts +42 -0
  77. package/src/context-ui/index.ts +15 -0
  78. package/src/context-ui/types.ts +43 -0
  79. package/src/design-tweaks/index.ts +1 -0
  80. package/src/design-tweaks/visual-style-controls.tsx +1084 -0
  81. package/src/editor/BubbleToolbar.tsx +253 -0
  82. package/src/editor/CodeBlockNode.tsx +254 -0
  83. package/src/editor/DragHandle.spec.ts +588 -0
  84. package/src/editor/DragHandle.ts +1437 -0
  85. package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
  86. package/src/editor/ImageExtension.ts +306 -0
  87. package/src/editor/RegistryBlockContext.tsx +107 -0
  88. package/src/editor/RegistryBlockNode.spec.ts +279 -0
  89. package/src/editor/RegistryBlockNode.tsx +652 -0
  90. package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
  91. package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
  92. package/src/editor/RichMarkdownEditor.tsx +81 -0
  93. package/src/editor/RunId.ts +61 -0
  94. package/src/editor/SharedRichEditor.tsx +321 -0
  95. package/src/editor/SlashCommandMenu.tsx +310 -0
  96. package/src/editor/disableHistory.spec.ts +85 -0
  97. package/src/editor/extensions.ts +402 -0
  98. package/src/editor/gfmDoc.spec.ts +230 -0
  99. package/src/editor/gfmDoc.ts +92 -0
  100. package/src/editor/index.ts +93 -0
  101. package/src/editor/registrySlashCommands.ts +131 -0
  102. package/src/editor/structuralReorderUndo.spec.ts +144 -0
  103. package/src/editor/surgical-apply.spec.ts +279 -0
  104. package/src/editor/surgical-apply.ts +192 -0
  105. package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
  106. package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
  107. package/src/editor/useCollabReconcile.ts +589 -0
  108. package/src/editor.css +510 -0
  109. package/src/hooks/index.ts +2 -0
  110. package/src/hooks/use-mobile.tsx +21 -0
  111. package/src/hooks/use-toast.ts +188 -0
  112. package/src/index.ts +8 -0
  113. package/src/onboarding/index.ts +1 -0
  114. package/src/provider.tsx +47 -0
  115. package/src/sharing/VisibilityBadge.spec.ts +96 -0
  116. package/src/sharing/VisibilityBadge.tsx +37 -0
  117. package/src/sharing/index.ts +4 -0
  118. package/src/styles.css +21 -0
  119. package/src/ui/accordion.tsx +56 -0
  120. package/src/ui/alert-dialog.tsx +141 -0
  121. package/src/ui/alert.tsx +59 -0
  122. package/src/ui/aspect-ratio.tsx +5 -0
  123. package/src/ui/avatar.tsx +48 -0
  124. package/src/ui/badge.tsx +37 -0
  125. package/src/ui/breadcrumb.tsx +115 -0
  126. package/src/ui/button.tsx +78 -0
  127. package/src/ui/calendar.tsx +214 -0
  128. package/src/ui/card.tsx +86 -0
  129. package/src/ui/carousel.tsx +260 -0
  130. package/src/ui/chart.tsx +375 -0
  131. package/src/ui/checkbox.tsx +28 -0
  132. package/src/ui/collapsible.tsx +9 -0
  133. package/src/ui/command.spec.tsx +51 -0
  134. package/src/ui/command.tsx +174 -0
  135. package/src/ui/context-menu.tsx +202 -0
  136. package/src/ui/date-picker.tsx +64 -0
  137. package/src/ui/dialog.spec.tsx +43 -0
  138. package/src/ui/dialog.tsx +146 -0
  139. package/src/ui/drawer.tsx +126 -0
  140. package/src/ui/dropdown-menu.tsx +218 -0
  141. package/src/ui/form.tsx +177 -0
  142. package/src/ui/hover-card.tsx +27 -0
  143. package/src/ui/index.ts +52 -0
  144. package/src/ui/input-otp.tsx +69 -0
  145. package/src/ui/input.tsx +22 -0
  146. package/src/ui/label.tsx +24 -0
  147. package/src/ui/logo.tsx +36 -0
  148. package/src/ui/menubar.tsx +235 -0
  149. package/src/ui/navigation-menu.tsx +128 -0
  150. package/src/ui/pagination.tsx +121 -0
  151. package/src/ui/popover.tsx +44 -0
  152. package/src/ui/progress.tsx +26 -0
  153. package/src/ui/radio-group.tsx +42 -0
  154. package/src/ui/resizable.tsx +43 -0
  155. package/src/ui/scroll-area.tsx +46 -0
  156. package/src/ui/select.tsx +158 -0
  157. package/src/ui/separator.tsx +29 -0
  158. package/src/ui/sheet.tsx +160 -0
  159. package/src/ui/sidebar.tsx +777 -0
  160. package/src/ui/skeleton.tsx +18 -0
  161. package/src/ui/slider.tsx +26 -0
  162. package/src/ui/sonner.tsx +58 -0
  163. package/src/ui/spinner.tsx +17 -0
  164. package/src/ui/switch.tsx +27 -0
  165. package/src/ui/table.tsx +117 -0
  166. package/src/ui/tabs.tsx +53 -0
  167. package/src/ui/textarea.tsx +23 -0
  168. package/src/ui/toast.tsx +127 -0
  169. package/src/ui/toaster.tsx +33 -0
  170. package/src/ui/toggle-group.tsx +59 -0
  171. package/src/ui/toggle.tsx +43 -0
  172. package/src/ui/tooltip.tsx +53 -0
  173. package/src/ui/use-toast.ts +3 -0
  174. package/src/utils.ts +8 -0
@@ -0,0 +1,101 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
4
+ import {
5
+ Tooltip,
6
+ TooltipContent,
7
+ TooltipProvider,
8
+ TooltipTrigger,
9
+ } from "../ui/tooltip.js";
10
+ import { cn } from "../utils.js";
11
+ import { formatContextTokens } from "./format.js";
12
+ import type { ContextManifestViewData } from "./types.js";
13
+
14
+ function ContextDonut({ pct, advisory }: { pct: number; advisory: boolean }) {
15
+ const radius = 7.5;
16
+ const circumference = 2 * Math.PI * radius;
17
+ const displayPct = Math.max(3, Math.min(100, pct));
18
+ const dashOffset = circumference - (displayPct / 100) * circumference;
19
+ return (
20
+ <span className="relative flex size-5 items-center justify-center">
21
+ <svg aria-hidden="true" viewBox="0 0 20 20" className="-rotate-90 size-5">
22
+ <circle
23
+ cx="10"
24
+ cy="10"
25
+ r={radius}
26
+ className="stroke-muted"
27
+ fill="none"
28
+ strokeWidth="3"
29
+ />
30
+ <circle
31
+ cx="10"
32
+ cy="10"
33
+ r={radius}
34
+ className={cn(advisory ? "stroke-amber-500" : "stroke-[#00B5FF]")}
35
+ fill="none"
36
+ strokeLinecap="round"
37
+ strokeWidth="3"
38
+ strokeDasharray={circumference}
39
+ strokeDashoffset={dashOffset}
40
+ />
41
+ </svg>
42
+ <span className="absolute size-2 rounded-full bg-background" />
43
+ </span>
44
+ );
45
+ }
46
+
47
+ export function ContextMeterView({
48
+ manifest,
49
+ contextWindow,
50
+ open,
51
+ onOpenChange,
52
+ children,
53
+ }: {
54
+ manifest: ContextManifestViewData;
55
+ contextWindow: number;
56
+ open: boolean;
57
+ onOpenChange: (open: boolean) => void;
58
+ children: ReactNode;
59
+ }) {
60
+ const pct = Math.min(
61
+ 100,
62
+ Math.round((manifest.totalTokens / contextWindow) * 100),
63
+ );
64
+ const { conversationTokens, systemTokens } = manifest;
65
+ return (
66
+ <TooltipProvider delayDuration={200}>
67
+ <Popover open={open} onOpenChange={onOpenChange}>
68
+ <Tooltip>
69
+ <TooltipTrigger asChild>
70
+ <PopoverTrigger asChild>
71
+ <button
72
+ type="button"
73
+ aria-label={`Context ${pct}%, ${formatContextTokens(manifest.totalTokens)}${systemTokens > 0 ? ` total: ${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation` : ""}. Open Context X-Ray.`}
74
+ className={cn(
75
+ "flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
76
+ open && "bg-accent/60 text-foreground",
77
+ )}
78
+ >
79
+ <ContextDonut pct={pct} advisory={!manifest.enforceable} />
80
+ </button>
81
+ </PopoverTrigger>
82
+ </TooltipTrigger>
83
+ <TooltipContent>
84
+ Context {pct}% · {formatContextTokens(manifest.totalTokens)}
85
+ {systemTokens > 0
86
+ ? ` (${formatContextTokens(systemTokens)} system + ${formatContextTokens(conversationTokens)} conversation)`
87
+ : ""}
88
+ </TooltipContent>
89
+ </Tooltip>
90
+ <PopoverContent
91
+ align="end"
92
+ side="top"
93
+ sideOffset={8}
94
+ className="w-[min(92vw,380px)] overflow-hidden border-border/70 p-0"
95
+ >
96
+ {children}
97
+ </PopoverContent>
98
+ </Popover>
99
+ </TooltipProvider>
100
+ );
101
+ }
@@ -0,0 +1,184 @@
1
+ import { IconLock, IconPin, IconRotate2, IconX } from "@tabler/icons-react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
6
+ import { cn } from "../utils.js";
7
+ import { contextStatusLabel, formatContextTokens } from "./format.js";
8
+ import type { ContextSegmentViewData } from "./types.js";
9
+
10
+ export function SegmentProvenancePopoverView({
11
+ segment,
12
+ children,
13
+ }: {
14
+ segment: ContextSegmentViewData;
15
+ children: ReactNode;
16
+ }) {
17
+ return (
18
+ <Popover>
19
+ <PopoverTrigger asChild>{children}</PopoverTrigger>
20
+ <PopoverContent align="start" className="w-80 p-3">
21
+ <div className="space-y-2">
22
+ <div>
23
+ <div className="text-[11px] font-medium uppercase text-muted-foreground">
24
+ Segment
25
+ </div>
26
+ <div className="mt-0.5 break-words text-xs text-foreground">
27
+ {segment.label}
28
+ </div>
29
+ </div>
30
+ <div className="grid grid-cols-2 gap-2 text-[11px] text-muted-foreground">
31
+ <div>
32
+ <span className="block font-medium text-foreground">
33
+ {formatContextTokens(segment.tokenCount)}
34
+ </span>
35
+ tokens{segment.tokenMethod === "estimate" ? " estimated" : ""}
36
+ </div>
37
+ <div>
38
+ <span className="block font-medium text-foreground">
39
+ {contextStatusLabel(segment)}
40
+ </span>
41
+ current status
42
+ </div>
43
+ <div>
44
+ <span className="block font-medium text-foreground">
45
+ {segment.msgIndex ?? "-"}
46
+ </span>
47
+ message index
48
+ </div>
49
+ <div>
50
+ <span className="block font-medium text-foreground">
51
+ {segment.partIndex ?? "-"}
52
+ </span>
53
+ part index
54
+ </div>
55
+ </div>
56
+ {segment.protected ? (
57
+ <div className="rounded-md border border-border bg-muted/40 px-2 py-1.5 text-[11px] text-muted-foreground">
58
+ This segment is part of the active turn and cannot be evicted yet.
59
+ </div>
60
+ ) : null}
61
+ </div>
62
+ </PopoverContent>
63
+ </Popover>
64
+ );
65
+ }
66
+
67
+ export function ContextSegmentRowView({
68
+ segment,
69
+ advisory,
70
+ onPin,
71
+ onEvict,
72
+ onRestore,
73
+ }: {
74
+ segment: ContextSegmentViewData;
75
+ advisory: boolean;
76
+ onPin: () => void;
77
+ onEvict: () => void;
78
+ onRestore: () => void;
79
+ }) {
80
+ const disabled = segment.protected || segment.status === "evicted";
81
+ return (
82
+ <div
83
+ tabIndex={0}
84
+ onKeyDown={(event) => {
85
+ if (event.key === "p") onPin();
86
+ if (event.key === "e" && !disabled) onEvict();
87
+ if (event.key === "u" && segment.status !== "active") onRestore();
88
+ }}
89
+ className={cn(
90
+ "group flex min-h-11 items-center gap-2 rounded-sm px-2 py-1.5 outline-none transition-colors hover:bg-accent/35 focus-visible:bg-accent/35 focus-visible:ring-1 focus-visible:ring-ring",
91
+ segment.status === "evicted" && "opacity-60",
92
+ )}
93
+ >
94
+ <SegmentProvenancePopoverView segment={segment}>
95
+ <button
96
+ type="button"
97
+ className="min-w-0 flex-1 text-left"
98
+ aria-label={`Inspect ${segment.label}`}
99
+ >
100
+ <div
101
+ className={cn(
102
+ "truncate text-[13px] font-medium leading-5 text-foreground",
103
+ segment.status === "evicted" && "line-through",
104
+ )}
105
+ >
106
+ {segment.label}
107
+ </div>
108
+ <div className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
109
+ <span>{formatContextTokens(segment.tokenCount)}</span>
110
+ {segment.tokenMethod === "estimate" ? <span>~</span> : null}
111
+ <span>·</span>
112
+ <span>{contextStatusLabel(segment)}</span>
113
+ {advisory ? <span>· advisory</span> : null}
114
+ </div>
115
+ </button>
116
+ </SegmentProvenancePopoverView>
117
+ <div className="flex shrink-0 items-center gap-1 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100">
118
+ {segment.protected ? (
119
+ <Tooltip>
120
+ <TooltipTrigger asChild>
121
+ <span className="flex size-6 items-center justify-center rounded-md text-muted-foreground">
122
+ <IconLock className="h-3.5 w-3.5" />
123
+ </span>
124
+ </TooltipTrigger>
125
+ <TooltipContent>Protected during active turn</TooltipContent>
126
+ </Tooltip>
127
+ ) : segment.status === "evicted" ||
128
+ segment.status === "summarized" ||
129
+ segment.status === "pinned" ? (
130
+ <Tooltip>
131
+ <TooltipTrigger asChild>
132
+ <button
133
+ type="button"
134
+ onClick={onRestore}
135
+ className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
136
+ aria-label={
137
+ segment.status === "pinned"
138
+ ? "Unpin segment"
139
+ : "Restore segment"
140
+ }
141
+ >
142
+ <IconRotate2 className="h-3.5 w-3.5" />
143
+ </button>
144
+ </TooltipTrigger>
145
+ <TooltipContent>
146
+ {segment.status === "pinned" ? "Unpin" : "Restore"}
147
+ </TooltipContent>
148
+ </Tooltip>
149
+ ) : (
150
+ <>
151
+ <Tooltip>
152
+ <TooltipTrigger asChild>
153
+ <button
154
+ type="button"
155
+ onClick={onPin}
156
+ className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
157
+ aria-label="Pin segment"
158
+ >
159
+ <IconPin className="h-3.5 w-3.5" />
160
+ </button>
161
+ </TooltipTrigger>
162
+ <TooltipContent>Pin</TooltipContent>
163
+ </Tooltip>
164
+ <Tooltip>
165
+ <TooltipTrigger asChild>
166
+ <button
167
+ type="button"
168
+ onClick={onEvict}
169
+ className="flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-destructive"
170
+ aria-label="Evict segment"
171
+ >
172
+ <IconX className="h-3.5 w-3.5" />
173
+ </button>
174
+ </TooltipTrigger>
175
+ <TooltipContent>
176
+ {advisory ? "Record eviction intent" : "Evict"}
177
+ </TooltipContent>
178
+ </Tooltip>
179
+ </>
180
+ )}
181
+ </div>
182
+ </div>
183
+ );
184
+ }
@@ -0,0 +1,125 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { Treemap } from "recharts";
3
+
4
+ import { contextGroupFill, formatContextTokens } from "./format.js";
5
+ import type {
6
+ ContextSegmentViewData,
7
+ ContextSystemSectionViewData,
8
+ } from "./types.js";
9
+
10
+ interface TreemapDatum {
11
+ name: string;
12
+ size: number;
13
+ group: string;
14
+ segmentId: string;
15
+ }
16
+
17
+ function TreemapContent(props: any) {
18
+ const { x, y, width, height, name, group, size } = props;
19
+ if (width < 8 || height < 8) return null;
20
+ const showLabel = width > 90 && height > 42;
21
+ return (
22
+ <g>
23
+ <rect
24
+ x={x}
25
+ y={y}
26
+ width={width}
27
+ height={height}
28
+ rx={3}
29
+ ry={3}
30
+ fill={contextGroupFill(group)}
31
+ stroke="var(--background)"
32
+ strokeWidth={2}
33
+ />
34
+ {showLabel ? (
35
+ <foreignObject
36
+ x={x + 6}
37
+ y={y + 6}
38
+ width={width - 12}
39
+ height={height - 12}
40
+ >
41
+ <div className="flex h-full flex-col justify-between overflow-hidden text-[11px] leading-tight text-white">
42
+ <div className="truncate font-medium">{name}</div>
43
+ <div className="text-white/85">{formatContextTokens(size)}</div>
44
+ </div>
45
+ </foreignObject>
46
+ ) : null}
47
+ </g>
48
+ );
49
+ }
50
+
51
+ export function ContextTreemapView({
52
+ segments,
53
+ systemSections = [],
54
+ onSelect,
55
+ }: {
56
+ segments: ContextSegmentViewData[];
57
+ systemSections?: ContextSystemSectionViewData[];
58
+ onSelect?: (segmentId: string) => void;
59
+ }) {
60
+ const containerRef = useRef<HTMLDivElement>(null);
61
+ const [chartWidth, setChartWidth] = useState(0);
62
+ const data: TreemapDatum[] = segments
63
+ .filter((segment) => segment.status !== "evicted" && segment.tokenCount > 0)
64
+ .map((segment) => ({
65
+ name: segment.label,
66
+ size: segment.tokenCount,
67
+ group: segment.group,
68
+ segmentId: segment.segmentId,
69
+ }));
70
+ data.push(
71
+ ...systemSections
72
+ .filter((section) => section.tokenCount > 0)
73
+ .map((section) => ({
74
+ name: section.label,
75
+ size: section.tokenCount,
76
+ group: `System · ${section.governance}`,
77
+ segmentId: section.segmentId,
78
+ })),
79
+ );
80
+
81
+ useEffect(() => {
82
+ const element = containerRef.current;
83
+ if (!element) return;
84
+ const updateWidth = () =>
85
+ setChartWidth(Math.max(0, Math.floor(element.clientWidth - 8)));
86
+ updateWidth();
87
+ const observer =
88
+ typeof ResizeObserver !== "undefined"
89
+ ? new ResizeObserver(updateWidth)
90
+ : null;
91
+ observer?.observe(element);
92
+ window.addEventListener("resize", updateWidth);
93
+ return () => {
94
+ observer?.disconnect();
95
+ window.removeEventListener("resize", updateWidth);
96
+ };
97
+ }, []);
98
+
99
+ if (data.length === 0) {
100
+ return (
101
+ <div className="flex h-52 items-center justify-center rounded-md bg-muted/30 text-xs text-muted-foreground">
102
+ No active segments
103
+ </div>
104
+ );
105
+ }
106
+ return (
107
+ <div ref={containerRef} className="h-56 min-w-0 rounded-md bg-muted/25 p-1">
108
+ {chartWidth > 0 ? (
109
+ <Treemap
110
+ width={chartWidth}
111
+ height={216}
112
+ data={data as any}
113
+ dataKey="size"
114
+ nameKey="name"
115
+ aspectRatio={4 / 3}
116
+ isAnimationActive={false}
117
+ content={<TreemapContent />}
118
+ onClick={(datum: any) => {
119
+ if (datum?.segmentId) onSelect?.(datum.segmentId);
120
+ }}
121
+ />
122
+ ) : null}
123
+ </div>
124
+ );
125
+ }