@elizaos/app-core 2.0.0-alpha.10

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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +90 -0
  3. package/src/App.tsx +472 -0
  4. package/src/actions/character.test.ts +139 -0
  5. package/src/actions/character.ts +152 -0
  6. package/src/actions/chat-helpers.ts +100 -0
  7. package/src/actions/cloud.ts +59 -0
  8. package/src/actions/index.ts +12 -0
  9. package/src/actions/lifecycle.ts +175 -0
  10. package/src/actions/onboarding.ts +46 -0
  11. package/src/actions/triggers.ts +190 -0
  12. package/src/ambient.d.ts +16 -0
  13. package/src/api/client.ts +5516 -0
  14. package/src/api/index.ts +1 -0
  15. package/src/autonomy/index.ts +477 -0
  16. package/src/bridge/capacitor-bridge.ts +295 -0
  17. package/src/bridge/electrobun-rpc.ts +58 -0
  18. package/src/bridge/electrobun-runtime.ts +28 -0
  19. package/src/bridge/index.ts +5 -0
  20. package/src/bridge/native-plugins.ts +134 -0
  21. package/src/bridge/plugin-bridge.ts +352 -0
  22. package/src/bridge/storage-bridge.ts +162 -0
  23. package/src/chat/index.ts +250 -0
  24. package/src/coding/index.ts +43 -0
  25. package/src/components/AdvancedPageView.tsx +362 -0
  26. package/src/components/AgentActivityBox.tsx +49 -0
  27. package/src/components/ApiKeyConfig.tsx +224 -0
  28. package/src/components/AppsPageView.tsx +52 -0
  29. package/src/components/AppsView.tsx +293 -0
  30. package/src/components/AvatarLoader.tsx +86 -0
  31. package/src/components/AvatarSelector.tsx +223 -0
  32. package/src/components/BscTradePanel.tsx +549 -0
  33. package/src/components/BugReportModal.tsx +499 -0
  34. package/src/components/CharacterView.tsx +1645 -0
  35. package/src/components/ChatAvatar.test.ts +96 -0
  36. package/src/components/ChatAvatar.tsx +147 -0
  37. package/src/components/ChatComposer.tsx +330 -0
  38. package/src/components/ChatMessage.tsx +448 -0
  39. package/src/components/ChatModalView.test.tsx +118 -0
  40. package/src/components/ChatModalView.tsx +125 -0
  41. package/src/components/ChatView.tsx +992 -0
  42. package/src/components/CloudSourceControls.tsx +80 -0
  43. package/src/components/CodingAgentSettingsSection.tsx +536 -0
  44. package/src/components/CommandPalette.tsx +284 -0
  45. package/src/components/CompanionSceneHost.tsx +497 -0
  46. package/src/components/CompanionShell.tsx +31 -0
  47. package/src/components/CompanionView.tsx +109 -0
  48. package/src/components/ConfigPageView.tsx +758 -0
  49. package/src/components/ConfigSaveFooter.tsx +41 -0
  50. package/src/components/ConfirmModal.tsx +379 -0
  51. package/src/components/ConnectionFailedBanner.tsx +91 -0
  52. package/src/components/ConnectorsPageView.tsx +13 -0
  53. package/src/components/ConversationsSidebar.tsx +279 -0
  54. package/src/components/CustomActionEditor.tsx +1125 -0
  55. package/src/components/CustomActionsPanel.tsx +288 -0
  56. package/src/components/CustomActionsView.tsx +322 -0
  57. package/src/components/DatabasePageView.tsx +55 -0
  58. package/src/components/DatabaseView.tsx +814 -0
  59. package/src/components/ElizaCloudDashboard.tsx +1696 -0
  60. package/src/components/EmotePicker.tsx +529 -0
  61. package/src/components/ErrorBoundary.tsx +76 -0
  62. package/src/components/FineTuningView.tsx +1077 -0
  63. package/src/components/GameView.tsx +552 -0
  64. package/src/components/GameViewOverlay.tsx +133 -0
  65. package/src/components/GlobalEmoteOverlay.tsx +155 -0
  66. package/src/components/Header.test.tsx +413 -0
  67. package/src/components/Header.tsx +403 -0
  68. package/src/components/HeartbeatsView.tsx +1003 -0
  69. package/src/components/InventoryView.tsx +385 -0
  70. package/src/components/KnowledgeView.tsx +1128 -0
  71. package/src/components/LanguageDropdown.tsx +188 -0
  72. package/src/components/LifoMonitorPanel.tsx +196 -0
  73. package/src/components/LifoSandboxView.tsx +499 -0
  74. package/src/components/LoadingScreen.tsx +77 -0
  75. package/src/components/LogsPageView.tsx +17 -0
  76. package/src/components/LogsView.tsx +239 -0
  77. package/src/components/MediaGalleryView.tsx +433 -0
  78. package/src/components/MediaSettingsSection.tsx +893 -0
  79. package/src/components/MessageContent.tsx +815 -0
  80. package/src/components/OnboardingWizard.test.tsx +107 -0
  81. package/src/components/OnboardingWizard.tsx +189 -0
  82. package/src/components/PairingView.tsx +110 -0
  83. package/src/components/PermissionsSection.tsx +1186 -0
  84. package/src/components/PluginsPageView.tsx +9 -0
  85. package/src/components/PluginsView.tsx +3157 -0
  86. package/src/components/ProviderSwitcher.tsx +908 -0
  87. package/src/components/RestartBanner.tsx +76 -0
  88. package/src/components/RuntimeView.tsx +460 -0
  89. package/src/components/SaveCommandModal.tsx +211 -0
  90. package/src/components/SecretsView.tsx +569 -0
  91. package/src/components/SettingsView.tsx +825 -0
  92. package/src/components/ShellOverlays.tsx +41 -0
  93. package/src/components/ShortcutsOverlay.tsx +155 -0
  94. package/src/components/SkillsView.tsx +1435 -0
  95. package/src/components/StartupFailureView.tsx +63 -0
  96. package/src/components/StreamView.tsx +483 -0
  97. package/src/components/StripeEmbeddedCheckout.tsx +155 -0
  98. package/src/components/SubscriptionStatus.tsx +640 -0
  99. package/src/components/SystemWarningBanner.tsx +71 -0
  100. package/src/components/ThemeToggle.tsx +100 -0
  101. package/src/components/TrajectoriesView.tsx +526 -0
  102. package/src/components/TrajectoryDetailView.tsx +426 -0
  103. package/src/components/TriggersView.tsx +1 -0
  104. package/src/components/VectorBrowserView.tsx +1633 -0
  105. package/src/components/VoiceConfigView.tsx +675 -0
  106. package/src/components/VrmStage.test.ts +219 -0
  107. package/src/components/VrmStage.tsx +432 -0
  108. package/src/components/WhatsAppQrOverlay.tsx +230 -0
  109. package/src/components/__tests__/chainConfig.test.ts +220 -0
  110. package/src/components/apps/AppDetailPane.tsx +242 -0
  111. package/src/components/apps/AppsCatalogGrid.tsx +137 -0
  112. package/src/components/apps/extensions/HyperscapeAppDetailPanel.tsx +577 -0
  113. package/src/components/apps/extensions/registry.ts +16 -0
  114. package/src/components/apps/extensions/types.ts +9 -0
  115. package/src/components/apps/helpers.ts +44 -0
  116. package/src/components/avatar/VrmAnimationLoader.test.ts +164 -0
  117. package/src/components/avatar/VrmAnimationLoader.ts +151 -0
  118. package/src/components/avatar/VrmBlinkController.ts +118 -0
  119. package/src/components/avatar/VrmCameraManager.ts +407 -0
  120. package/src/components/avatar/VrmEngine.ts +2678 -0
  121. package/src/components/avatar/VrmFootShadow.ts +96 -0
  122. package/src/components/avatar/VrmViewer.tsx +421 -0
  123. package/src/components/avatar/__tests__/VrmCameraManager.test.ts +168 -0
  124. package/src/components/avatar/__tests__/VrmEngine.test.ts +1574 -0
  125. package/src/components/avatar/mixamoVRMRigMap.ts +62 -0
  126. package/src/components/avatar/retargetMixamoFbxToVrm.ts +144 -0
  127. package/src/components/avatar/retargetMixamoGltfToVrm.ts +119 -0
  128. package/src/components/chainConfig.ts +380 -0
  129. package/src/components/companion/CompanionHeader.tsx +47 -0
  130. package/src/components/companion/CompanionSceneHost.tsx +1 -0
  131. package/src/components/companion/VrmStage.tsx +2 -0
  132. package/src/components/companion/__tests__/walletUtils.test.ts +742 -0
  133. package/src/components/companion/walletUtils.ts +290 -0
  134. package/src/components/companion-shell-styles.test.ts +142 -0
  135. package/src/components/companion-shell-styles.ts +270 -0
  136. package/src/components/confirm-delete-control.tsx +69 -0
  137. package/src/components/conversations/ConversationListItem.tsx +185 -0
  138. package/src/components/conversations/conversation-utils.ts +151 -0
  139. package/src/components/format.ts +131 -0
  140. package/src/components/index.ts +94 -0
  141. package/src/components/inventory/CopyableAddress.tsx +41 -0
  142. package/src/components/inventory/InventoryToolbar.tsx +142 -0
  143. package/src/components/inventory/NftGrid.tsx +99 -0
  144. package/src/components/inventory/TokenLogo.tsx +71 -0
  145. package/src/components/inventory/TokensTable.tsx +216 -0
  146. package/src/components/inventory/constants.ts +170 -0
  147. package/src/components/inventory/index.ts +29 -0
  148. package/src/components/inventory/media-url.test.ts +38 -0
  149. package/src/components/inventory/media-url.ts +36 -0
  150. package/src/components/inventory/useInventoryData.ts +460 -0
  151. package/src/components/knowledge-upload-image.ts +215 -0
  152. package/src/components/labels.ts +46 -0
  153. package/src/components/onboarding/ActivateStep.tsx +30 -0
  154. package/src/components/onboarding/ConnectionStep.tsx +1530 -0
  155. package/src/components/onboarding/IdentityStep.tsx +147 -0
  156. package/src/components/onboarding/OnboardingPanel.tsx +39 -0
  157. package/src/components/onboarding/OnboardingStepNav.tsx +31 -0
  158. package/src/components/onboarding/PermissionsStep.tsx +20 -0
  159. package/src/components/onboarding/RpcStep.tsx +402 -0
  160. package/src/components/onboarding/WakeUpStep.tsx +184 -0
  161. package/src/components/permissions/PermissionIcon.tsx +25 -0
  162. package/src/components/permissions/StreamingPermissions.tsx +413 -0
  163. package/src/components/plugins/showcase-data.ts +481 -0
  164. package/src/components/shared/ShellHeaderControls.tsx +193 -0
  165. package/src/components/shared-companion-scene-context.ts +15 -0
  166. package/src/components/skeletons.tsx +88 -0
  167. package/src/components/stream/ActivityFeed.tsx +113 -0
  168. package/src/components/stream/AvatarPip.tsx +10 -0
  169. package/src/components/stream/ChatContent.tsx +126 -0
  170. package/src/components/stream/ChatTicker.tsx +55 -0
  171. package/src/components/stream/IdleContent.tsx +73 -0
  172. package/src/components/stream/StatusBar.tsx +469 -0
  173. package/src/components/stream/StreamSettings.tsx +506 -0
  174. package/src/components/stream/StreamTerminal.tsx +94 -0
  175. package/src/components/stream/StreamVoiceConfig.tsx +160 -0
  176. package/src/components/stream/helpers.ts +134 -0
  177. package/src/components/stream/overlays/OverlayLayer.tsx +75 -0
  178. package/src/components/stream/overlays/built-in/ActionTickerWidget.tsx +64 -0
  179. package/src/components/stream/overlays/built-in/AlertPopupWidget.tsx +87 -0
  180. package/src/components/stream/overlays/built-in/BrandingWidget.tsx +51 -0
  181. package/src/components/stream/overlays/built-in/CustomHtmlWidget.tsx +105 -0
  182. package/src/components/stream/overlays/built-in/PeonGlassWidget.tsx +265 -0
  183. package/src/components/stream/overlays/built-in/PeonHudWidget.tsx +247 -0
  184. package/src/components/stream/overlays/built-in/PeonSakuraWidget.tsx +278 -0
  185. package/src/components/stream/overlays/built-in/ThoughtBubbleWidget.tsx +77 -0
  186. package/src/components/stream/overlays/built-in/ViewerCountWidget.tsx +46 -0
  187. package/src/components/stream/overlays/built-in/index.ts +13 -0
  188. package/src/components/stream/overlays/registry.ts +22 -0
  189. package/src/components/stream/overlays/types.ts +90 -0
  190. package/src/components/stream/overlays/useOverlayLayout.ts +218 -0
  191. package/src/components/trajectory-format.ts +50 -0
  192. package/src/components/ui-badges.tsx +109 -0
  193. package/src/components/ui-switch.tsx +57 -0
  194. package/src/components/vector-browser-three.ts +27 -0
  195. package/src/config/config-catalog.ts +1092 -0
  196. package/src/config/config-field.tsx +1901 -0
  197. package/src/config/config-renderer.tsx +730 -0
  198. package/src/config/index.ts +11 -0
  199. package/src/config/ui-renderer.tsx +1751 -0
  200. package/src/config/ui-spec.ts +256 -0
  201. package/src/events/index.ts +89 -0
  202. package/src/hooks/index.ts +13 -0
  203. package/src/hooks/useBugReport.tsx +43 -0
  204. package/src/hooks/useCanvasWindow.ts +372 -0
  205. package/src/hooks/useChatAvatarVoice.ts +111 -0
  206. package/src/hooks/useContextMenu.ts +127 -0
  207. package/src/hooks/useKeyboardShortcuts.ts +86 -0
  208. package/src/hooks/useLifoSync.ts +143 -0
  209. package/src/hooks/useMemoryMonitor.ts +334 -0
  210. package/src/hooks/useRenderGuard.ts +43 -0
  211. package/src/hooks/useRetakeCapture.ts +67 -0
  212. package/src/hooks/useStreamPopoutNavigation.ts +27 -0
  213. package/src/hooks/useTimeout.ts +37 -0
  214. package/src/hooks/useVoiceChat.ts +1441 -0
  215. package/src/hooks/useWhatsAppPairing.ts +123 -0
  216. package/src/i18n/index.ts +76 -0
  217. package/src/i18n/locales/en.json +1194 -0
  218. package/src/i18n/locales/es.json +1194 -0
  219. package/src/i18n/locales/ko.json +1194 -0
  220. package/src/i18n/locales/pt.json +1194 -0
  221. package/src/i18n/locales/zh-CN.json +1194 -0
  222. package/src/i18n/messages.ts +21 -0
  223. package/src/index.ts +6 -0
  224. package/src/navigation/index.ts +282 -0
  225. package/src/navigation.test.ts +189 -0
  226. package/src/onboarding-config.test.ts +104 -0
  227. package/src/onboarding-config.ts +114 -0
  228. package/src/platform/browser-launch.test.ts +94 -0
  229. package/src/platform/browser-launch.ts +149 -0
  230. package/src/platform/index.ts +58 -0
  231. package/src/platform/init.ts +236 -0
  232. package/src/platform/lifo.ts +215 -0
  233. package/src/providers/index.ts +99 -0
  234. package/src/state/AppContext.tsx +5846 -0
  235. package/src/state/index.ts +6 -0
  236. package/src/state/internal.ts +86 -0
  237. package/src/state/onboarding-resume.test.ts +135 -0
  238. package/src/state/onboarding-resume.ts +263 -0
  239. package/src/state/parsers.test.ts +124 -0
  240. package/src/state/parsers.ts +308 -0
  241. package/src/state/persistence.ts +321 -0
  242. package/src/state/shell-routing.ts +32 -0
  243. package/src/state/types.ts +701 -0
  244. package/src/state/ui-preferences.ts +3 -0
  245. package/src/state/useApp.ts +23 -0
  246. package/src/state/vrm.ts +76 -0
  247. package/src/stories/AppMockProvider.tsx +32 -0
  248. package/src/stories/ChatEmptyState.stories.tsx +27 -0
  249. package/src/stories/ChatMessage.stories.tsx +115 -0
  250. package/src/stories/CompanionHeader.stories.tsx +74 -0
  251. package/src/stories/CompanionView.stories.tsx +33 -0
  252. package/src/stories/ConversationListItem.stories.tsx +102 -0
  253. package/src/stories/TypingIndicator.stories.tsx +28 -0
  254. package/src/styles/anime.css +6324 -0
  255. package/src/styles/base.css +196 -0
  256. package/src/styles/onboarding-game.css +738 -0
  257. package/src/styles/styles.css +2087 -0
  258. package/src/styles/xterm.css +241 -0
  259. package/src/types/index.ts +715 -0
  260. package/src/types/react-test-renderer.d.ts +45 -0
  261. package/src/utils/asset-url.ts +110 -0
  262. package/src/utils/assistant-text.ts +172 -0
  263. package/src/utils/clipboard.ts +41 -0
  264. package/src/utils/desktop-dialogs.ts +80 -0
  265. package/src/utils/index.ts +6 -0
  266. package/src/utils/number-parsing.ts +125 -0
  267. package/src/utils/openExternalUrl.ts +20 -0
  268. package/src/utils/spoken-text.ts +65 -0
  269. package/src/utils/streaming-text.ts +120 -0
  270. package/src/voice/index.ts +1 -0
  271. package/src/voice/types.ts +197 -0
  272. package/src/wallet-rpc.ts +176 -0
  273. package/test/app/AppContext.pty-sessions.test.tsx +143 -0
  274. package/test/app/MessageContent.test.tsx +326 -0
  275. package/test/app/PermissionsOnboarding.test.tsx +356 -0
  276. package/test/app/PermissionsSection.test.tsx +573 -0
  277. package/test/app/advanced-trajectory-fine-tuning.e2e.test.ts +393 -0
  278. package/test/app/agent-activity-box.test.tsx +132 -0
  279. package/test/app/agent-transfer-lock.test.ts +274 -0
  280. package/test/app/api-client-electron-fallback.test.ts +139 -0
  281. package/test/app/api-client-timeout.test.ts +75 -0
  282. package/test/app/api-client-ws.test.ts +98 -0
  283. package/test/app/api-client.ws-max-reconnect.test.ts +139 -0
  284. package/test/app/api-client.ws-reconnect.test.ts +157 -0
  285. package/test/app/app-context-autonomy-events.test.ts +478 -0
  286. package/test/app/apps-page-view.test.ts +114 -0
  287. package/test/app/apps-view.test.ts +769 -0
  288. package/test/app/autonomous-workflows.e2e.test.ts +765 -0
  289. package/test/app/autonomy-events.test.ts +150 -0
  290. package/test/app/avatar-selector.test.tsx +52 -0
  291. package/test/app/bsc-trade-panel.test.tsx +134 -0
  292. package/test/app/bug-report-modal.test.tsx +353 -0
  293. package/test/app/character-customization.e2e.test.ts +1199 -0
  294. package/test/app/chat-advanced-features.e2e.test.ts +706 -0
  295. package/test/app/chat-composer.test.tsx +181 -0
  296. package/test/app/chat-language-header.test.ts +64 -0
  297. package/test/app/chat-message.test.tsx +222 -0
  298. package/test/app/chat-modal-view.test.tsx +191 -0
  299. package/test/app/chat-routine-filter.test.ts +96 -0
  300. package/test/app/chat-send-lock.test.ts +1465 -0
  301. package/test/app/chat-stream-api-client.test.tsx +390 -0
  302. package/test/app/chat-view-game-modal.test.tsx +661 -0
  303. package/test/app/chat-view.test.tsx +877 -0
  304. package/test/app/cloud-api.e2e.test.ts +258 -0
  305. package/test/app/cloud-login-flow.e2e.test.ts +494 -0
  306. package/test/app/cloud-login-lock.test.ts +411 -0
  307. package/test/app/command-palette.test.tsx +184 -0
  308. package/test/app/command-registry.test.ts +75 -0
  309. package/test/app/companion-greeting-wave.test.tsx +425 -0
  310. package/test/app/companion-stale-conversation.test.tsx +447 -0
  311. package/test/app/companion-view.test.tsx +686 -0
  312. package/test/app/confirm-delete-control.test.ts +79 -0
  313. package/test/app/confirm-modal.test.tsx +219 -0
  314. package/test/app/connectors-ui.e2e.test.ts +508 -0
  315. package/test/app/conversations-sidebar-game-modal.test.tsx +260 -0
  316. package/test/app/conversations-sidebar.test.tsx +160 -0
  317. package/test/app/custom-actions-smoke.test.ts +387 -0
  318. package/test/app/custom-avatar-api-client.test.ts +207 -0
  319. package/test/app/desktop-utils.test.ts +145 -0
  320. package/test/app/electrobun-rpc-bridge.test.ts +83 -0
  321. package/test/app/events.test.ts +88 -0
  322. package/test/app/export-import-flows.e2e.test.ts +700 -0
  323. package/test/app/fine-tuning-view.test.ts +471 -0
  324. package/test/app/game-view-auth-session.test.tsx +186 -0
  325. package/test/app/game-view.test.ts +444 -0
  326. package/test/app/global-emote-overlay.test.tsx +106 -0
  327. package/test/app/header-status.test.tsx +149 -0
  328. package/test/app/i18n.test.ts +152 -0
  329. package/test/app/inventory-bsc-view.test.ts +940 -0
  330. package/test/app/knowledge-ui.e2e.test.ts +762 -0
  331. package/test/app/knowledge-upload-helpers.test.ts +124 -0
  332. package/test/app/lifecycle-lock.test.ts +267 -0
  333. package/test/app/lifo-popout-utils.test.ts +208 -0
  334. package/test/app/lifo-safe-endpoint.test.ts +34 -0
  335. package/test/app/loading-screen.test.tsx +45 -0
  336. package/test/app/memory-monitor.test.ts +332 -0
  337. package/test/app/navigation.test.tsx +22 -0
  338. package/test/app/onboarding-finish-lock.test.ts +663 -0
  339. package/test/app/onboarding-language.test.tsx +160 -0
  340. package/test/app/onboarding-steps.test.tsx +375 -0
  341. package/test/app/open-external-url.test.ts +65 -0
  342. package/test/app/pages-navigation-smoke.e2e.test.ts +633 -0
  343. package/test/app/pairing-lock.test.ts +260 -0
  344. package/test/app/pairing-view.test.tsx +74 -0
  345. package/test/app/permissions-section.test.ts +432 -0
  346. package/test/app/plugin-bridge.test.ts +109 -0
  347. package/test/app/plugins-ui.e2e.test.ts +605 -0
  348. package/test/app/plugins-view-game-modal.test.tsx +650 -0
  349. package/test/app/plugins-view-toggle-restart.test.ts +129 -0
  350. package/test/app/provider-dropdown-default.test.tsx +302 -0
  351. package/test/app/restart-banner.test.tsx +197 -0
  352. package/test/app/retake-capture.test.ts +84 -0
  353. package/test/app/sandbox-api-client.test.ts +108 -0
  354. package/test/app/save-command-modal.test.tsx +109 -0
  355. package/test/app/secrets-view.test.tsx +92 -0
  356. package/test/app/settings-control-styles.test.tsx +142 -0
  357. package/test/app/settings-reset.e2e.test.ts +726 -0
  358. package/test/app/settings-sections.e2e.test.ts +614 -0
  359. package/test/app/shared-format.test.ts +44 -0
  360. package/test/app/shared-switch.test.ts +69 -0
  361. package/test/app/shell-mode-switching.e2e.test.ts +829 -0
  362. package/test/app/shell-mode-tab-memory.test.tsx +58 -0
  363. package/test/app/shell-overlays.test.tsx +50 -0
  364. package/test/app/shortcuts-overlay.test.tsx +111 -0
  365. package/test/app/sse-interruption.test.ts +122 -0
  366. package/test/app/startup-asset-missing.e2e.test.ts +126 -0
  367. package/test/app/startup-backend-missing.e2e.test.ts +118 -0
  368. package/test/app/startup-chat.e2e.test.ts +305 -0
  369. package/test/app/startup-conversation-restore.test.tsx +344 -0
  370. package/test/app/startup-failure-view.test.tsx +103 -0
  371. package/test/app/startup-onboarding.e2e.test.ts +618 -0
  372. package/test/app/startup-timeout.test.tsx +80 -0
  373. package/test/app/startup-token-401.e2e.test.ts +103 -0
  374. package/test/app/stream-helpers.test.ts +46 -0
  375. package/test/app/stream-popout-navigation.test.tsx +41 -0
  376. package/test/app/stream-status-bar.test.tsx +89 -0
  377. package/test/app/theme-toggle.test.tsx +33 -0
  378. package/test/app/training-api-client.test.ts +128 -0
  379. package/test/app/trajectories-view.test.tsx +220 -0
  380. package/test/app/triggers-api-client.test.ts +77 -0
  381. package/test/app/triggers-navigation.test.ts +113 -0
  382. package/test/app/triggers-view.e2e.test.ts +674 -0
  383. package/test/app/update-channel-lock.test.ts +259 -0
  384. package/test/app/vector-browser.async-cleanup.test.tsx +367 -0
  385. package/test/app/vector-browser.e2e.test.ts +653 -0
  386. package/test/app/vrm-stage.test.tsx +351 -0
  387. package/test/app/vrm-viewer.test.tsx +298 -0
  388. package/test/app/wallet-api-save-lock.test.ts +298 -0
  389. package/test/app/wallet-hooks.test.ts +405 -0
  390. package/test/app/wallet-ui-flows.e2e.test.ts +556 -0
  391. package/test/avatar/asset-url.test.ts +90 -0
  392. package/test/avatar/avatar-selector.test.ts +173 -0
  393. package/test/avatar/mixamo-vrm-rig-map.test.ts +111 -0
  394. package/test/avatar/voice-chat-streaming-text.test.ts +96 -0
  395. package/test/avatar/voice-chat.test.ts +391 -0
  396. package/test/ui/command-palette-commands.test.ts +57 -0
  397. package/test/ui/ui-renderer.test.ts +39 -0
  398. package/tsconfig.build.json +19 -0
  399. package/tsconfig.json +20 -0
