@agent-native/toolkit 0.213.1 → 0.214.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 (198) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +10 -4
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +13 -1
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +3 -1
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/index.d.ts +1 -0
  10. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/index.js +1 -0
  12. package/dist/app/agentkit/react/index.js.map +1 -1
  13. package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
  14. package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
  15. package/dist/app/agentkit/react/message-content-order.js +173 -0
  16. package/dist/app/agentkit/react/message-content-order.js.map +1 -0
  17. package/dist/app/agentkit/react/root.d.ts +3 -1
  18. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/root.js +2 -2
  20. package/dist/app/agentkit/react/root.js.map +1 -1
  21. package/dist/app/agentkit/react/styles.css +2 -2
  22. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  23. package/dist/app/chat/AgentKitAssistantChat.js +7 -2
  24. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  25. package/dist/app/chat/AgentPanel.d.ts +14 -2
  26. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  27. package/dist/app/chat/AgentPanel.js +22 -5
  28. package/dist/app/chat/AgentPanel.js.map +1 -1
  29. package/dist/app/chat/chat/surface-types.d.ts +8 -0
  30. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  31. package/dist/app/chat/chat/surface-types.js.map +1 -1
  32. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  33. package/dist/app/chat/chat/widgets/DataChartRenderer.js +127 -30
  34. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  35. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
  36. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  37. package/dist/app/chat/chat/widgets/DataChartWidget.js +14 -9
  38. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  39. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
  40. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  41. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
  42. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
  43. package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
  44. package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
  45. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
  46. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
  47. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +131 -0
  48. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
  49. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  50. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  51. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  52. package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
  53. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  54. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
  55. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
  56. package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
  57. package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
  58. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  59. package/dist/app/i18n/catalogs/ar-SA.js +8 -0
  60. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  61. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  62. package/dist/app/i18n/catalogs/de-DE.js +8 -0
  63. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  64. package/dist/app/i18n/catalogs/en-US.d.ts +8 -0
  65. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  66. package/dist/app/i18n/catalogs/en-US.js +8 -0
  67. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  68. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  69. package/dist/app/i18n/catalogs/es-ES.js +8 -0
  70. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  71. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  72. package/dist/app/i18n/catalogs/fr-FR.js +8 -0
  73. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  74. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  75. package/dist/app/i18n/catalogs/hi-IN.js +8 -0
  76. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  77. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  78. package/dist/app/i18n/catalogs/ja-JP.js +8 -0
  79. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  80. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/ko-KR.js +8 -0
  82. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  83. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/pt-BR.js +8 -0
  85. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  86. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/zh-CN.js +8 -0
  88. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  89. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/zh-TW.js +8 -0
  91. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  92. package/dist/app/providers/AppProviders.d.ts +2 -1
  93. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  94. package/dist/app/providers/AppProviders.js +4 -3
  95. package/dist/app/providers/AppProviders.js.map +1 -1
  96. package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
  97. package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
  98. package/dist/app/providers/ThemeFamilySync.js +6 -0
  99. package/dist/app/providers/ThemeFamilySync.js.map +1 -0
  100. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  101. package/dist/app/settings/account/PreferencesSettings.js +2 -1
  102. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  103. package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
  104. package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
  105. package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
  106. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
  107. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  108. package/dist/app/settings/account/search-entries.js +6 -0
  109. package/dist/app/settings/account/search-entries.js.map +1 -1
  110. package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
  111. package/dist/app/shared/DefaultSpinner.js +4 -5
  112. package/dist/app/shared/DefaultSpinner.js.map +1 -1
  113. package/dist/app/shared/ErrorBoundary.d.ts +1 -0
  114. package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
  115. package/dist/app/shared/ErrorBoundary.js +27 -12
  116. package/dist/app/shared/ErrorBoundary.js.map +1 -1
  117. package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
  118. package/dist/app/shared/PoweredByBadge.js +8 -49
  119. package/dist/app/shared/PoweredByBadge.js.map +1 -1
  120. package/dist/app/styles/agent-conversation.css +0 -23
  121. package/dist/app/styles/agent-native.css +1 -1
  122. package/dist/app/styles/theme-families.css +66 -0
  123. package/dist/composer/ComposerContextBanner.d.ts +22 -0
  124. package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
  125. package/dist/composer/ComposerContextBanner.js +9 -0
  126. package/dist/composer/ComposerContextBanner.js.map +1 -0
  127. package/dist/composer/ComposerModePicker.d.ts +26 -0
  128. package/dist/composer/ComposerModePicker.d.ts.map +1 -0
  129. package/dist/composer/ComposerModePicker.js +14 -0
  130. package/dist/composer/ComposerModePicker.js.map +1 -0
  131. package/dist/composer/index.d.ts +2 -0
  132. package/dist/composer/index.d.ts.map +1 -1
  133. package/dist/composer/index.js +2 -0
  134. package/dist/composer/index.js.map +1 -1
  135. package/dist/styles.css +1 -0
  136. package/package.json +3 -3
  137. package/src/app/agentkit/react/components.spec.tsx +12 -0
  138. package/src/app/agentkit/react/components.tsx +41 -22
  139. package/src/app/agentkit/react/context.tsx +17 -0
  140. package/src/app/agentkit/react/index.ts +1 -0
  141. package/src/app/agentkit/react/lifecycle.spec.tsx +116 -1
  142. package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
  143. package/src/app/agentkit/react/message-content-order.ts +246 -0
  144. package/src/app/agentkit/react/root.tsx +4 -0
  145. package/src/app/agentkit/react/styles.css +2 -2
  146. package/src/app/chat/AgentKitAssistantChat.spec.tsx +19 -0
  147. package/src/app/chat/AgentKitAssistantChat.tsx +10 -1
  148. package/src/app/chat/AgentPanel.header.spec.ts +72 -14
  149. package/src/app/chat/AgentPanel.tsx +97 -53
  150. package/src/app/chat/chat/surface-types.ts +8 -0
  151. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +217 -0
  152. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +306 -68
  153. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +329 -0
  154. package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
  155. package/src/app/chat/chat/widgets/DataChartWidget.tsx +48 -33
  156. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
  157. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
  158. package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
  159. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +301 -0
  160. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
  161. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
  162. package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
  163. package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
  164. package/src/app/i18n/catalogs/ar-SA.ts +9 -0
  165. package/src/app/i18n/catalogs/de-DE.ts +9 -0
  166. package/src/app/i18n/catalogs/en-US.ts +9 -0
  167. package/src/app/i18n/catalogs/es-ES.ts +10 -0
  168. package/src/app/i18n/catalogs/fr-FR.ts +10 -0
  169. package/src/app/i18n/catalogs/hi-IN.ts +9 -0
  170. package/src/app/i18n/catalogs/ja-JP.ts +9 -0
  171. package/src/app/i18n/catalogs/ko-KR.ts +9 -0
  172. package/src/app/i18n/catalogs/pt-BR.ts +9 -0
  173. package/src/app/i18n/catalogs/zh-CN.ts +8 -0
  174. package/src/app/i18n/catalogs/zh-TW.ts +9 -0
  175. package/src/app/providers/AppProviders.spec.tsx +71 -0
  176. package/src/app/providers/AppProviders.tsx +10 -0
  177. package/src/app/providers/ThemeFamilySync.tsx +6 -0
  178. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
  179. package/src/app/settings/account/PreferencesSettings.tsx +8 -0
  180. package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
  181. package/src/app/settings/account/search-entries.ts +6 -0
  182. package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
  183. package/src/app/shared/DefaultSpinner.tsx +4 -5
  184. package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
  185. package/src/app/shared/ErrorBoundary.tsx +37 -12
  186. package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
  187. package/src/app/shared/PoweredByBadge.tsx +8 -50
  188. package/src/app/styles/agent-conversation.css +0 -23
  189. package/src/app/styles/agent-native.css +1 -1
  190. package/src/app/styles/agent-native.spec.ts +18 -0
  191. package/src/app/styles/theme-families.css +66 -0
  192. package/src/app/styles/theme-families.spec.ts +29 -0
  193. package/src/composer/ComposerContextBanner.spec.tsx +74 -0
  194. package/src/composer/ComposerContextBanner.tsx +72 -0
  195. package/src/composer/ComposerModePicker.spec.tsx +101 -0
  196. package/src/composer/ComposerModePicker.tsx +98 -0
  197. package/src/composer/index.ts +9 -0
  198. package/src/styles.css +1 -0
