@wopr-network/platform-ui-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (543) hide show
  1. package/.env.paperclip +18 -0
  2. package/.env.wopr +18 -0
  3. package/README.md +36 -0
  4. package/biome.json +52 -0
  5. package/next.config.ts +45 -0
  6. package/package.json +84 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/file.svg +1 -0
  9. package/public/globe.svg +1 -0
  10. package/public/window.svg +1 -0
  11. package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
  12. package/src/__tests__/account-page-redirect.test.tsx +73 -0
  13. package/src/__tests__/account-switcher.test.tsx +85 -0
  14. package/src/__tests__/activity-page.test.tsx +176 -0
  15. package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
  16. package/src/__tests__/admin-api.test.ts +244 -0
  17. package/src/__tests__/admin-gpu-api.test.ts +188 -0
  18. package/src/__tests__/admin-guard.test.tsx +79 -0
  19. package/src/__tests__/admin-marketplace-api.test.ts +179 -0
  20. package/src/__tests__/admin-middleware.test.ts +157 -0
  21. package/src/__tests__/admin-tenant-table.test.tsx +95 -0
  22. package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
  23. package/src/__tests__/api-401-redirect.test.ts +78 -0
  24. package/src/__tests__/api-client.test.ts +316 -0
  25. package/src/__tests__/api-config.test.ts +89 -0
  26. package/src/__tests__/api-control-instance.test.ts +69 -0
  27. package/src/__tests__/api-fleet-resources.test.ts +52 -0
  28. package/src/__tests__/api-fleet-trpc.test.ts +252 -0
  29. package/src/__tests__/api-get-instance-config.test.ts +41 -0
  30. package/src/__tests__/api-null-guards.test.ts +244 -0
  31. package/src/__tests__/api-rename-instance.test.ts +60 -0
  32. package/src/__tests__/api-update-instance-config.test.ts +60 -0
  33. package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
  34. package/src/__tests__/auth-client.test.ts +87 -0
  35. package/src/__tests__/auth-password-reset.test.tsx +435 -0
  36. package/src/__tests__/auth-redirect.test.tsx +60 -0
  37. package/src/__tests__/auth.test.tsx +269 -0
  38. package/src/__tests__/auto-topup-card.test.tsx +257 -0
  39. package/src/__tests__/backups-tab.test.tsx +221 -0
  40. package/src/__tests__/billing-byok-callout.test.tsx +76 -0
  41. package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
  42. package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
  43. package/src/__tests__/billing.test.tsx +509 -0
  44. package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
  45. package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
  46. package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
  47. package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
  48. package/src/__tests__/bot-settings-data-control.test.ts +49 -0
  49. package/src/__tests__/bot-settings-restart.test.tsx +149 -0
  50. package/src/__tests__/bot-settings.test.tsx +678 -0
  51. package/src/__tests__/brand.test.ts +335 -0
  52. package/src/__tests__/buy-credits-panel.test.tsx +249 -0
  53. package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
  54. package/src/__tests__/capability-conflicts.test.ts +88 -0
  55. package/src/__tests__/capability-resolver.test.tsx +173 -0
  56. package/src/__tests__/changeset-detail.test.tsx +156 -0
  57. package/src/__tests__/channel-setup-logger.test.ts +22 -0
  58. package/src/__tests__/channel-setup-toast.test.tsx +60 -0
  59. package/src/__tests__/channel-wizard.test.tsx +505 -0
  60. package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
  61. package/src/__tests__/chat/chat-input.test.tsx +78 -0
  62. package/src/__tests__/chat/chat-message.test.tsx +45 -0
  63. package/src/__tests__/chat/chat-panel.test.tsx +111 -0
  64. package/src/__tests__/chat/chat-widget.test.tsx +82 -0
  65. package/src/__tests__/chat-store.test.ts +87 -0
  66. package/src/__tests__/command-center.test.tsx +246 -0
  67. package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
  68. package/src/__tests__/coupon-input.test.tsx +119 -0
  69. package/src/__tests__/create-instance.test.tsx +96 -0
  70. package/src/__tests__/create-org-wizard.test.tsx +200 -0
  71. package/src/__tests__/credit-balance.test.tsx +103 -0
  72. package/src/__tests__/credits.test.tsx +376 -0
  73. package/src/__tests__/csrf-middleware.test.ts +198 -0
  74. package/src/__tests__/degraded-state-banner.test.tsx +130 -0
  75. package/src/__tests__/dividend-calculator.test.tsx +20 -0
  76. package/src/__tests__/dividend-stats.test.tsx +64 -0
  77. package/src/__tests__/dividend.test.tsx +169 -0
  78. package/src/__tests__/dockerfile.test.ts +110 -0
  79. package/src/__tests__/email-verification-banner.test.tsx +64 -0
  80. package/src/__tests__/env-example.test.ts +25 -0
  81. package/src/__tests__/error-boundaries.test.tsx +64 -0
  82. package/src/__tests__/fetch-pricing.test.ts +121 -0
  83. package/src/__tests__/field-oauth.test.tsx +302 -0
  84. package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
  85. package/src/__tests__/fixtures/mock-manifests.ts +24 -0
  86. package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
  87. package/src/__tests__/fleet-health-update.test.tsx +83 -0
  88. package/src/__tests__/format-credit.test.ts +58 -0
  89. package/src/__tests__/gpu-dashboard.test.tsx +236 -0
  90. package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
  91. package/src/__tests__/instance-detail.test.tsx +571 -0
  92. package/src/__tests__/instance-list.test.tsx +230 -0
  93. package/src/__tests__/landing-hero.test.tsx +27 -0
  94. package/src/__tests__/landing-nav.test.tsx +24 -0
  95. package/src/__tests__/layout-snapshots.test.tsx +167 -0
  96. package/src/__tests__/logger.test.ts +54 -0
  97. package/src/__tests__/login-page-redirect.test.tsx +142 -0
  98. package/src/__tests__/manifest-validation.test.ts +126 -0
  99. package/src/__tests__/marketplace-admin.test.tsx +151 -0
  100. package/src/__tests__/marketplace.test.tsx +609 -0
  101. package/src/__tests__/merge-api-rates.test.ts +70 -0
  102. package/src/__tests__/middleware.test.ts +690 -0
  103. package/src/__tests__/network-page.test.tsx +100 -0
  104. package/src/__tests__/next-config-headers.test.ts +28 -0
  105. package/src/__tests__/not-found.test.tsx +26 -0
  106. package/src/__tests__/notifications.test.tsx +128 -0
  107. package/src/__tests__/oauth-buttons.test.tsx +101 -0
  108. package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
  109. package/src/__tests__/observability.test.tsx +541 -0
  110. package/src/__tests__/onboarding-data.test.ts +363 -0
  111. package/src/__tests__/onboarding-page.test.tsx +113 -0
  112. package/src/__tests__/onboarding-store.test.ts +121 -0
  113. package/src/__tests__/org-billing-api.test.tsx +70 -0
  114. package/src/__tests__/org-billing-null-guards.test.ts +64 -0
  115. package/src/__tests__/org-billing-page.test.tsx +124 -0
  116. package/src/__tests__/plugin-definition.test.ts +43 -0
  117. package/src/__tests__/plugin-install-flow.test.tsx +535 -0
  118. package/src/__tests__/plugin-registry.test.tsx +475 -0
  119. package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
  120. package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
  121. package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
  122. package/src/__tests__/plugin-tool-sync.test.ts +59 -0
  123. package/src/__tests__/portfolio-chart.test.tsx +24 -0
  124. package/src/__tests__/pricing.test.tsx +107 -0
  125. package/src/__tests__/promotion-form.test.tsx +180 -0
  126. package/src/__tests__/promotions-list.test.tsx +194 -0
  127. package/src/__tests__/provider-key-api.test.ts +134 -0
  128. package/src/__tests__/resend-verification-button.test.tsx +104 -0
  129. package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
  130. package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
  131. package/src/__tests__/settings.test.tsx +937 -0
  132. package/src/__tests__/setup-checklist.test.tsx +274 -0
  133. package/src/__tests__/setup.ts +82 -0
  134. package/src/__tests__/smoke.test.tsx +10 -0
  135. package/src/__tests__/snapshot-api.test.ts +104 -0
  136. package/src/__tests__/status-api.test.ts +46 -0
  137. package/src/__tests__/status-badge.test.tsx +33 -0
  138. package/src/__tests__/status-colors.test.ts +83 -0
  139. package/src/__tests__/status-page.test.tsx +86 -0
  140. package/src/__tests__/step-superpowers.test.tsx +218 -0
  141. package/src/__tests__/story-sections.test.tsx +24 -0
  142. package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
  143. package/src/__tests__/superpower-content.test.tsx +44 -0
  144. package/src/__tests__/suspension-banner.test.tsx +140 -0
  145. package/src/__tests__/tenant-context.test.tsx +146 -0
  146. package/src/__tests__/tenant-keys-api.test.ts +114 -0
  147. package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
  148. package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
  149. package/src/__tests__/terminal-sequence.test.tsx +28 -0
  150. package/src/__tests__/transaction-history.test.tsx +325 -0
  151. package/src/__tests__/trpc-types.test.ts +102 -0
  152. package/src/__tests__/use-capability-meta.test.ts +161 -0
  153. package/src/__tests__/use-chat.test.ts +616 -0
  154. package/src/__tests__/use-has-org.test.ts +44 -0
  155. package/src/__tests__/use-image-status.test.ts +77 -0
  156. package/src/__tests__/use-pagination-params.test.ts +88 -0
  157. package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
  158. package/src/__tests__/use-webmcp.test.ts +119 -0
  159. package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
  160. package/src/__tests__/validate-redirect-url.test.ts +61 -0
  161. package/src/__tests__/verify-page.test.tsx +140 -0
  162. package/src/__tests__/verify-redirect.test.tsx +41 -0
  163. package/src/__tests__/verify-result-banner.test.tsx +66 -0
  164. package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
  165. package/src/__tests__/webmcp-hook.test.tsx +72 -0
  166. package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
  167. package/src/__tests__/webmcp-register.test.ts +103 -0
  168. package/src/__tests__/webmcp-set-provider.test.ts +47 -0
  169. package/src/__tests__/webmcp-tools.test.ts +348 -0
  170. package/src/app/(auth)/error.tsx +72 -0
  171. package/src/app/(auth)/forgot-password/page.tsx +137 -0
  172. package/src/app/(auth)/layout.tsx +14 -0
  173. package/src/app/(auth)/loading.tsx +26 -0
  174. package/src/app/(auth)/login/page.tsx +188 -0
  175. package/src/app/(auth)/reset-password/page.tsx +169 -0
  176. package/src/app/(auth)/signup/page.tsx +309 -0
  177. package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
  178. package/src/app/(dashboard)/billing/error.tsx +72 -0
  179. package/src/app/(dashboard)/billing/layout.tsx +73 -0
  180. package/src/app/(dashboard)/billing/loading.tsx +41 -0
  181. package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
  182. package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
  183. package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
  184. package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
  185. package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
  186. package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
  187. package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
  188. package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
  189. package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
  190. package/src/app/(dashboard)/changesets/error.tsx +72 -0
  191. package/src/app/(dashboard)/changesets/page.tsx +10 -0
  192. package/src/app/(dashboard)/chat/page.tsx +74 -0
  193. package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
  194. package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
  195. package/src/app/(dashboard)/dashboard/page.tsx +13 -0
  196. package/src/app/(dashboard)/error.tsx +72 -0
  197. package/src/app/(dashboard)/layout.tsx +113 -0
  198. package/src/app/(dashboard)/loading.tsx +27 -0
  199. package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
  200. package/src/app/(dashboard)/marketplace/error.tsx +72 -0
  201. package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
  202. package/src/app/(dashboard)/marketplace/page.tsx +268 -0
  203. package/src/app/(dashboard)/not-found.tsx +46 -0
  204. package/src/app/(dashboard)/onboarding/page.tsx +267 -0
  205. package/src/app/(dashboard)/settings/account/page.tsx +132 -0
  206. package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
  207. package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
  208. package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
  209. package/src/app/(dashboard)/settings/error.tsx +72 -0
  210. package/src/app/(dashboard)/settings/layout.tsx +114 -0
  211. package/src/app/(dashboard)/settings/loading.tsx +31 -0
  212. package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
  213. package/src/app/(dashboard)/settings/org/page.tsx +617 -0
  214. package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
  215. package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
  216. package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
  217. package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
  218. package/src/app/admin/accounting/loading.tsx +32 -0
  219. package/src/app/admin/accounting/page.tsx +5 -0
  220. package/src/app/admin/affiliates/loading.tsx +32 -0
  221. package/src/app/admin/affiliates/page.tsx +5 -0
  222. package/src/app/admin/audit/loading.tsx +32 -0
  223. package/src/app/admin/audit/page.tsx +5 -0
  224. package/src/app/admin/billing-health/loading.tsx +17 -0
  225. package/src/app/admin/billing-health/page.tsx +10 -0
  226. package/src/app/admin/compliance/page.tsx +5 -0
  227. package/src/app/admin/error.tsx +72 -0
  228. package/src/app/admin/gpu/loading.tsx +38 -0
  229. package/src/app/admin/gpu/page.tsx +5 -0
  230. package/src/app/admin/incidents/page.tsx +10 -0
  231. package/src/app/admin/inference/loading.tsx +32 -0
  232. package/src/app/admin/inference/page.tsx +5 -0
  233. package/src/app/admin/layout.tsx +44 -0
  234. package/src/app/admin/loading.tsx +32 -0
  235. package/src/app/admin/marketplace/loading.tsx +32 -0
  236. package/src/app/admin/marketplace/page.tsx +5 -0
  237. package/src/app/admin/migrations/loading.tsx +22 -0
  238. package/src/app/admin/migrations/page.tsx +5 -0
  239. package/src/app/admin/onboarding/loading.tsx +18 -0
  240. package/src/app/admin/onboarding/page.tsx +5 -0
  241. package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
  242. package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
  243. package/src/app/admin/promotions/[id]/loading.tsx +15 -0
  244. package/src/app/admin/promotions/[id]/page.tsx +311 -0
  245. package/src/app/admin/promotions/loading.tsx +21 -0
  246. package/src/app/admin/promotions/new/loading.tsx +16 -0
  247. package/src/app/admin/promotions/new/page.tsx +12 -0
  248. package/src/app/admin/promotions/page.tsx +266 -0
  249. package/src/app/admin/rate-overrides/loading.tsx +17 -0
  250. package/src/app/admin/rate-overrides/page.tsx +290 -0
  251. package/src/app/admin/roles/loading.tsx +27 -0
  252. package/src/app/admin/roles/page.tsx +5 -0
  253. package/src/app/admin/tenants/loading.tsx +32 -0
  254. package/src/app/admin/tenants/page.tsx +5 -0
  255. package/src/app/apple-icon.tsx +32 -0
  256. package/src/app/auth/callback/[provider]/page.tsx +104 -0
  257. package/src/app/auth/verify/page.tsx +224 -0
  258. package/src/app/channels/error.tsx +72 -0
  259. package/src/app/channels/loading.tsx +29 -0
  260. package/src/app/channels/page.tsx +262 -0
  261. package/src/app/channels/setup/[plugin]/page.tsx +136 -0
  262. package/src/app/error.tsx +72 -0
  263. package/src/app/favicon.ico +0 -0
  264. package/src/app/fleet/error.tsx +72 -0
  265. package/src/app/fleet/health/page.tsx +9 -0
  266. package/src/app/fleet/layout.tsx +14 -0
  267. package/src/app/fleet/loading.tsx +33 -0
  268. package/src/app/fleet/page.tsx +5 -0
  269. package/src/app/global-error.tsx +96 -0
  270. package/src/app/globals.css +251 -0
  271. package/src/app/icon.svg +4 -0
  272. package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
  273. package/src/app/instances/[id]/page.tsx +10 -0
  274. package/src/app/instances/error.tsx +72 -0
  275. package/src/app/instances/instance-list-client.tsx +540 -0
  276. package/src/app/instances/loading.tsx +33 -0
  277. package/src/app/instances/new/create-instance-client.tsx +377 -0
  278. package/src/app/instances/new/page.tsx +9 -0
  279. package/src/app/instances/page.tsx +9 -0
  280. package/src/app/layout.tsx +83 -0
  281. package/src/app/not-found.tsx +38 -0
  282. package/src/app/og/route.tsx +50 -0
  283. package/src/app/page.tsx +39 -0
  284. package/src/app/plugins/error.tsx +72 -0
  285. package/src/app/plugins/layout.tsx +14 -0
  286. package/src/app/plugins/loading.tsx +30 -0
  287. package/src/app/plugins/page.tsx +555 -0
  288. package/src/app/pricing/error.tsx +72 -0
  289. package/src/app/pricing/loading.tsx +25 -0
  290. package/src/app/pricing/page.tsx +20 -0
  291. package/src/app/privacy/page.tsx +406 -0
  292. package/src/app/robots.ts +9 -0
  293. package/src/app/sitemap.ts +11 -0
  294. package/src/app/status/error.tsx +72 -0
  295. package/src/app/status/loading.tsx +21 -0
  296. package/src/app/status/page.tsx +20 -0
  297. package/src/app/terms/page.tsx +414 -0
  298. package/src/components/account-switcher.tsx +82 -0
  299. package/src/components/admin/accounting-dashboard.tsx +190 -0
  300. package/src/components/admin/admin-guard.tsx +36 -0
  301. package/src/components/admin/admin-nav.tsx +71 -0
  302. package/src/components/admin/affiliate-dashboard.tsx +564 -0
  303. package/src/components/admin/audit-log-table.tsx +336 -0
  304. package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
  305. package/src/components/admin/billing-health-dashboard.tsx +416 -0
  306. package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
  307. package/src/components/admin/bulk-actions-bar.tsx +80 -0
  308. package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
  309. package/src/components/admin/bulk-export-dialog.tsx +189 -0
  310. package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
  311. package/src/components/admin/bulk-grant-dialog.tsx +147 -0
  312. package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
  313. package/src/components/admin/bulk-preview-dialog.tsx +106 -0
  314. package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
  315. package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
  316. package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
  317. package/src/components/admin/bulk-select-all-banner.tsx +44 -0
  318. package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
  319. package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
  320. package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
  321. package/src/components/admin/bulk-undo-toast.tsx +121 -0
  322. package/src/components/admin/compliance-dashboard.tsx +1341 -0
  323. package/src/components/admin/gpu-dashboard.tsx +552 -0
  324. package/src/components/admin/grant-credits-dialog.tsx +121 -0
  325. package/src/components/admin/incident-dashboard.test.tsx +44 -0
  326. package/src/components/admin/incident-dashboard.tsx +717 -0
  327. package/src/components/admin/inference-dashboard.tsx +415 -0
  328. package/src/components/admin/marketplace-admin.tsx +765 -0
  329. package/src/components/admin/migrations-dashboard.tsx +404 -0
  330. package/src/components/admin/onboarding-dashboard.tsx +422 -0
  331. package/src/components/admin/promotions/promotion-form.tsx +440 -0
  332. package/src/components/admin/roles-dashboard.tsx +278 -0
  333. package/src/components/admin/suspend-dialog.tsx +98 -0
  334. package/src/components/admin/tenant-notes-panel.tsx +134 -0
  335. package/src/components/admin/tenant-row-actions.tsx +78 -0
  336. package/src/components/admin/tenant-table.tsx +339 -0
  337. package/src/components/auth/auth-error.tsx +22 -0
  338. package/src/components/auth/auth-redirect.tsx +18 -0
  339. package/src/components/auth/auth-shell.tsx +25 -0
  340. package/src/components/auth/email-verification-banner.tsx +25 -0
  341. package/src/components/auth/email-verification-result-banner.tsx +70 -0
  342. package/src/components/auth/resend-verification-button.tsx +94 -0
  343. package/src/components/auth/wopr-wordmark.tsx +19 -0
  344. package/src/components/billing/add-payment-method-dialog.tsx +267 -0
  345. package/src/components/billing/affiliate-dashboard.tsx +300 -0
  346. package/src/components/billing/auto-topup-card.tsx +432 -0
  347. package/src/components/billing/buy-credits-panel.tsx +180 -0
  348. package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
  349. package/src/components/billing/byok-callout.tsx +87 -0
  350. package/src/components/billing/coupon-input.tsx +86 -0
  351. package/src/components/billing/credit-balance.tsx +79 -0
  352. package/src/components/billing/degraded-state-banner.tsx +95 -0
  353. package/src/components/billing/dividend-banner.tsx +97 -0
  354. package/src/components/billing/dividend-eligibility.tsx +86 -0
  355. package/src/components/billing/dividend-pool-stats.tsx +86 -0
  356. package/src/components/billing/first-dividend-dialog.tsx +109 -0
  357. package/src/components/billing/low-balance-banner.tsx +50 -0
  358. package/src/components/billing/org-billing-page.tsx +360 -0
  359. package/src/components/billing/suspension-banner.tsx +53 -0
  360. package/src/components/billing/transaction-history.tsx +239 -0
  361. package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
  362. package/src/components/bot-settings/backups-tab.tsx +377 -0
  363. package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
  364. package/src/components/bot-settings/resources-tab.tsx +203 -0
  365. package/src/components/bot-settings/storage-tab.tsx +248 -0
  366. package/src/components/bot-settings/vps-info-panel.tsx +132 -0
  367. package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
  368. package/src/components/capability/CapabilityResolver.tsx +113 -0
  369. package/src/components/channel-wizard/field-interactive.tsx +48 -0
  370. package/src/components/channel-wizard/field-oauth.tsx +181 -0
  371. package/src/components/channel-wizard/field-paste.tsx +47 -0
  372. package/src/components/channel-wizard/field-qr.tsx +302 -0
  373. package/src/components/channel-wizard/index.ts +6 -0
  374. package/src/components/channel-wizard/step-renderer.tsx +103 -0
  375. package/src/components/channel-wizard/wizard.tsx +200 -0
  376. package/src/components/chat/ambient-dot.tsx +32 -0
  377. package/src/components/chat/chat-input.tsx +56 -0
  378. package/src/components/chat/chat-message.tsx +36 -0
  379. package/src/components/chat/chat-panel.tsx +138 -0
  380. package/src/components/chat/chat-widget.tsx +41 -0
  381. package/src/components/chat/index.ts +5 -0
  382. package/src/components/dashboard/command-center.tsx +614 -0
  383. package/src/components/instances/friends-tab.test.tsx +265 -0
  384. package/src/components/instances/friends-tab.tsx +721 -0
  385. package/src/components/landing/hero.tsx +53 -0
  386. package/src/components/landing/landing-nav.tsx +21 -0
  387. package/src/components/landing/landing-page.tsx +71 -0
  388. package/src/components/landing/portfolio-chart.tsx +349 -0
  389. package/src/components/landing/story-sections.tsx +50 -0
  390. package/src/components/landing/terminal-lines.ts +99 -0
  391. package/src/components/landing/terminal-sequence.tsx +453 -0
  392. package/src/components/landing/typing-effect.tsx +43 -0
  393. package/src/components/marketplace/category-filter.tsx +61 -0
  394. package/src/components/marketplace/empty-state.tsx +61 -0
  395. package/src/components/marketplace/featured-heroes.tsx +84 -0
  396. package/src/components/marketplace/first-visit-hero.tsx +110 -0
  397. package/src/components/marketplace/index.ts +9 -0
  398. package/src/components/marketplace/install-wizard.tsx +782 -0
  399. package/src/components/marketplace/marketplace-tabs.tsx +54 -0
  400. package/src/components/marketplace/plugin-card.tsx +129 -0
  401. package/src/components/marketplace/superpower-card.tsx +104 -0
  402. package/src/components/marketplace/superpower-content.tsx +117 -0
  403. package/src/components/marketplace/terminal-search.tsx +67 -0
  404. package/src/components/oauth-buttons.tsx +75 -0
  405. package/src/components/observability/fleet-health.tsx +370 -0
  406. package/src/components/observability/health-overview.tsx +246 -0
  407. package/src/components/observability/logs-viewer.tsx +215 -0
  408. package/src/components/observability/metrics-dashboard.tsx +288 -0
  409. package/src/components/onboarding/fallback-setup.tsx +137 -0
  410. package/src/components/onboarding/index.ts +3 -0
  411. package/src/components/onboarding/setup-checklist.tsx +333 -0
  412. package/src/components/onboarding/step-superpowers.tsx +122 -0
  413. package/src/components/plugin-setup/index.ts +1 -0
  414. package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
  415. package/src/components/pricing/dividend-calculator.tsx +47 -0
  416. package/src/components/pricing/dividend-stats.tsx +117 -0
  417. package/src/components/pricing/pricing-page.tsx +229 -0
  418. package/src/components/settings/create-org-wizard.tsx +225 -0
  419. package/src/components/sidebar.tsx +202 -0
  420. package/src/components/status/status-page.tsx +209 -0
  421. package/src/components/status-badge.tsx +28 -0
  422. package/src/components/theme-provider.tsx +8 -0
  423. package/src/components/ui/alert-dialog.tsx +141 -0
  424. package/src/components/ui/badge.tsx +47 -0
  425. package/src/components/ui/banner.tsx +36 -0
  426. package/src/components/ui/button.tsx +64 -0
  427. package/src/components/ui/card.tsx +75 -0
  428. package/src/components/ui/checkbox.tsx +52 -0
  429. package/src/components/ui/collapsible.tsx +31 -0
  430. package/src/components/ui/credit-detailed.tsx +33 -0
  431. package/src/components/ui/dialog.tsx +143 -0
  432. package/src/components/ui/dropdown-menu.tsx +228 -0
  433. package/src/components/ui/form.tsx +151 -0
  434. package/src/components/ui/input.tsx +21 -0
  435. package/src/components/ui/label.tsx +21 -0
  436. package/src/components/ui/popover.tsx +74 -0
  437. package/src/components/ui/progress.tsx +28 -0
  438. package/src/components/ui/radio-group.tsx +45 -0
  439. package/src/components/ui/select.tsx +175 -0
  440. package/src/components/ui/separator.tsx +28 -0
  441. package/src/components/ui/sheet.tsx +125 -0
  442. package/src/components/ui/skeleton.tsx +15 -0
  443. package/src/components/ui/switch.tsx +35 -0
  444. package/src/components/ui/table.tsx +92 -0
  445. package/src/components/ui/tabs.tsx +81 -0
  446. package/src/components/ui/textarea.tsx +18 -0
  447. package/src/components/ui/tooltip.tsx +44 -0
  448. package/src/config/provider-docs.ts +17 -0
  449. package/src/hooks/__tests__/use-async.test.ts +127 -0
  450. package/src/hooks/__tests__/use-count-up.test.ts +129 -0
  451. package/src/hooks/__tests__/use-debounce.test.ts +105 -0
  452. package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
  453. package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
  454. package/src/hooks/__tests__/use-mobile.test.ts +86 -0
  455. package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
  456. package/src/hooks/use-async.ts +54 -0
  457. package/src/hooks/use-capability-meta.ts +99 -0
  458. package/src/hooks/use-count-up.ts +23 -0
  459. package/src/hooks/use-debounce.ts +12 -0
  460. package/src/hooks/use-fleet-sse.ts +47 -0
  461. package/src/hooks/use-has-org.ts +18 -0
  462. package/src/hooks/use-image-status.ts +36 -0
  463. package/src/hooks/use-local-storage.ts +36 -0
  464. package/src/hooks/use-mobile.ts +17 -0
  465. package/src/hooks/use-page-context.ts +24 -0
  466. package/src/hooks/use-pagination-params.ts +30 -0
  467. package/src/hooks/use-plugin-registry.ts +247 -0
  468. package/src/hooks/use-plugin-setup-chat.ts +211 -0
  469. package/src/hooks/use-save-queue.ts +54 -0
  470. package/src/hooks/use-webmcp.ts +40 -0
  471. package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
  472. package/src/lib/__tests__/admin-api.test.ts +487 -0
  473. package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
  474. package/src/lib/__tests__/api-fetch.test.ts +196 -0
  475. package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
  476. package/src/lib/__tests__/org-api.test.ts +281 -0
  477. package/src/lib/__tests__/org-billing-api.test.ts +242 -0
  478. package/src/lib/__tests__/pricing-data.test.ts +32 -0
  479. package/src/lib/__tests__/settings-api.test.ts +272 -0
  480. package/src/lib/admin-affiliate-api.ts +51 -0
  481. package/src/lib/admin-api.ts +325 -0
  482. package/src/lib/admin-compliance-api.ts +127 -0
  483. package/src/lib/admin-gpu-api.ts +82 -0
  484. package/src/lib/admin-incident-api.ts +121 -0
  485. package/src/lib/admin-inference-api.ts +47 -0
  486. package/src/lib/admin-marketplace-api.ts +97 -0
  487. package/src/lib/api-config.test.ts +111 -0
  488. package/src/lib/api-config.ts +65 -0
  489. package/src/lib/api-errors.test.ts +43 -0
  490. package/src/lib/api.ts +2011 -0
  491. package/src/lib/auth-client.ts +11 -0
  492. package/src/lib/bot-settings-data.ts +342 -0
  493. package/src/lib/brand-config.ts +145 -0
  494. package/src/lib/brand.ts +669 -0
  495. package/src/lib/changeset-api.ts +29 -0
  496. package/src/lib/changeset-types.ts +56 -0
  497. package/src/lib/channel-manifests.ts +50 -0
  498. package/src/lib/chat/chat-context.tsx +70 -0
  499. package/src/lib/chat/chat-store.ts +62 -0
  500. package/src/lib/chat/types.ts +35 -0
  501. package/src/lib/chat/use-chat.ts +255 -0
  502. package/src/lib/cost-comparison-data.test.ts +95 -0
  503. package/src/lib/cost-comparison-data.ts +54 -0
  504. package/src/lib/errors.test.ts +64 -0
  505. package/src/lib/errors.ts +52 -0
  506. package/src/lib/fetch-utils.test.ts +57 -0
  507. package/src/lib/fetch-utils.ts +25 -0
  508. package/src/lib/format-credit.test.ts +66 -0
  509. package/src/lib/format-credit.ts +24 -0
  510. package/src/lib/format.test.ts +62 -0
  511. package/src/lib/format.ts +17 -0
  512. package/src/lib/logger.ts +28 -0
  513. package/src/lib/marketplace-data.ts +346 -0
  514. package/src/lib/oauth-errors.ts +19 -0
  515. package/src/lib/onboarding-data.ts +1265 -0
  516. package/src/lib/onboarding-store.ts +233 -0
  517. package/src/lib/org-api.ts +74 -0
  518. package/src/lib/org-billing-api.ts +81 -0
  519. package/src/lib/page-prompts.test.ts +32 -0
  520. package/src/lib/page-prompts.ts +23 -0
  521. package/src/lib/plugin/index.ts +32 -0
  522. package/src/lib/plugin/tool-definitions.ts +306 -0
  523. package/src/lib/pricing-data.ts +115 -0
  524. package/src/lib/promotions-types.ts +58 -0
  525. package/src/lib/settings-api.ts +63 -0
  526. package/src/lib/status-colors.ts +38 -0
  527. package/src/lib/tenant-context.tsx +134 -0
  528. package/src/lib/trpc-types.ts +173 -0
  529. package/src/lib/trpc.tsx +86 -0
  530. package/src/lib/utils.test.ts +55 -0
  531. package/src/lib/utils.ts +18 -0
  532. package/src/lib/validate-redirect-url.ts +39 -0
  533. package/src/lib/webmcp/feature-detect.ts +13 -0
  534. package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
  535. package/src/lib/webmcp/register.ts +44 -0
  536. package/src/lib/webmcp/tools.ts +422 -0
  537. package/src/proxy.ts +258 -0
  538. package/src/types/missing-deps.d.ts +160 -0
  539. package/src/types/motion-dom.d.ts +162 -0
  540. package/src/types/vitest-matchers.d.ts +40 -0
  541. package/src/types/web-mcp.d.ts +22 -0
  542. package/tsconfig.json +34 -0
  543. package/vitest.config.ts +26 -0