@@ -0,0 +1,730 @@
1
+ /**
2
+ * ConfigRenderer — Schema-driven plugin config form (React port).
3
+ *
4
+ * Takes a JSON Schema + ConfigUiHints, resolves each property to a field type
5
+ * via the catalog, and renders via the registry.
6
+ *
7
+ * Phase 2 features (json-render parity):
8
+ * - Rich visibility: evaluateVisibility() with LogicExpression support
9
+ * - Validation checks: declarative checks alongside Zod validation
10
+ * - Actions: onAction() callback for executing catalog actions
11
+ * - Prompt generation: registry.catalog.prompt() for AI system prompts
12
+ */
13
+
14
+ import React, {
15
+ forwardRef,
16
+ useCallback,
17
+ useImperativeHandle,
18
+ useMemo,
19
+ useState,
20
+ } from "react";
21
+ import { useApp } from "../state";
22
+ import type { ConfigUiHint, PluginUiTheme } from "../types";
23
+ import type {
24
+ FieldRegistry,
25
+ FieldRenderer,
26
+ FieldRenderProps,
27
+ JsonSchemaObject,
28
+ ResolvedField,
29
+ } from "./config-catalog";
30
+ import {
31
+ defaultCatalog,
32
+ defineRegistry,
33
+ evaluateShowIf,
34
+ evaluateVisibility,
35
+ resolveFields,
36
+ runValidation,
37
+ } from "./config-catalog";
38
+ import { ConfigField } from "./config-field";
39
+
40
+ // ── Props ──────────────────────────────────────────────────────────────
41
+
42
+ export interface ConfigRendererProps {
43
+ /** JSON Schema describing the config structure (type: "object"). */
44
+ schema: JsonSchemaObject | null;
45
+ /** UI rendering hints keyed by property name. */
46
+ hints?: Record<string, ConfigUiHint>;
47
+ /** Current config values keyed by property name. */
48
+ values?: Record<string, unknown>;
49
+ /** Which keys currently have values set (for status dots). */
50
+ setKeys?: Set<string>;
51
+ /** Field registry (catalog + renderers + action handlers). */
52
+ registry: FieldRegistry;
53
+ /** Plugin ID (used for revealing sensitive values via API). */
54
+ pluginId?: string;
55
+ /** Callback to reveal a sensitive field's real value. */
56
+ revealSecret?: (pluginId: string, key: string) => Promise<string | null>;
57
+ /** Callback when a field value changes. */
58
+ onChange?: (key: string, value: unknown) => void;
59
+ /** Render function for each field — receives renderProps and the resolved renderer. */
60
+ renderField?: (
61
+ renderProps: FieldRenderProps,
62
+ renderer: FieldRenderer,
63
+ ) => React.ReactNode;
64
+ /** Show a validation error summary above the form fields when errors exist. Defaults to true. */
65
+ showValidationSummary?: boolean;
66
+ /** Partial theme overrides for plugin UI tokens. */
67
+ theme?: Partial<PluginUiTheme>;
68
+ }
69
+
70
+ /** Handle exposed by ConfigRenderer via ref for parent-driven validation. */
71
+ export interface ConfigRendererHandle {
72
+ /** Run validation on all visible fields. Returns true if the form is valid (no errors). */
73
+ validateAll: () => boolean;
74
+ }
75
+
76
+ // ── Group icons ────────────────────────────────────────────────────────
77
+
78
+ const GROUP_ICONS: Record<string, string> = {
79
+ // Auth & Security
80
+ auth: "\u{1F511}",
81
+ authentication: "\u{1F511}",
82
+ security: "\u{1F6E1}\uFE0F",
83
+ permissions: "\u{1F512}",
84
+ "api keys": "\u{1F511}",
85
+ // Connection & Network
86
+ connection: "\u{1F517}",
87
+ network: "\u{1F310}",
88
+ api: "\u{1F50C}",
89
+ webhook: "\u{1F4E1}",
90
+ // Models & AI
91
+ models: "\u{1F916}",
92
+ model: "\u{1F916}",
93
+ "ai models": "\u{1F916}",
94
+ "text generation": "\u{1F916}",
95
+ embeddings: "\u{1F9E0}",
96
+ // Behavior & Config
97
+ behavior: "\u2699\uFE0F",
98
+ configuration: "\u2699\uFE0F",
99
+ general: "\u2699\uFE0F",
100
+ defaults: "\u2699\uFE0F",
101
+ advanced: "\u{1F527}",
102
+ features: "\u2728",
103
+ // Time & Scheduling
104
+ timing: "\u23F1\uFE0F",
105
+ scheduling: "\u{1F4C5}",
106
+ // Storage & Data
107
+ storage: "\u{1F4BE}",
108
+ bucket: "\u{1F4E6}",
109
+ paths: "\u{1F4C2}",
110
+ output: "\u{1F4E4}",
111
+ repository: "\u{1F4DA}",
112
+ // Communication
113
+ messaging: "\u{1F4AC}",
114
+ channels: "\u{1F4E2}",
115
+ chatrooms: "\u{1F4AC}",
116
+ voice: "\u{1F3A4}",
117
+ speech: "\u{1F3A4}",
118
+ "speech-to-text": "\u{1F3A4}",
119
+ // Identity
120
+ identity: "\u{1F464}",
121
+ "client identity": "\u{1F464}",
122
+ session: "\u{1F464}",
123
+ // Display & Media
124
+ display: "\u{1F3A8}",
125
+ media: "\u{1F3AC}",
126
+ // Notifications
127
+ notifications: "\u{1F514}",
128
+ logging: "\u{1F4DD}",
129
+ // Finance & Trading
130
+ trading: "\u{1F4C8}",
131
+ "risk management": "\u{1F6E1}\uFE0F",
132
+ wallet: "\u{1F4B0}",
133
+ payment: "\u{1F4B3}",
134
+ pricing: "\u{1F4B2}",
135
+ // Blockchain
136
+ blockchain: "\u26D3\uFE0F",
137
+ ethereum: "\u26D3\uFE0F",
138
+ solana: "\u26D3\uFE0F",
139
+ base: "\u26D3\uFE0F",
140
+ arbitrum: "\u26D3\uFE0F",
141
+ bsc: "\u26D3\uFE0F",
142
+ testnets: "\u{1F9EA}",
143
+ "dex config": "\u{1F4CA}",
144
+ // Social
145
+ posting: "\u{1F4DD}",
146
+ "x/twitter authentication": "\u{1F511}",
147
+ "x/twitter behavior": "\u{1F426}",
148
+ // System
149
+ limits: "\u{1F4CF}",
150
+ providers: "\u{1F50C}",
151
+ commands: "\u2318",
152
+ actions: "\u26A1",
153
+ policies: "\u{1F4DC}",
154
+ autonomy: "\u{1F916}",
155
+ "background jobs": "\u{1F504}",
156
+ "n8n connection": "\u{1F517}",
157
+ app: "\u{1F4F1}",
158
+ };
159
+
160
+ function groupIcon(group: string): string {
161
+ return GROUP_ICONS[group.toLowerCase()] ?? "\u25A0";
162
+ }
163
+
164
+ // ── Width → Tailwind column span ───────────────────────────────────────
165
+
166
+ function widthClass(width: "full" | "half" | "third"): string {
167
+ switch (width) {
168
+ case "half":
169
+ return "col-span-6 sm:col-span-3";
170
+ case "third":
171
+ return "col-span-6 sm:col-span-2";
172
+ default:
173
+ return "col-span-6";
174
+ }
175
+ }
176
+
177
+ // ── Validation Summary ─────────────────────────────────────────────────
178
+
179
+ interface ValidationSummaryProps {
180
+ /** Map of field key to its error messages. */
181
+ fieldErrors: Map<string, string[]>;
182
+ /** Map of field key to its display label. */
183
+ fieldLabels: Map<string, string>;
184
+ /** Plugin ID for scoping field IDs. */
185
+ pluginId?: string;
186
+ }
187
+
188
+ function ValidationSummary({
189
+ fieldErrors,
190
+ fieldLabels,
191
+ pluginId,
192
+ }: ValidationSummaryProps) {
193
+ const { t } = useApp();
194
+ const errorEntries = [...fieldErrors.entries()].filter(
195
+ ([, errors]) => errors.length > 0,
196
+ );
197
+ const totalErrors = errorEntries.length;
198
+
199
+ if (totalErrors === 0) return null;
200
+
201
+ const handleFieldClick = (key: string) => {
202
+ const el = document.getElementById(
203
+ pluginId ? `field-${pluginId}-${key}` : `field-${key}`,
204
+ );
205
+ if (el) {
206
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
207
+ }
208
+ };
209
+
210
+ return (
211
+ <div
212
+ className="mb-4 border border-[var(--destructive)] bg-[color-mix(in_srgb,var(--destructive)_6%,transparent)] px-4 py-3 rounded-sm"
213
+ role="alert"
214
+ >
215
+ <div className="text-[13px] font-semibold text-[var(--destructive)] mb-2">
216
+ {totalErrors} {totalErrors === 1 ? "field needs" : "fields need"}{" "}
217
+ {t("config-renderer.attention")}
218
+ </div>
219
+ <ul className="list-none m-0 p-0 flex flex-col gap-1">
220
+ {errorEntries.map(([key]) => (
221
+ <li key={key}>
222
+ <button
223
+ type="button"
224
+ className="text-[12px] text-[var(--destructive)] cursor-pointer bg-transparent border-none p-0 hover:underline transition-all text-left flex items-center gap-1.5"
225
+ onClick={() => handleFieldClick(key)}
226
+ >
227
+ <span className="opacity-60">{t("config-renderer.Rarr")}</span>
228
+ <span>{fieldLabels.get(key) ?? key}</span>
229
+ </button>
230
+ </li>
231
+ ))}
232
+ </ul>
233
+ </div>
234
+ );
235
+ }
236
+
237
+ // ── Theme mapping ──────────────────────────────────────────────────────
238
+
239
+ /** Maps PluginUiTheme keys to CSS variable names. */
240
+ const THEME_TO_CSS: Record<keyof PluginUiTheme, string> = {
241
+ fieldGap: "--plugin-field-gap",
242
+ groupGap: "--plugin-group-gap",
243
+ sectionPadding: "--plugin-section-padding",
244
+ labelSize: "--plugin-label-size",
245
+ helpSize: "--plugin-help-size",
246
+ errorSize: "--plugin-error-size",
247
+ labelColor: "--plugin-label",
248
+ helpColor: "--plugin-help",
249
+ errorColor: "--plugin-error",
250
+ borderColor: "--plugin-border",
251
+ focusRing: "--plugin-focus-ring",
252
+ inputHeight: "--plugin-input-height",
253
+ maxFieldWidth: "--plugin-max-field-width",
254
+ };
255
+
256
+ // ── Component ──────────────────────────────────────────────────────────
257
+
258
+ export const ConfigRenderer = forwardRef<
259
+ ConfigRendererHandle,
260
+ ConfigRendererProps
261
+ >(function ConfigRenderer(
262
+ {
263
+ schema,
264
+ hints = {},
265
+ values = {},
266
+ setKeys = new Set(),
267
+ registry,
268
+ pluginId = "",
269
+ revealSecret,
270
+ onChange,
271
+ renderField: renderFieldOverride,
272
+ showValidationSummary = true,
273
+ theme,
274
+ }: ConfigRendererProps,
275
+ ref,
276
+ ) {
277
+ const [advancedOpen, setAdvancedOpen] = useState(false);
278
+ const [fieldErrors, setFieldErrors] = useState<Map<string, string[]>>(
279
+ new Map(),
280
+ );
281
+
282
+ // ── Validation pipeline (4 stages) ──────────────────────────────────
283
+
284
+ const validateField = useCallback(
285
+ (field: ResolvedField, value: unknown): string[] => {
286
+ const errors: string[] = [];
287
+
288
+ // 1. Required check
289
+ if (field.required && (value == null || value === "")) {
290
+ errors.push("This field is required.");
291
+ }
292
+
293
+ // 2. Zod validation
294
+ if (value != null && value !== "") {
295
+ const result = registry.catalog.validate(field.fieldType, value);
296
+ if (!result.success) {
297
+ errors.push(...result.error.issues.map((i) => i.message));
298
+ }
299
+ }
300
+
301
+ // 3. Pattern validation from hints
302
+ if (field.hint.pattern && typeof value === "string" && value) {
303
+ try {
304
+ // Guard against ReDoS: reject overly long or nested-quantifier patterns
305
+ const pat = field.hint.pattern;
306
+ if (pat.length <= 200 && !/([+*])\)?[+*]/.test(pat)) {
307
+ if (!new RegExp(pat).test(value)) {
308
+ errors.push(field.hint.patternError ?? "Invalid format.");
309
+ }
310
+ }
311
+ } catch {
312
+ // invalid regex in hint — skip
313
+ }
314
+ }
315
+
316
+ // 4. Declarative validation checks (json-render style)
317
+ if (field.validation) {
318
+ const checkResult = runValidation(
319
+ field.validation,
320
+ value,
321
+ values,
322
+ registry.catalog.functions,
323
+ );
324
+ if (!checkResult.valid) {
325
+ errors.push(...checkResult.errors);
326
+ }
327
+ }
328
+
329
+ return errors;
330
+ },
331
+ [registry, values],
332
+ );
333
+
334
+ // ── Visibility evaluation ────────────────────────────────────────────
335
+
336
+ const isFieldVisible = useCallback(
337
+ (field: ResolvedField): boolean => {
338
+ // Hidden fields are never visible
339
+ if (field.hidden) return false;
340
+
341
+ // Rich visibility condition (json-render style) takes priority
342
+ if (field.visible !== undefined) {
343
+ return evaluateVisibility(field.visible, values);
344
+ }
345
+
346
+ // Legacy showIf fallback
347
+ return evaluateShowIf(field.showIf, values);
348
+ },
349
+ [values],
350
+ );
351
+
352
+ // ── Field change handler ─────────────────────────────────────────────
353
+
354
+ const handleFieldChange = useCallback(
355
+ (field: ResolvedField, value: unknown): void => {
356
+ // Validate and store errors
357
+ const errors = validateField(field, value);
358
+ setFieldErrors((prev) => {
359
+ const next = new Map(prev);
360
+ if (errors.length > 0) {
361
+ next.set(field.key, errors);
362
+ } else {
363
+ next.delete(field.key);
364
+ }
365
+ return next;
366
+ });
367
+
368
+ onChange?.(field.key, value);
369
+ },
370
+ [validateField, onChange],
371
+ );
372
+
373
+ // ── Action execution ─────────────────────────────────────────────────
374
+
375
+ const executeAction = useCallback(
376
+ async (
377
+ action: string,
378
+ params?: Record<string, unknown>,
379
+ ): Promise<unknown> => {
380
+ const handler = registry.resolveAction(action);
381
+ if (!handler) {
382
+ console.warn(`[config-renderer] No handler for action: ${action}`);
383
+ return undefined;
384
+ }
385
+ return handler(params ?? {}, values);
386
+ },
387
+ [registry, values],
388
+ );
389
+
390
+ // ── Build render props for a field ───────────────────────────────────
391
+
392
+ const buildRenderProps = useCallback(
393
+ (field: ResolvedField): FieldRenderProps => {
394
+ const isSensitive = field.hint.sensitive === true;
395
+ return {
396
+ key: field.key,
397
+ value: values[field.key],
398
+ schema: field.schema,
399
+ hint: field.hint,
400
+ fieldType: field.fieldType,
401
+ onChange: (value: unknown) => handleFieldChange(field, value),
402
+ isSet: setKeys.has(field.key),
403
+ required: field.required,
404
+ errors: fieldErrors.get(field.key),
405
+ readonly: field.readonly,
406
+ onReveal:
407
+ isSensitive && revealSecret && pluginId
408
+ ? () => revealSecret(pluginId, field.key)
409
+ : undefined,
410
+ onAction: (action: string, params?: Record<string, unknown>) =>
411
+ executeAction(action, params),
412
+ };
413
+ },
414
+ [
415
+ values,
416
+ setKeys,
417
+ fieldErrors,
418
+ handleFieldChange,
419
+ revealSecret,
420
+ pluginId,
421
+ executeAction,
422
+ ],
423
+ );
424
+
425
+ // ── Render a single field ────────────────────────────────────────────
426
+
427
+ const renderField = useCallback(
428
+ (field: ResolvedField) => {
429
+ const rp = buildRenderProps(field);
430
+ const renderer = registry.resolveOrFallback(field.fieldType);
431
+
432
+ if (renderFieldOverride) {
433
+ return (
434
+ <div key={field.key} className={widthClass(field.width)}>
435
+ {renderFieldOverride(rp, renderer)}
436
+ </div>
437
+ );
438
+ }
439
+
440
+ return (
441
+ <div key={field.key} className={widthClass(field.width)}>
442
+ <ConfigField
443
+ renderProps={rp}
444
+ renderer={renderer}
445
+ pluginId={pluginId}
446
+ />
447
+ </div>
448
+ );
449
+ },
450
+ [buildRenderProps, registry, renderFieldOverride, pluginId],
451
+ );
452
+
453
+ // ── Resolve and partition fields ─────────────────────────────────────
454
+
455
+ const { groups, advanced, showHeaders, allVisibleFields } = useMemo(() => {
456
+ if (!schema)
457
+ return {
458
+ groups: new Map<string, ResolvedField[]>(),
459
+ advanced: [] as ResolvedField[],
460
+ showHeaders: false,
461
+ allVisibleFields: [] as ResolvedField[],
462
+ };
463
+
464
+ const catalog = registry.catalog;
465
+ const allFields = resolveFields(schema, hints, catalog);
466
+
467
+ // Filter: hidden fields, showIf + rich visibility conditions
468
+ const visibleFields = allFields.filter(isFieldVisible);
469
+
470
+ const generalFields = visibleFields.filter((f) => !f.advanced);
471
+ const advancedFields = visibleFields.filter((f) => f.advanced);
472
+
473
+ // Group general fields, sort required-unconfigured to the top within each group
474
+ const fieldGroups = new Map<string, ResolvedField[]>();
475
+ for (const f of generalFields) {
476
+ const g = fieldGroups.get(f.group) ?? [];
477
+ g.push(f);
478
+ fieldGroups.set(f.group, g);
479
+ }
480
+ for (const [, fields] of fieldGroups) {
481
+ fields.sort((a, b) => {
482
+ const aEmpty =
483
+ a.required && (values[a.key] == null || values[a.key] === "");
484
+ const bEmpty =
485
+ b.required && (values[b.key] == null || values[b.key] === "");
486
+ if (aEmpty && !bEmpty) return -1;
487
+ if (!aEmpty && bEmpty) return 1;
488
+ return (a.hint.order ?? 999) - (b.hint.order ?? 999);
489
+ });
490
+ }
491
+
492
+ return {
493
+ groups: fieldGroups,
494
+ advanced: advancedFields,
495
+ showHeaders: fieldGroups.size > 1,
496
+ allVisibleFields: visibleFields,
497
+ };
498
+ }, [schema, hints, registry, isFieldVisible, values]);
499
+
500
+ // ── Field labels for validation summary ────────────────────────────
501
+
502
+ const fieldLabels = useMemo(() => {
503
+ const labels = new Map<string, string>();
504
+ for (const field of allVisibleFields) {
505
+ labels.set(field.key, field.hint.label ?? field.key);
506
+ }
507
+ return labels;
508
+ }, [allVisibleFields]);
509
+
510
+ // ── Validate all visible fields ────────────────────────────────────
511
+
512
+ const validateAll = useCallback((): boolean => {
513
+ const nextErrors = new Map<string, string[]>();
514
+ for (const field of allVisibleFields) {
515
+ const errors = validateField(field, values[field.key]);
516
+ if (errors.length > 0) {
517
+ nextErrors.set(field.key, errors);
518
+ }
519
+ }
520
+ setFieldErrors(nextErrors);
521
+ return nextErrors.size === 0;
522
+ }, [allVisibleFields, validateField, values]);
523
+
524
+ // ── Expose validateAll to parent via ref ───────────────────────────
525
+
526
+ useImperativeHandle(ref, () => ({ validateAll }), [validateAll]);
527
+
528
+ // ── Configuration progress ─────────────────────────────────────────
529
+
530
+ const configProgress = useMemo(() => {
531
+ const total = allVisibleFields.length;
532
+ if (total === 0) return null;
533
+ const isConfigured = (f: ResolvedField) => {
534
+ if (setKeys.has(f.key)) return true;
535
+ const v = values[f.key];
536
+ return v != null && v !== "";
537
+ };
538
+ const configured = allVisibleFields.filter(isConfigured).length;
539
+ const requiredTotal = allVisibleFields.filter((f) => f.required).length;
540
+ const requiredSet = allVisibleFields.filter(
541
+ (f) => f.required && isConfigured(f),
542
+ ).length;
543
+ return { total, configured, requiredTotal, requiredSet };
544
+ }, [allVisibleFields, values, setKeys]);
545
+
546
+ // ── Theme style ────────────────────────────────────────────────────
547
+
548
+ const themeStyle = useMemo(() => {
549
+ if (!theme) return undefined;
550
+ const style: Record<string, string> = {};
551
+ for (const [key, value] of Object.entries(theme)) {
552
+ const cssVar = THEME_TO_CSS[key as keyof typeof THEME_TO_CSS];
553
+ if (cssVar && value) {
554
+ style[cssVar] = value as string;
555
+ }
556
+ }
557
+ return Object.keys(style).length > 0 ? style : undefined;
558
+ }, [theme]);
559
+
560
+ // ── useApp for i18n ─────────────────────────────────────────────────
561
+ const { t: tFn } = useApp();
562
+
563
+ // ── Empty state ──────────────────────────────────────────────────────
564
+
565
+ if (!schema) {
566
+ return (
567
+ <div className="text-xs text-[var(--muted)] italic py-3">
568
+ {tFn("config-renderer.NoSchemaProvided")}
569
+ </div>
570
+ );
571
+ }
572
+
573
+ // ── Render ───────────────────────────────────────────────────────────
574
+
575
+ return (
576
+ <div style={themeStyle}>
577
+ {/* Progress indicator */}
578
+ {configProgress &&
579
+ configProgress.requiredTotal > 0 &&
580
+ configProgress.requiredSet < configProgress.requiredTotal && (
581
+ <div className="mb-4 px-3.5 py-2.5 border border-[var(--warning,#f39c12)] bg-[color-mix(in_srgb,var(--warning,#f39c12)_6%,transparent)] rounded-sm">
582
+ <ConfigProgressText configProgress={configProgress} />
583
+ <div className="w-full h-1.5 bg-[var(--border)] rounded-full overflow-hidden">
584
+ <div
585
+ className="h-full bg-[var(--warning,#f39c12)] rounded-full transition-all duration-300"
586
+ style={{
587
+ width: `${(configProgress.requiredSet / configProgress.requiredTotal) * 100}%`,
588
+ }}
589
+ />
590
+ </div>
591
+ </div>
592
+ )}
593
+
594
+ {showValidationSummary && fieldErrors.size > 0 ? (
595
+ <ValidationSummary
596
+ fieldErrors={fieldErrors}
597
+ fieldLabels={fieldLabels}
598
+ pluginId={pluginId}
599
+ />
600
+ ) : null}
601
+
602
+ {[...groups.entries()].map(([group, fields], groupIndex) => (
603
+ <div key={group} className={groupIndex > 0 ? "mt-5" : ""}>
604
+ {showHeaders && (
605
+ <div className="flex items-center gap-2 mb-3">
606
+ <span className="text-base leading-none">{groupIcon(group)}</span>
607
+ <span className="text-[12px] font-bold uppercase tracking-wider text-[var(--text)] opacity-70">
608
+ {group}
609
+ </span>
610
+ <span className="flex-1 h-px bg-[var(--border)] ml-1" />
611
+ </div>
612
+ )}
613
+ <div className="grid grid-cols-6 gap-x-5 gap-y-0">
614
+ {fields.map((f) => renderField(f))}
615
+ </div>
616
+ </div>
617
+ ))}
618
+
619
+ {advanced.length > 0 && (
620
+ <div className="mt-5 pt-4 border-t border-[var(--border)]">
621
+ <AdvancedSectionToggle
622
+ advanced={advanced}
623
+ advancedOpen={advancedOpen}
624
+ setAdvancedOpen={setAdvancedOpen}
625
+ />
626
+ {advancedOpen && (
627
+ <div className="grid grid-cols-6 gap-x-5 gap-y-0 pt-1 animate-[cr-slide_var(--duration-normal,200ms)_ease]">
628
+ {advanced.map((f) => renderField(f))}
629
+ </div>
630
+ )}
631
+ </div>
632
+ )}
633
+ </div>
634
+ );
635
+ });
636
+
637
+ function ConfigProgressText({
638
+ configProgress,
639
+ }: {
640
+ configProgress: {
641
+ requiredSet: number;
642
+ requiredTotal: number;
643
+ configured: number;
644
+ total: number;
645
+ };
646
+ }) {
647
+ const { t } = useApp();
648
+ return (
649
+ <div className="flex items-center justify-between mb-1.5">
650
+ <span className="text-[12px] font-semibold text-[var(--warning,#f39c12)]">
651
+ {configProgress.requiredSet}/{configProgress.requiredTotal}{" "}
652
+ {t("config-renderer.requiredFieldsConf")}
653
+ </span>
654
+ <span className="text-[11px] text-[var(--muted)]">
655
+ {configProgress.configured}/{configProgress.total}{" "}
656
+ {t("config-renderer.total")}
657
+ </span>
658
+ </div>
659
+ );
660
+ }
661
+
662
+ function AdvancedSectionToggle({
663
+ advanced,
664
+ advancedOpen,
665
+ setAdvancedOpen,
666
+ }: {
667
+ advanced: ResolvedField[];
668
+ advancedOpen: boolean;
669
+ setAdvancedOpen: React.Dispatch<React.SetStateAction<boolean>>;
670
+ }) {
671
+ const { t } = useApp();
672
+ return (
673
+ <button
674
+ type="button"
675
+ className="flex items-center gap-2 cursor-pointer select-none group mb-3"
676
+ onClick={() => setAdvancedOpen((prev) => !prev)}
677
+ >
678
+ <span
679
+ className="inline-block text-[10px] text-[var(--muted)] transition-transform duration-200 group-hover:text-[var(--text)]"
680
+ style={{ transform: advancedOpen ? "rotate(90deg)" : "none" }}
681
+ >
682
+ &#9654;
683
+ </span>
684
+ <span className="text-[12px] font-bold uppercase tracking-wider text-[var(--muted)] group-hover:text-[var(--text)] transition-colors">
685
+ {t("config-renderer.Advanced")}
686
+ </span>
687
+ <span className="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1.5 text-[10px] font-bold bg-[var(--accent-subtle,rgba(255,255,255,0.05))] text-[var(--accent)] border border-[var(--border)] rounded-sm">
688
+ {advanced.length}
689
+ </span>
690
+ <span className="flex-1 h-px bg-[var(--border)] opacity-50 ml-1" />
691
+ </button>
692
+ );
693
+ }
694
+
695
+ // ── Default registry ───────────────────────────────────────────────────
696
+
697
+ // Import actual field renderers
698
+ import { defaultRenderers } from "./config-field";
699
+
700
+ /** The default registry wiring defaultCatalog → defaultRenderers. */
701
+ export const defaultRegistry = defineRegistry(defaultCatalog, defaultRenderers);
702
+
703
+ // ── useConfigValidation hook ────────────────────────────────────────────
704
+
705
+ /**
706
+ * Convenience hook that creates a ref for ConfigRenderer and exposes
707
+ * a `validateAll()` function the parent can call before submitting.
708
+ *
709
+ * @example
710
+ * ```tsx
711
+ * const { configRef, validateAll } = useConfigValidation();
712
+ *
713
+ * const handleSave = () => {
714
+ * if (!validateAll()) return; // form has errors
715
+ * // proceed with save
716
+ * };
717
+ *
718
+ * return <ConfigRenderer ref={configRef} ... />;
719
+ * ```
720
+ */
721
+ export function useConfigValidation() {
722
+ const configRef = React.useRef<ConfigRendererHandle>(null);
723
+
724
+ const validateAll = useCallback((): boolean => {
725
+ if (!configRef.current) return true;
726
+ return configRef.current.validateAll();
727
+ }, []);
728
+
729
+ return { configRef, validateAll };
730
+ }