@@ -47,8 +47,6 @@ const containerStyle = (
47
47
  opacity: 0.82,
48
48
  });
49
49
 
50
- const darkQuery = "(prefers-color-scheme: dark)";
51
-
52
50
  export function PoweredByBadge({
53
51
  position = "bottom-right",
54
52
  variant = "badge",
@@ -91,54 +89,36 @@ export function PoweredByBadge({
91
89
  .an-powered-badge-plain:hover {
92
90
  opacity: 0.82 !important;
93
91
  }
94
- .dark .an-powered-badge:not(.an-powered-badge-plain) {
92
+ :is(html.dark, html[data-theme="dark"]:not(.light)) .an-powered-badge:not(.an-powered-badge-plain) {
95
93
  background: rgba(255, 255, 255, 0.06) !important;
96
94
  border-color: rgba(255, 255, 255, 0.08) !important;
97
95
  color: rgba(215, 215, 215, 0.94) !important;
98
96
  }
99
- .dark .an-powered-logo-light {
97
+ :is(html.dark, html[data-theme="dark"]:not(.light)) .an-powered-logo-light {
100
98
  display: none;
101
99
  }
102
- .dark .an-powered-logo-dark {
100
+ :is(html.dark, html[data-theme="dark"]:not(.light)) .an-powered-logo-dark {
103
101
  display: block;
104
102
  }
105
- @media ${darkQuery} {
106
- .an-powered-badge:not(.an-powered-badge-plain) {
107
- background: rgba(255, 255, 255, 0.06) !important;
108
- border-color: rgba(255, 255, 255, 0.08) !important;
109
- color: rgba(215, 215, 215, 0.94) !important;
110
- }
111
- .an-powered-logo-light {
112
- display: none;
113
- }
114
- .an-powered-logo-dark {
115
- display: block;
116
- }
117
- }
118
- .light .an-powered-badge:not(.an-powered-badge-plain) {
103
+ :is(html.light, html[data-theme="light"]:not(.dark)) .an-powered-badge:not(.an-powered-badge-plain) {
119
104
  background: rgba(0, 0, 0, 0.05) !important;
120
105
  border-color: rgba(0, 0, 0, 0.06) !important;
121
106
  color: rgba(95, 95, 95, 0.95) !important;
122
107
  }
123
- .light .an-powered-logo-light {
108
+ :is(html.light, html[data-theme="light"]:not(.dark)) .an-powered-logo-light {
124
109
  display: block;
125
110
  }
126
- .light .an-powered-logo-dark {
111
+ :is(html.light, html[data-theme="light"]:not(.dark)) .an-powered-logo-dark {
127
112
  display: none;
128
113
  }
129
114
  .an-powered-badge:not(.an-powered-badge-plain):hover {
130
115
  opacity: 1 !important;
131
116
  color: rgba(70, 70, 70, 1) !important;
132
117
  }
133
- @media ${darkQuery} {
134
- .an-powered-badge:not(.an-powered-badge-plain):hover {
135
- color: rgba(238, 238, 238, 1) !important;
136
- }
137
- }
138
- .dark .an-powered-badge:not(.an-powered-badge-plain):hover {
118
+ :is(html.dark, html[data-theme="dark"]:not(.light)) .an-powered-badge:not(.an-powered-badge-plain):hover {
139
119
  color: rgba(238, 238, 238, 1) !important;
140
120
  }
141
- .light .an-powered-badge:not(.an-powered-badge-plain):hover {
121
+ :is(html.light, html[data-theme="light"]:not(.dark)) .an-powered-badge:not(.an-powered-badge-plain):hover {
142
122
  color: rgba(70, 70, 70, 1) !important;
143
123
  }
144
124
  `}</style>
@@ -205,32 +185,10 @@ export function OpenSourceBadge({
205
185
  max-width: calc(100vw - 2rem);
206
186
  }
207
187
  }
208
- .dark .an-open-source-badge {
209
- color: #00B5FF !important;
210
- }
211
- @media ${darkQuery} {
212
- .an-open-source-badge {
213
- color: #00B5FF !important;
214
- }
215
- }
216
- .light .an-open-source-badge {
217
- color: #00B5FF !important;
218
- }
219
188
  .an-open-source-badge:hover {
220
189
  opacity: 1 !important;
221
190
  color: #33C4FF !important;
222
191
  }
223
- @media ${darkQuery} {
224
- .an-open-source-badge:hover {
225
- color: #33C4FF !important;
226
- }
227
- }
228
- .dark .an-open-source-badge:hover {
229
- color: #33C4FF !important;
230
- }
231
- .light .an-open-source-badge:hover {
232
- color: #33C4FF !important;
233
- }
234
192
  `}</style>
235
193
  <a
236
194
  href="https://github.com/BuilderIO/agent-native"
@@ -219,17 +219,6 @@
219
219
  background: var(--shiki-dark-bg);
220
220
  }
221
221
 
222
- @media (prefers-color-scheme: dark) {
223
- :root:not(.light) .agent-conversation-shiki pre {
224
- background: var(--shiki-dark-bg, hsl(var(--muted) / 0.72));
225
- color: var(--shiki-dark, hsl(var(--foreground)));
226
- }
227
- :root:not(.light) .agent-conversation-shiki pre span {
228
- color: var(--shiki-dark);
229
- background: var(--shiki-dark-bg);
230
- }
231
- }
232
-
233
222
  .agent-conversation-tool {
234
223
  width: 100%;
235
224
  border: 1px solid hsl(var(--border) / 0.9);
@@ -727,18 +716,6 @@
727
716
  background: var(--shiki-dark-bg);
728
717
  }
729
718
 
730
- @media (prefers-color-scheme: dark) {
731
- :root:not(.light) .agent-markdown-shiki pre {
732
- background: var(--shiki-dark-bg);
733
- color: var(--shiki-dark);
734
- }
735
-
736
- :root:not(.light) .agent-markdown-shiki pre span {
737
- color: var(--shiki-dark);
738
- background: var(--shiki-dark-bg);
739
- }
740
- }
741
-
742
719
  .agent-tool-code .agent-markdown-shiki {
743
720
  margin: 0;
744
721
  border-radius: 0;
@@ -27,7 +27,7 @@
27
27
  @import "./tokens/agent-kit.css";
28
28
  @import "../agentkit/react/styles.css";
29
29
 
30
- @custom-variant dark (&:is(.dark *));
30
+ @custom-variant dark (&:where(.dark, .dark *));
31
31
 
32
32
  @plugin "@tailwindcss/typography";
33
33
 
@@ -3,6 +3,24 @@ import { readFileSync } from "node:fs";
3
3
  import { describe, expect, it } from "vitest";
4
4
 
5
5
  describe("agent-native shell surface tokens", () => {
6
+ it("uses the selected theme class for dark utilities and chat markdown", () => {
7
+ const toolkitCss = readFileSync(
8
+ new URL("./agent-native.css", import.meta.url),
9
+ { encoding: "utf8" },
10
+ );
11
+ const conversationCss = readFileSync(
12
+ new URL("./agent-conversation.css", import.meta.url),
13
+ { encoding: "utf8" },
14
+ );
15
+
16
+ expect(toolkitCss).toContain(
17
+ "@custom-variant dark (&:where(.dark, .dark *));",
18
+ );
19
+ expect(conversationCss).not.toContain("prefers-color-scheme: dark");
20
+ expect(conversationCss).toContain(".dark .agent-conversation-shiki pre");
21
+ expect(conversationCss).toContain(".dark .agent-markdown-shiki pre");
22
+ });
23
+
6
24
  it("keeps the composer surface opaque while preserving its muted blend", () => {
7
25
  const css = readFileSync(new URL("./agent-native.css", import.meta.url), {
8
26
  encoding: "utf8",
@@ -0,0 +1,66 @@
1
+ /*
2
+ * The agent-native family uses the shared Tailwind/shadcn palette from the
3
+ * Design template. App styles remain the original family by default.
4
+ */
5
+ :root[data-theme-family="agent-native"],
6
+ :root[data-theme-family="agent-native"][data-appearance] {
7
+ --background: 0 0% 100%;
8
+ --foreground: 0 0% 10%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 0 0% 10%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 0 0% 10%;
13
+ --primary: 0 0% 15%;
14
+ --primary-foreground: 0 0% 100%;
15
+ --secondary: 0 0% 95%;
16
+ --secondary-foreground: 0 0% 15%;
17
+ --muted: 0 0% 95%;
18
+ --muted-foreground: 0 0% 45%;
19
+ --accent: 0 0% 95%;
20
+ --accent-foreground: 0 0% 15%;
21
+ --destructive: 0 84% 60%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 0 0% 90%;
24
+ --input: 0 0% 90%;
25
+ --ring: 0 0% 40%;
26
+ --radius: 0.5rem;
27
+ --sidebar-background: 0 0% 97%;
28
+ --sidebar-foreground: 0 0% 45%;
29
+ --sidebar-primary: 0 0% 15%;
30
+ --sidebar-primary-foreground: 0 0% 100%;
31
+ --sidebar-accent: 0 0% 95%;
32
+ --sidebar-accent-foreground: 0 0% 15%;
33
+ --sidebar-border: 0 0% 90%;
34
+ --sidebar-ring: 0 0% 40%;
35
+ }
36
+
37
+ .dark[data-theme-family="agent-native"],
38
+ .dark[data-theme-family="agent-native"][data-appearance] {
39
+ --background: 0 0% 13%;
40
+ --foreground: 0 0% 90%;
41
+ --card: 0 0% 15%;
42
+ --card-foreground: 0 0% 90%;
43
+ --popover: 0 0% 15%;
44
+ --popover-foreground: 0 0% 90%;
45
+ --primary: 0 0% 75%;
46
+ --primary-foreground: 0 0% 10%;
47
+ --secondary: 0 0% 18%;
48
+ --secondary-foreground: 0 0% 90%;
49
+ --muted: 0 0% 16%;
50
+ --muted-foreground: 0 0% 60%;
51
+ --accent: 0 0% 18%;
52
+ --accent-foreground: 0 0% 96%;
53
+ --destructive: 0 91% 71%;
54
+ --destructive-foreground: 0 0% 98%;
55
+ --border: 0 0% 24%;
56
+ --input: 0 0% 24%;
57
+ --ring: 0 0% 60%;
58
+ --sidebar-background: 0 0% 10%;
59
+ --sidebar-foreground: 0 0% 60%;
60
+ --sidebar-primary: 0 0% 75%;
61
+ --sidebar-primary-foreground: 0 0% 10%;
62
+ --sidebar-accent: 0 0% 16%;
63
+ --sidebar-accent-foreground: 0 0% 90%;
64
+ --sidebar-border: 0 0% 20%;
65
+ --sidebar-ring: 0 0% 60%;
66
+ }
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ const css = readFileSync(
6
+ new URL("./theme-families.css", import.meta.url),
7
+ "utf8",
8
+ );
9
+
10
+ describe("theme families", () => {
11
+ it("provides a shared palette for the resolved light and dark modes", () => {
12
+ expect(css).toContain(':root[data-theme-family="agent-native"]');
13
+ expect(css).toContain('.dark[data-theme-family="agent-native"]');
14
+ expect(css).toContain("--background: 0 0% 100%");
15
+ expect(css).toContain("--background: 0 0% 13%");
16
+ expect(css).not.toContain("prefers-color-scheme");
17
+ });
18
+
19
+ it("keeps the selected family palette ahead of appearance presets", () => {
20
+ expect(css).toContain(
21
+ ':root[data-theme-family="agent-native"][data-appearance]',
22
+ );
23
+ expect(css).toContain(
24
+ '.dark[data-theme-family="agent-native"][data-appearance]',
25
+ );
26
+ expect(css).toContain("--background: 0 0% 100%");
27
+ expect(css).toContain("--background: 0 0% 13%");
28
+ });
29
+ });
@@ -0,0 +1,74 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { ComposerContextBanner } from "./ComposerContextBanner.js";
8
+
9
+ (
10
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
11
+ ).IS_REACT_ACT_ENVIRONMENT = true;
12
+
13
+ let container: HTMLDivElement;
14
+ let root: Root;
15
+
16
+ beforeEach(() => {
17
+ container = document.createElement("div");
18
+ document.body.appendChild(container);
19
+ root = createRoot(container);
20
+ });
21
+
22
+ afterEach(() => {
23
+ act(() => root.unmount());
24
+ container.remove();
25
+ });
26
+
27
+ describe("ComposerContextBanner", () => {
28
+ it("renders localized context actions and calls add and dismiss handlers", async () => {
29
+ const onAddContext = vi.fn();
30
+ const onDismiss = vi.fn();
31
+
32
+ await act(async () =>
33
+ root.render(
34
+ <ComposerContextBanner
35
+ ariaLabel="Context guidance"
36
+ addContextLabel="Add context"
37
+ dismissLabel="Dismiss context guidance"
38
+ onAddContext={onAddContext}
39
+ onDismiss={onDismiss}
40
+ >
41
+ Improve answers by adding data definitions.
42
+ </ComposerContextBanner>,
43
+ ),
44
+ );
45
+
46
+ const banner = container.querySelector<HTMLElement>(
47
+ '[data-agent-composer-slot="context-banner"]',
48
+ )!;
49
+ expect(banner.getAttribute("aria-label")).toBe("Context guidance");
50
+ expect(banner.classList).toContain("gap-1.5");
51
+ expect(banner.classList).toContain("px-2.5");
52
+ expect(banner.classList).toContain("py-2");
53
+ expect(banner.classList).toContain("rounded-lg");
54
+ expect(banner.textContent).toContain(
55
+ "Improve answers by adding data definitions.",
56
+ );
57
+
58
+ await act(async () =>
59
+ Array.from(container.querySelectorAll("button"))
60
+ .find((button) => button.textContent?.includes("Add context"))!
61
+ .click(),
62
+ );
63
+ await act(async () =>
64
+ container
65
+ .querySelector<HTMLButtonElement>(
66
+ 'button[aria-label="Dismiss context guidance"]',
67
+ )!
68
+ .click(),
69
+ );
70
+
71
+ expect(onAddContext).toHaveBeenCalledOnce();
72
+ expect(onDismiss).toHaveBeenCalledOnce();
73
+ });
74
+ });
@@ -0,0 +1,72 @@
1
+ import { IconPlus, IconSparkles, IconX } from "@tabler/icons-react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { Button } from "../ui/button.js";
5
+ import { cn } from "../utils.js";
6
+
7
+ interface ComposerContextBannerBaseProps {
8
+ /** Localized name for the banner's content. */
9
+ ariaLabel: string;
10
+ children: ReactNode;
11
+ /** Localized label for the action that opens or adds context. */
12
+ addContextLabel: string;
13
+ onAddContext: () => void;
14
+ icon?: ReactNode;
15
+ className?: string;
16
+ }
17
+
18
+ export type ComposerContextBannerProps = ComposerContextBannerBaseProps &
19
+ (
20
+ | { dismissLabel?: never; onDismiss?: never }
21
+ | { dismissLabel: string; onDismiss: () => void }
22
+ );
23
+
24
+ /** Shared informational banner that hosts can place in `composerContextBannerSlot`. */
25
+ export function ComposerContextBanner({
26
+ ariaLabel,
27
+ children,
28
+ addContextLabel,
29
+ onAddContext,
30
+ dismissLabel,
31
+ onDismiss,
32
+ icon,
33
+ className,
34
+ }: ComposerContextBannerProps) {
35
+ return (
36
+ <aside
37
+ aria-label={ariaLabel}
38
+ data-agent-composer-slot="context-banner"
39
+ className={cn(
40
+ "agent-composer-context-banner mx-auto mb-2 flex w-full max-w-3xl items-center gap-1.5 rounded-lg border border-border bg-background px-2.5 py-2 text-xs text-muted-foreground",
41
+ className,
42
+ )}
43
+ >
44
+ <span className="shrink-0 text-primary">
45
+ {icon ?? <IconSparkles aria-hidden="true" className="size-4" />}
46
+ </span>
47
+ <span className="min-w-0 flex-1">{children}</span>
48
+ <Button
49
+ className="shrink-0"
50
+ onClick={onAddContext}
51
+ size="xs"
52
+ type="button"
53
+ variant="outline"
54
+ >
55
+ {addContextLabel}
56
+ <IconPlus aria-hidden="true" />
57
+ </Button>
58
+ {onDismiss ? (
59
+ <Button
60
+ aria-label={dismissLabel}
61
+ className="shrink-0"
62
+ onClick={onDismiss}
63
+ size="icon-xs"
64
+ type="button"
65
+ variant="ghost"
66
+ >
67
+ <IconX aria-hidden="true" />
68
+ </Button>
69
+ ) : null}
70
+ </aside>
71
+ );
72
+ }
@@ -0,0 +1,101 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act, useState } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { ComposerModePicker } from "./ComposerModePicker.js";
8
+
9
+ (
10
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
11
+ ).IS_REACT_ACT_ENVIRONMENT = true;
12
+
13
+ let container: HTMLDivElement;
14
+ let root: Root;
15
+
16
+ beforeEach(() => {
17
+ container = document.createElement("div");
18
+ document.body.appendChild(container);
19
+ root = createRoot(container);
20
+ });
21
+
22
+ afterEach(() => {
23
+ act(() => root.unmount());
24
+ container.remove();
25
+ document.body.querySelectorAll('[role="menu"]').forEach((element) => {
26
+ if (!container.contains(element)) element.remove();
27
+ });
28
+ });
29
+
30
+ function ModePickerHarness({
31
+ onChange,
32
+ }: {
33
+ onChange: (value: string) => void;
34
+ }) {
35
+ const [value, setValue] = useState("ask");
36
+ return (
37
+ <ComposerModePicker
38
+ ariaLabel="Ask mode"
39
+ options={[
40
+ { value: "ask", label: "Ask", description: "Answer with context." },
41
+ { value: "default", label: "Default" },
42
+ ]}
43
+ value={value}
44
+ onValueChange={(next) => {
45
+ onChange(next);
46
+ setValue(next);
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ describe("ComposerModePicker", () => {
53
+ it("shows the selected host option and updates it through the shared menu", async () => {
54
+ const onChange = vi.fn();
55
+ await act(async () =>
56
+ root.render(<ModePickerHarness onChange={onChange} />),
57
+ );
58
+
59
+ const trigger = container.querySelector<HTMLButtonElement>(
60
+ '[data-agent-composer-slot="mode-picker"]',
61
+ )!;
62
+ expect(trigger.getAttribute("aria-label")).toBe("Ask mode: Ask");
63
+
64
+ await act(async () => {
65
+ trigger.dispatchEvent(
66
+ new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
67
+ );
68
+ trigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));
69
+ await Promise.resolve();
70
+ });
71
+
72
+ const defaultOption = Array.from(
73
+ document.body.querySelectorAll<HTMLElement>('[role="menuitemradio"]'),
74
+ ).find((option) => option.textContent?.trim() === "Default");
75
+ expect(defaultOption).toBeDefined();
76
+ await act(async () => defaultOption!.click());
77
+
78
+ expect(onChange).toHaveBeenCalledWith("default");
79
+ expect(trigger.textContent).toContain("Default");
80
+ expect(trigger.getAttribute("aria-label")).toBe("Ask mode: Default");
81
+ });
82
+
83
+ it("keeps an unavailable selected value visible instead of choosing another mode", async () => {
84
+ await act(async () =>
85
+ root.render(
86
+ <ComposerModePicker
87
+ ariaLabel="Ask mode"
88
+ options={[{ value: "ask", label: "Ask" }]}
89
+ value="custom"
90
+ onValueChange={() => {}}
91
+ />,
92
+ ),
93
+ );
94
+
95
+ const trigger = container.querySelector<HTMLButtonElement>(
96
+ '[data-agent-composer-slot="mode-picker"]',
97
+ );
98
+ expect(trigger?.textContent).toContain("custom");
99
+ expect(trigger?.getAttribute("aria-label")).toBe("Ask mode: custom");
100
+ });
101
+ });
@@ -0,0 +1,98 @@
1
+ import { IconChevronDown } from "@tabler/icons-react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuRadioGroup,
8
+ DropdownMenuRadioItem,
9
+ DropdownMenuTrigger,
10
+ } from "../ui/dropdown-menu.js";
11
+ import { cn } from "../utils.js";
12
+
13
+ export interface ComposerModeOption {
14
+ /** Stable host-defined mode identifier. */
15
+ value: string;
16
+ /** Localized visible label supplied by the host. */
17
+ label: string;
18
+ /** Optional localized explanation shown in the menu. */
19
+ description?: string;
20
+ /** Optional host-provided icon. */
21
+ icon?: ReactNode;
22
+ disabled?: boolean;
23
+ }
24
+
25
+ export interface ComposerModePickerProps {
26
+ /** Localized accessible name for this choice, such as "Ask mode". */
27
+ ariaLabel: string;
28
+ value: string;
29
+ options: readonly ComposerModeOption[];
30
+ onValueChange: (value: string) => void;
31
+ className?: string;
32
+ }
33
+
34
+ /**
35
+ * A shared, host-configurable mode picker for composer modes that are not part
36
+ * of the framework's built-in Act/Plan execution setting.
37
+ */
38
+ export function ComposerModePicker({
39
+ ariaLabel,
40
+ value,
41
+ options,
42
+ onValueChange,
43
+ className,
44
+ }: ComposerModePickerProps) {
45
+ const selectedOption = options.find((option) => option.value === value);
46
+ const selectedLabel = selectedOption?.label ?? value;
47
+
48
+ return (
49
+ <DropdownMenu>
50
+ <DropdownMenuTrigger asChild>
51
+ <button
52
+ type="button"
53
+ aria-label={`${ariaLabel}: ${selectedLabel}`}
54
+ data-agent-composer-slot="mode-picker"
55
+ className={cn(
56
+ "agent-composer-mode-picker inline-flex h-7 max-w-48 shrink-0 items-center gap-1 rounded-md px-2 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
57
+ className,
58
+ )}
59
+ >
60
+ {selectedOption?.icon}
61
+ <span className="min-w-0 truncate">{selectedLabel}</span>
62
+ <IconChevronDown
63
+ aria-hidden="true"
64
+ className="size-3 shrink-0 opacity-60"
65
+ />
66
+ </button>
67
+ </DropdownMenuTrigger>
68
+ <DropdownMenuContent
69
+ side="top"
70
+ align="start"
71
+ collisionPadding={8}
72
+ className="agent-composer-mode-picker-menu min-w-48"
73
+ >
74
+ <DropdownMenuRadioGroup value={value} onValueChange={onValueChange}>
75
+ {options.map((option) => (
76
+ <DropdownMenuRadioItem
77
+ key={option.value}
78
+ value={option.value}
79
+ indicator="check"
80
+ disabled={option.disabled}
81
+ className="agent-composer-mode-picker-option pe-3"
82
+ >
83
+ {option.icon}
84
+ <span className="min-w-0 flex-1">
85
+ <span className="block truncate">{option.label}</span>
86
+ {option.description ? (
87
+ <span className="mt-0.5 block whitespace-normal text-xs text-muted-foreground">
88
+ {option.description}
89
+ </span>
90
+ ) : null}
91
+ </span>
92
+ </DropdownMenuRadioItem>
93
+ ))}
94
+ </DropdownMenuRadioGroup>
95
+ </DropdownMenuContent>
96
+ </DropdownMenu>
97
+ );
98
+ }
@@ -1,4 +1,13 @@
1
1
  export { FileReference } from "./extensions/FileReference.js";
2
+ export {
3
+ ComposerContextBanner,
4
+ type ComposerContextBannerProps,
5
+ } from "./ComposerContextBanner.js";
6
+ export {
7
+ ComposerModePicker,
8
+ type ComposerModeOption,
9
+ type ComposerModePickerProps,
10
+ } from "./ComposerModePicker.js";
2
11
  export {
3
12
  useComposerDefaultActions,
4
13
  mergeComposerMenuItems,
package/src/styles.css CHANGED
@@ -18,6 +18,7 @@
18
18
  */
19
19
  @import "./theme/resource-icons.css";
20
20
  @import "./app-shell/prompt-home.css";
21
+ @import "./app/styles/theme-families.css";
21
22
  @import "./app/styles/agent-native.css";
22
23
 
23
24
  @source "./**/*.{js,ts,tsx}";