@@ -0,0 +1,505 @@
1
+ import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { Wizard } from "@/components/channel-wizard";
4
+ import { FieldInteractive } from "@/components/channel-wizard/field-interactive";
5
+ import { FieldPaste } from "@/components/channel-wizard/field-paste";
6
+ import { StepRenderer } from "@/components/channel-wizard/step-renderer";
7
+ import type { SetupStep } from "@/lib/channel-manifests";
8
+ import {
9
+ CHANNEL_MANIFESTS_FIXTURE,
10
+ DISCORD_MANIFEST,
11
+ TELEGRAM_MANIFEST,
12
+ } from "./fixtures/mock-manifests";
13
+
14
+ // Mock @/lib/channel-manifests to use fixture data (sync for Wizard component tests)
15
+ vi.mock("@/lib/channel-manifests", async () => {
16
+ const actual = await vi.importActual("@/lib/channel-manifests");
17
+ return {
18
+ ...actual,
19
+ getChannelManifests: vi.fn().mockResolvedValue(CHANNEL_MANIFESTS_FIXTURE),
20
+ getManifest: vi
21
+ .fn()
22
+ .mockImplementation(async (id: string) => CHANNEL_MANIFESTS_FIXTURE.find((m) => m.id === id)),
23
+ };
24
+ });
25
+
26
+ // Mock next/navigation
27
+ vi.mock("next/navigation", () => ({
28
+ useRouter: () => ({ push: vi.fn() }),
29
+ useSearchParams: () => new URLSearchParams(),
30
+ }));
31
+
32
+ // Mock @/lib/api for connection test tests
33
+ vi.mock("@/lib/api", () => ({
34
+ testChannelConnection: vi.fn(),
35
+ }));
36
+
37
+ describe("channel-manifests", () => {
38
+ it("provides Discord, Slack, and Telegram manifests via getChannelManifests", async () => {
39
+ const { getChannelManifests } = await import("@/lib/channel-manifests");
40
+ const manifests = await getChannelManifests();
41
+ expect(manifests).toHaveLength(3);
42
+ expect(manifests.map((m) => m.id)).toEqual(["discord", "slack", "telegram"]);
43
+ });
44
+
45
+ it("getManifest returns correct manifest by id", async () => {
46
+ const { getManifest } = await import("@/lib/channel-manifests");
47
+ const discord = await getManifest("discord");
48
+ expect(discord?.name).toBe("Discord");
49
+ });
50
+
51
+ it("getManifest returns undefined for unknown id", async () => {
52
+ const { getManifest } = await import("@/lib/channel-manifests");
53
+ const result = await getManifest("unknown");
54
+ expect(result).toBeUndefined();
55
+ });
56
+
57
+ it("Discord manifest has 4 setup steps", async () => {
58
+ const { getManifest } = await import("@/lib/channel-manifests");
59
+ const discord = await getManifest("discord");
60
+ expect(discord?.setup).toHaveLength(4);
61
+ expect(discord?.setup.map((s) => s.id)).toEqual([
62
+ "create-bot",
63
+ "paste-token",
64
+ "select-guild",
65
+ "done",
66
+ ]);
67
+ });
68
+
69
+ it("Telegram manifest has secret token field with paste flow", async () => {
70
+ const { getManifest } = await import("@/lib/channel-manifests");
71
+ const telegram = await getManifest("telegram");
72
+ const tokenStep = telegram?.setup.find((s) => s.id === "paste-token");
73
+ const tokenField = tokenStep?.fields[0];
74
+ expect(tokenField?.secret).toBe(true);
75
+ expect(tokenField?.setupFlow).toBe("paste");
76
+ });
77
+
78
+ it("Slack manifest has oauth flow", async () => {
79
+ const { getManifest } = await import("@/lib/channel-manifests");
80
+ const slack = await getManifest("slack");
81
+ const oauthStep = slack?.setup.find((s) => s.id === "oauth");
82
+ const oauthField = oauthStep?.fields[0];
83
+ expect(oauthField?.setupFlow).toBe("oauth");
84
+ });
85
+ });
86
+
87
+ describe("FieldPaste", () => {
88
+ it("renders label and input", () => {
89
+ const onChange = vi.fn();
90
+ render(
91
+ <FieldPaste
92
+ field={{
93
+ key: "token",
94
+ label: "Bot Token",
95
+ type: "string",
96
+ required: true,
97
+ secret: true,
98
+ setupFlow: "paste",
99
+ placeholder: "Enter token",
100
+ }}
101
+ value=""
102
+ onChange={onChange}
103
+ />,
104
+ );
105
+
106
+ expect(screen.getByText("Bot Token")).toBeInTheDocument();
107
+ expect(screen.getByPlaceholderText("Enter token")).toBeInTheDocument();
108
+ });
109
+
110
+ it("masks secret fields by default and toggles visibility", () => {
111
+ const onChange = vi.fn();
112
+ render(
113
+ <FieldPaste
114
+ field={{
115
+ key: "token",
116
+ label: "Token",
117
+ type: "string",
118
+ required: true,
119
+ secret: true,
120
+ setupFlow: "paste",
121
+ }}
122
+ value="secret-value"
123
+ onChange={onChange}
124
+ />,
125
+ );
126
+
127
+ const input = screen.getByLabelText("Token") as HTMLInputElement;
128
+ expect(input.type).toBe("password");
129
+
130
+ fireEvent.click(screen.getByText("Show"));
131
+ expect(input.type).toBe("text");
132
+
133
+ fireEvent.click(screen.getByText("Hide"));
134
+ expect(input.type).toBe("password");
135
+ });
136
+
137
+ it("calls onChange when typing", () => {
138
+ const onChange = vi.fn();
139
+ render(
140
+ <FieldPaste
141
+ field={{
142
+ key: "token",
143
+ label: "Token",
144
+ type: "string",
145
+ required: true,
146
+ setupFlow: "paste",
147
+ }}
148
+ value=""
149
+ onChange={onChange}
150
+ />,
151
+ );
152
+
153
+ fireEvent.change(screen.getByLabelText("Token"), { target: { value: "abc" } });
154
+ expect(onChange).toHaveBeenCalledWith("token", "abc");
155
+ });
156
+
157
+ it("displays error message", () => {
158
+ render(
159
+ <FieldPaste
160
+ field={{
161
+ key: "token",
162
+ label: "Token",
163
+ type: "string",
164
+ required: true,
165
+ setupFlow: "paste",
166
+ }}
167
+ value=""
168
+ onChange={vi.fn()}
169
+ error="Token is required"
170
+ />,
171
+ );
172
+
173
+ expect(screen.getByText("Token is required")).toBeInTheDocument();
174
+ });
175
+ });
176
+
177
+ describe("FieldInteractive", () => {
178
+ it("renders select options", () => {
179
+ const onChange = vi.fn();
180
+ render(
181
+ <FieldInteractive
182
+ field={{
183
+ key: "guild",
184
+ label: "Server",
185
+ type: "select",
186
+ required: true,
187
+ setupFlow: "interactive",
188
+ options: [
189
+ { label: "Server A", value: "a" },
190
+ { label: "Server B", value: "b" },
191
+ ],
192
+ }}
193
+ value=""
194
+ onChange={onChange}
195
+ />,
196
+ );
197
+
198
+ expect(screen.getByText("Server A")).toBeInTheDocument();
199
+ expect(screen.getByText("Server B")).toBeInTheDocument();
200
+ });
201
+
202
+ it("highlights selected option and calls onChange", () => {
203
+ const onChange = vi.fn();
204
+ render(
205
+ <FieldInteractive
206
+ field={{
207
+ key: "guild",
208
+ label: "Server",
209
+ type: "select",
210
+ required: true,
211
+ setupFlow: "interactive",
212
+ options: [
213
+ { label: "Server A", value: "a" },
214
+ { label: "Server B", value: "b" },
215
+ ],
216
+ }}
217
+ value="a"
218
+ onChange={onChange}
219
+ />,
220
+ );
221
+
222
+ expect(screen.getByText("Selected")).toBeInTheDocument();
223
+ fireEvent.click(screen.getByText("Server B"));
224
+ expect(onChange).toHaveBeenCalledWith("guild", "b");
225
+ });
226
+ });
227
+
228
+ describe("StepRenderer", () => {
229
+ it("renders instruction text and external link", () => {
230
+ const step: SetupStep = {
231
+ id: "create-bot",
232
+ title: "Create Bot",
233
+ description: "Create a bot",
234
+ instruction: "Go to the developer portal",
235
+ externalUrl: "https://discord.com/developers",
236
+ fields: [],
237
+ };
238
+
239
+ render(<StepRenderer step={step} values={{}} errors={{}} onChange={vi.fn()} />);
240
+
241
+ expect(screen.getByText("Go to the developer portal")).toBeInTheDocument();
242
+ expect(screen.getByText("Open discord.com")).toBeInTheDocument();
243
+ });
244
+
245
+ it("renders completion step with checkmark", () => {
246
+ const step: SetupStep = {
247
+ id: "done",
248
+ title: "Done",
249
+ description: "Your bot is ready to use.",
250
+ fields: [],
251
+ };
252
+
253
+ render(<StepRenderer step={step} values={{}} errors={{}} onChange={vi.fn()} />);
254
+
255
+ expect(screen.getByText("Your bot is ready to use.")).toBeInTheDocument();
256
+ });
257
+ });
258
+
259
+ describe("Wizard", () => {
260
+ const discord = DISCORD_MANIFEST;
261
+
262
+ it("renders the first step", () => {
263
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
264
+
265
+ expect(screen.getByText("Create a Discord Bot")).toBeInTheDocument();
266
+ expect(screen.getByText("Step 1 of 4")).toBeInTheDocument();
267
+ expect(screen.getByText("Cancel")).toBeInTheDocument();
268
+ expect(screen.getByText("Continue")).toBeInTheDocument();
269
+ });
270
+
271
+ it("renders the Discord brand color badge", () => {
272
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
273
+
274
+ const brandBadge = screen.getByText("D");
275
+ expect(brandBadge).toBeInTheDocument();
276
+ });
277
+
278
+ it("navigates to next step on Continue", () => {
279
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
280
+
281
+ fireEvent.click(screen.getByText("Continue"));
282
+ expect(screen.getByText("Enter Bot Token")).toBeInTheDocument();
283
+ expect(screen.getByText("Step 2 of 4")).toBeInTheDocument();
284
+ });
285
+
286
+ it("navigates back on Back button", () => {
287
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
288
+
289
+ fireEvent.click(screen.getByText("Continue"));
290
+ expect(screen.getByText("Enter Bot Token")).toBeInTheDocument();
291
+
292
+ fireEvent.click(screen.getByText("Back"));
293
+ expect(screen.getByText("Create a Discord Bot")).toBeInTheDocument();
294
+ });
295
+
296
+ it("calls onCancel when Cancel is clicked", () => {
297
+ const onCancel = vi.fn();
298
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={onCancel} />);
299
+
300
+ fireEvent.click(screen.getByText("Cancel"));
301
+ expect(onCancel).toHaveBeenCalledOnce();
302
+ });
303
+
304
+ it("validates required fields before advancing", () => {
305
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
306
+
307
+ // Step 1 has no fields, advance to step 2
308
+ fireEvent.click(screen.getByText("Continue"));
309
+ expect(screen.getByText("Enter Bot Token")).toBeInTheDocument();
310
+
311
+ // Try to advance without filling token
312
+ fireEvent.click(screen.getByText("Continue"));
313
+ expect(screen.getByText("Bot Token is required")).toBeInTheDocument();
314
+ // Should still be on step 2
315
+ expect(screen.getByText("Step 2 of 4")).toBeInTheDocument();
316
+ });
317
+
318
+ it("advances after filling required field", () => {
319
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
320
+
321
+ // Step 1 -> Step 2
322
+ fireEvent.click(screen.getByText("Continue"));
323
+
324
+ // Fill in the token
325
+ fireEvent.change(screen.getByPlaceholderText("Paste your Discord bot token"), {
326
+ target: { value: "valid-token-123" },
327
+ });
328
+
329
+ // Step 2 -> Step 3
330
+ fireEvent.click(screen.getByText("Continue"));
331
+ expect(screen.getByText("Select Server")).toBeInTheDocument();
332
+ });
333
+
334
+ it("shows Finish on last step and calls onComplete", () => {
335
+ const onComplete = vi.fn();
336
+ render(<Wizard manifest={discord} onComplete={onComplete} onCancel={vi.fn()} />);
337
+
338
+ // Navigate through all steps
339
+ // Step 1 (no fields)
340
+ fireEvent.click(screen.getByText("Continue"));
341
+
342
+ // Step 2 (token)
343
+ fireEvent.change(screen.getByPlaceholderText("Paste your Discord bot token"), {
344
+ target: { value: "valid-token-123" },
345
+ });
346
+ fireEvent.click(screen.getByText("Continue"));
347
+
348
+ // Step 3 (select guild)
349
+ fireEvent.click(screen.getByText("Platform HQ"));
350
+ fireEvent.click(screen.getByText("Continue"));
351
+
352
+ // Step 4 (done)
353
+ expect(screen.getByText("Finish")).toBeInTheDocument();
354
+ expect(screen.getByText("Connection Complete")).toBeInTheDocument();
355
+
356
+ fireEvent.click(screen.getByText("Finish"));
357
+ expect(onComplete).toHaveBeenCalledWith({
358
+ botToken: "valid-token-123",
359
+ guildId: "1234567890",
360
+ });
361
+ });
362
+
363
+ it("shows test connection button on final step", () => {
364
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
365
+
366
+ // Navigate to final step
367
+ fireEvent.click(screen.getByText("Continue"));
368
+ fireEvent.change(screen.getByPlaceholderText("Paste your Discord bot token"), {
369
+ target: { value: "valid-token-123" },
370
+ });
371
+ fireEvent.click(screen.getByText("Continue"));
372
+ fireEvent.click(screen.getByText("Platform HQ"));
373
+ fireEvent.click(screen.getByText("Continue"));
374
+
375
+ expect(screen.getByText("Test Bot Connection")).toBeInTheDocument();
376
+ });
377
+
378
+ it("renders progress bar", () => {
379
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
380
+
381
+ expect(screen.getByText("25%")).toBeInTheDocument();
382
+ fireEvent.click(screen.getByText("Continue"));
383
+ expect(screen.getByText("50%")).toBeInTheDocument();
384
+ });
385
+
386
+ it("disables Finish button when submitting is true", () => {
387
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} submitting={true} />);
388
+
389
+ // Navigate to final step
390
+ fireEvent.click(screen.getByText("Continue")); // step 1
391
+ fireEvent.change(screen.getByPlaceholderText("Paste your Discord bot token"), {
392
+ target: { value: "valid-token-123" },
393
+ });
394
+ fireEvent.click(screen.getByText("Continue")); // step 2
395
+ fireEvent.click(screen.getByText("Platform HQ")); // select guild
396
+ fireEvent.click(screen.getByText("Continue")); // step 3
397
+
398
+ const finishBtn = screen.getByText("Connecting...");
399
+ expect(finishBtn.closest("button")).toBeDisabled();
400
+ });
401
+ });
402
+
403
+ describe("Wizard with Telegram (short flow)", () => {
404
+ const telegram = TELEGRAM_MANIFEST;
405
+
406
+ it("renders Telegram wizard with 3 steps", () => {
407
+ render(<Wizard manifest={telegram} onComplete={vi.fn()} onCancel={vi.fn()} />);
408
+
409
+ expect(screen.getByText("Create a Telegram Bot")).toBeInTheDocument();
410
+ expect(screen.getByText("Step 1 of 3")).toBeInTheDocument();
411
+ });
412
+
413
+ it("validates token format", () => {
414
+ render(<Wizard manifest={telegram} onComplete={vi.fn()} onCancel={vi.fn()} />);
415
+
416
+ fireEvent.click(screen.getByText("Continue"));
417
+
418
+ // Enter invalid format
419
+ fireEvent.change(screen.getByPlaceholderText("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"), {
420
+ target: { value: "bad token with spaces" },
421
+ });
422
+ fireEvent.click(screen.getByText("Continue"));
423
+ expect(screen.getByText("Invalid Telegram bot token format")).toBeInTheDocument();
424
+ });
425
+ });
426
+
427
+ describe("Wizard connection test API integration", () => {
428
+ const discord = DISCORD_MANIFEST;
429
+
430
+ /** Navigate the Discord wizard to the final step with a valid token. */
431
+ async function navigateToFinalStep() {
432
+ render(<Wizard manifest={discord} onComplete={vi.fn()} onCancel={vi.fn()} />);
433
+ fireEvent.click(screen.getByText("Continue")); // step 1
434
+ fireEvent.change(screen.getByPlaceholderText("Paste your Discord bot token"), {
435
+ target: { value: "valid-token-123" },
436
+ });
437
+ fireEvent.click(screen.getByText("Continue")); // step 2
438
+ fireEvent.click(screen.getByText("Platform HQ")); // select guild
439
+ fireEvent.click(screen.getByText("Continue")); // step 3 → final
440
+ }
441
+
442
+ it("calls API and shows success when connection test passes", async () => {
443
+ const { testChannelConnection } = await import("@/lib/api");
444
+ vi.mocked(testChannelConnection).mockResolvedValue({ success: true });
445
+
446
+ await navigateToFinalStep();
447
+ await act(async () => {
448
+ fireEvent.click(screen.getByText("Test Bot Connection"));
449
+ });
450
+
451
+ await waitFor(() => {
452
+ expect(screen.getByText("Connection successful")).toBeInTheDocument();
453
+ });
454
+ });
455
+
456
+ it("calls API and shows error message when connection test fails", async () => {
457
+ const { testChannelConnection } = await import("@/lib/api");
458
+ vi.mocked(testChannelConnection).mockResolvedValue({
459
+ success: false,
460
+ error: "Invalid bot token",
461
+ });
462
+
463
+ await navigateToFinalStep();
464
+ await act(async () => {
465
+ fireEvent.click(screen.getByText("Test Bot Connection"));
466
+ });
467
+
468
+ await waitFor(() => {
469
+ expect(screen.getByText("Invalid bot token")).toBeInTheDocument();
470
+ });
471
+ });
472
+
473
+ it("shows network error message when fetch throws", async () => {
474
+ const { testChannelConnection } = await import("@/lib/api");
475
+ vi.mocked(testChannelConnection).mockRejectedValue(new Error("Network error"));
476
+
477
+ await navigateToFinalStep();
478
+ await act(async () => {
479
+ fireEvent.click(screen.getByText("Test Bot Connection"));
480
+ });
481
+
482
+ await waitFor(() => {
483
+ expect(
484
+ screen.getByText("Could not reach the server. Check your connection."),
485
+ ).toBeInTheDocument();
486
+ });
487
+ });
488
+
489
+ it("shows Testing... and disables button while request is in flight", async () => {
490
+ const { testChannelConnection } = await import("@/lib/api");
491
+ vi.mocked(testChannelConnection).mockReturnValue(
492
+ new Promise(() => {
493
+ /* never resolves — keep in-flight state for test */
494
+ }),
495
+ );
496
+
497
+ await navigateToFinalStep();
498
+ await act(async () => {
499
+ fireEvent.click(screen.getByText("Test Bot Connection"));
500
+ });
501
+
502
+ const btn = screen.getByRole("button", { name: "Testing..." });
503
+ expect(btn).toBeDisabled();
504
+ });
505
+ });
@@ -0,0 +1,35 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { AmbientDot } from "@/components/chat/ambient-dot";
5
+
6
+ describe("AmbientDot", () => {
7
+ it("renders button with correct aria-label", () => {
8
+ render(<AmbientDot hasUnread={false} onClick={vi.fn()} />);
9
+ expect(screen.getByLabelText("Open Platform chat")).toBeInTheDocument();
10
+ });
11
+
12
+ it("renders with chat-ambient-dot testid", () => {
13
+ render(<AmbientDot hasUnread={false} onClick={vi.fn()} />);
14
+ expect(screen.getByTestId("chat-ambient-dot")).toBeInTheDocument();
15
+ });
16
+
17
+ it("calls onClick when clicked", async () => {
18
+ const user = userEvent.setup();
19
+ const onClick = vi.fn();
20
+ render(<AmbientDot hasUnread={false} onClick={onClick} />);
21
+
22
+ await user.click(screen.getByTestId("chat-ambient-dot"));
23
+ expect(onClick).toHaveBeenCalledOnce();
24
+ });
25
+
26
+ it("renders unread pulse animation when hasUnread is true", () => {
27
+ render(<AmbientDot hasUnread={true} onClick={vi.fn()} />);
28
+ expect(screen.getByTestId("chat-unread-pulse")).toBeInTheDocument();
29
+ });
30
+
31
+ it("does not render pulse animation when hasUnread is false", () => {
32
+ render(<AmbientDot hasUnread={false} onClick={vi.fn()} />);
33
+ expect(screen.queryByTestId("chat-unread-pulse")).not.toBeInTheDocument();
34
+ });
35
+ });
@@ -0,0 +1,78 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { ChatInput } from "@/components/chat/chat-input";
5
+
6
+ describe("ChatInput", () => {
7
+ it("renders textarea and send button", () => {
8
+ render(<ChatInput onSend={vi.fn()} />);
9
+ expect(screen.getByLabelText("Chat message input")).toBeInTheDocument();
10
+ expect(screen.getByLabelText("Send message")).toBeInTheDocument();
11
+ });
12
+
13
+ it("calls onSend with trimmed text when clicking send", async () => {
14
+ const user = userEvent.setup();
15
+ const onSend = vi.fn();
16
+ render(<ChatInput onSend={onSend} />);
17
+
18
+ await user.type(screen.getByLabelText("Chat message input"), " hello world ");
19
+ await user.click(screen.getByLabelText("Send message"));
20
+
21
+ expect(onSend).toHaveBeenCalledWith("hello world");
22
+ });
23
+
24
+ it("clears input after sending", async () => {
25
+ const user = userEvent.setup();
26
+ render(<ChatInput onSend={vi.fn()} />);
27
+
28
+ const input = screen.getByLabelText("Chat message input");
29
+ await user.type(input, "test");
30
+ await user.click(screen.getByLabelText("Send message"));
31
+
32
+ expect(input).toHaveValue("");
33
+ });
34
+
35
+ it("sends on Enter key (without Shift)", async () => {
36
+ const user = userEvent.setup();
37
+ const onSend = vi.fn();
38
+ render(<ChatInput onSend={onSend} />);
39
+
40
+ await user.type(screen.getByLabelText("Chat message input"), "enter test");
41
+ await user.keyboard("{Enter}");
42
+
43
+ expect(onSend).toHaveBeenCalledWith("enter test");
44
+ });
45
+
46
+ it("does not send on Shift+Enter", async () => {
47
+ const user = userEvent.setup();
48
+ const onSend = vi.fn();
49
+ render(<ChatInput onSend={onSend} />);
50
+
51
+ await user.type(screen.getByLabelText("Chat message input"), "multi");
52
+ await user.keyboard("{Shift>}{Enter}{/Shift}");
53
+
54
+ expect(onSend).not.toHaveBeenCalled();
55
+ });
56
+
57
+ it("does not send empty/whitespace-only messages", async () => {
58
+ const user = userEvent.setup();
59
+ const onSend = vi.fn();
60
+ render(<ChatInput onSend={onSend} />);
61
+
62
+ await user.type(screen.getByLabelText("Chat message input"), " ");
63
+ await user.click(screen.getByLabelText("Send message"));
64
+
65
+ expect(onSend).not.toHaveBeenCalled();
66
+ });
67
+
68
+ it("disables textarea and button when disabled prop is true", () => {
69
+ render(<ChatInput onSend={vi.fn()} disabled />);
70
+ expect(screen.getByLabelText("Chat message input")).toBeDisabled();
71
+ expect(screen.getByLabelText("Send message")).toBeDisabled();
72
+ });
73
+
74
+ it("disables send button when input is empty", () => {
75
+ render(<ChatInput onSend={vi.fn()} />);
76
+ expect(screen.getByLabelText("Send message")).toBeDisabled();
77
+ });
78
+ });
@@ -0,0 +1,45 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { ChatMessage } from "@/components/chat/chat-message";
4
+ import type { ChatMessage as ChatMessageType } from "@/lib/chat/types";
5
+
6
+ function msg(
7
+ overrides: Partial<ChatMessageType> & { role: ChatMessageType["role"]; content: string },
8
+ ): ChatMessageType {
9
+ return { id: crypto.randomUUID(), timestamp: Date.now(), ...overrides };
10
+ }
11
+
12
+ describe("ChatMessage", () => {
13
+ it("renders user message with right-aligned styling", () => {
14
+ render(<ChatMessage message={msg({ role: "user", content: "Hello bot" })} />);
15
+ const el = screen.getByTestId("chat-message-user");
16
+ expect(el).toBeInTheDocument();
17
+ expect(el.className).toContain("justify-end");
18
+ expect(screen.getByText("Hello bot")).toBeInTheDocument();
19
+ });
20
+
21
+ it("renders bot message with left-aligned styling", () => {
22
+ render(<ChatMessage message={msg({ role: "bot", content: "Hi there" })} />);
23
+ const el = screen.getByTestId("chat-message-bot");
24
+ expect(el).toBeInTheDocument();
25
+ expect(el.className).toContain("justify-start");
26
+ expect(screen.getByText("Hi there")).toBeInTheDocument();
27
+ });
28
+
29
+ it("renders event message as centered marker", () => {
30
+ render(<ChatMessage message={msg({ role: "event", content: "session started" })} />);
31
+ const el = screen.getByTestId("chat-event-marker");
32
+ expect(el).toBeInTheDocument();
33
+ expect(screen.getByText(/session started/)).toBeInTheDocument();
34
+ });
35
+
36
+ it("applies different background classes for user vs bot", () => {
37
+ const { rerender } = render(<ChatMessage message={msg({ role: "user", content: "u" })} />);
38
+ const userBubble = screen.getByText("u");
39
+ expect(userBubble.className).toContain("bg-primary/20");
40
+
41
+ rerender(<ChatMessage message={msg({ role: "bot", content: "b" })} />);
42
+ const botBubble = screen.getByText("b");
43
+ expect(botBubble.className).toContain("bg-muted/30");
44
+ });
45
+ });