wadi 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 (294) hide show
  1. package/.agent/workflows/auto_sync.md +19 -0
  2. package/.agent/workflows/kivo_pipeline.md +27 -0
  3. package/.devcontainer/devcontainer.json +27 -0
  4. package/.github/workflows/kivo-cicd.yml +45 -0
  5. package/.github/workflows/monorepo-ci.yml +51 -0
  6. package/.github/workflows/wadi-ci.yml +38 -0
  7. package/.husky/pre-commit +2 -0
  8. package/.prettierignore +12 -0
  9. package/.prettierrc +9 -0
  10. package/.vscode/settings.json +19 -0
  11. package/CHANGELOG.md +56 -0
  12. package/CODE_OF_CONDUCT.md +43 -0
  13. package/CONTRIBUTING.md +42 -0
  14. package/DEPLOY_GUIDE.md +143 -0
  15. package/GO_LIVE_CHECKLIST.md +41 -0
  16. package/GO_LIVE_COMPLETE_REPORT.md +58 -0
  17. package/GO_LIVE_VALIDATION.md +39 -0
  18. package/LEGAL.md +38 -0
  19. package/MANIFESTO.md +40 -0
  20. package/MANUAL.md +82 -0
  21. package/OPS_PLAN.md +90 -0
  22. package/README.md +126 -0
  23. package/RELEASE_NOTES.md +51 -0
  24. package/ROADMAP.md +51 -0
  25. package/api_listado.txt +2197 -0
  26. package/apps/api/WADI_PROTOCOL.md +52 -0
  27. package/apps/api/debug-brain.js +11 -0
  28. package/apps/api/package.json +21 -0
  29. package/apps/api/src/core/analisis.js +17 -0
  30. package/apps/api/src/core/errors.js +31 -0
  31. package/apps/api/src/core/logger.js +32 -0
  32. package/apps/api/src/core/prompt-kivo.js +62 -0
  33. package/apps/api/src/index.js +212 -0
  34. package/apps/api/src/layers/human_pattern/composeResponse.js +35 -0
  35. package/apps/api/src/layers/human_pattern/detectPattern.js +39 -0
  36. package/apps/api/src/layers/human_pattern/heuristics.js +28 -0
  37. package/apps/api/src/layers/human_pattern/index.js +28 -0
  38. package/apps/api/src/layers/human_pattern/socialMemory.js +35 -0
  39. package/apps/api/src/middleware/errorHandler.js +24 -0
  40. package/apps/api/src/middleware/rateLimiter.js +38 -0
  41. package/apps/api/src/middleware/requestLogger.js +31 -0
  42. package/apps/api/src/middleware/upload.js +21 -0
  43. package/apps/api/src/middleware/validation.js +70 -0
  44. package/apps/api/src/modules/data.js +25 -0
  45. package/apps/api/src/modules/marketing.js +54 -0
  46. package/apps/api/src/modules/projects.js +40 -0
  47. package/apps/api/src/openai.js +16 -0
  48. package/apps/api/src/preferences/index.js +20 -0
  49. package/apps/api/src/register_user.js +22 -0
  50. package/apps/api/src/routes/kivo.js +58 -0
  51. package/apps/api/src/routes/monitoring.js +55 -0
  52. package/apps/api/src/routes.js +656 -0
  53. package/apps/api/src/supabase.js +17 -0
  54. package/apps/api/src/tools/index.js +57 -0
  55. package/apps/api/src/wadi-brain.js +171 -0
  56. package/apps/api/supabase/migrations/20251218_audit_logs.sql +27 -0
  57. package/apps/api/supabase/migrations/v2-chat-persistence.sql +83 -0
  58. package/apps/api/supabase/migrations/v3-cascade-delete.sql +11 -0
  59. package/apps/api/supabase/migrations/v3-security-fix.sql +90 -0
  60. package/apps/api/supabase/migrations/v3-storage.sql +36 -0
  61. package/apps/api/supabase/migrations/v4-gamification.sql +83 -0
  62. package/apps/api/supabase/migrations/v5-smoke-index.sql +6 -0
  63. package/apps/api/supabase/migrations/v6-schema-integrity-fix.sql +98 -0
  64. package/apps/api/supabase/migrations/v7-performance-indexes.sql +5 -0
  65. package/apps/api/supabase/migrations/v7-security-hardening.sql +132 -0
  66. package/apps/api/supabase/migrations/v8-security-hardening.sql +79 -0
  67. package/apps/api/test_human_pattern.js +30 -0
  68. package/apps/api/test_human_pattern_vague.js +30 -0
  69. package/apps/api/test_output.txt +76 -0
  70. package/apps/api/test_vague.js +30 -0
  71. package/apps/api/test_vague_block.js +30 -0
  72. package/apps/api/test_wadi.js +31 -0
  73. package/apps/api/tsconfig.json +13 -0
  74. package/apps/frontend/.env.local +3 -0
  75. package/apps/frontend/README.md +73 -0
  76. package/apps/frontend/eslint.config.js +27 -0
  77. package/apps/frontend/index.html +49 -0
  78. package/apps/frontend/package.json +41 -0
  79. package/apps/frontend/postcss.config.cjs +6 -0
  80. package/apps/frontend/public/cursors/wadi-neutral.svg +1 -0
  81. package/apps/frontend/public/cursors/wadi-select.svg +1 -0
  82. package/apps/frontend/public/icon-192.svg +1 -0
  83. package/apps/frontend/public/icon-512.svg +1 -0
  84. package/apps/frontend/public/manifest.webmanifest +23 -0
  85. package/apps/frontend/public/sw.js +57 -0
  86. package/apps/frontend/public/vite.svg +1 -0
  87. package/apps/frontend/public/wadi.svg +5 -0
  88. package/apps/frontend/src/assets/react.svg +1 -0
  89. package/apps/frontend/src/components/AuthLoader.tsx +50 -0
  90. package/apps/frontend/src/components/ChatInput.tsx +272 -0
  91. package/apps/frontend/src/components/ChatInterface.tsx +202 -0
  92. package/apps/frontend/src/components/ErrorBoundary.tsx +52 -0
  93. package/apps/frontend/src/components/InputArea.tsx +201 -0
  94. package/apps/frontend/src/components/Layout.tsx +73 -0
  95. package/apps/frontend/src/components/MessageBubble.tsx +66 -0
  96. package/apps/frontend/src/components/OnboardingModal.tsx +108 -0
  97. package/apps/frontend/src/components/SettingsModal.tsx +187 -0
  98. package/apps/frontend/src/components/Sidebar.tsx +171 -0
  99. package/apps/frontend/src/components/WadiOnboarding.tsx +71 -0
  100. package/apps/frontend/src/components/auditor/AuditReport.tsx +166 -0
  101. package/apps/frontend/src/components/auditor/AuditorHeader.tsx +34 -0
  102. package/apps/frontend/src/components/auditor/ContextPanel.tsx +138 -0
  103. package/apps/frontend/src/components/auditor/DataDeconstructor.tsx +85 -0
  104. package/apps/frontend/src/components/auditor/DecisionWall.tsx +65 -0
  105. package/apps/frontend/src/components/auditor/Dropzone.tsx +137 -0
  106. package/apps/frontend/src/components/common/Button.tsx +97 -0
  107. package/apps/frontend/src/components/common/Card.tsx +43 -0
  108. package/apps/frontend/src/components/common/Input.tsx +73 -0
  109. package/apps/frontend/src/components/common/Modal.tsx +53 -0
  110. package/apps/frontend/src/components/ui/Button.tsx +68 -0
  111. package/apps/frontend/src/components/ui/Card.tsx +86 -0
  112. package/apps/frontend/src/components/ui/Input.tsx +28 -0
  113. package/apps/frontend/src/components/ui/LogItem.tsx +64 -0
  114. package/apps/frontend/src/components/ui/MondayButton.tsx +40 -0
  115. package/apps/frontend/src/components/ui/MondayCard.tsx +24 -0
  116. package/apps/frontend/src/components/ui/Scouter.tsx +208 -0
  117. package/apps/frontend/src/components/ui/TerminalInput.tsx +202 -0
  118. package/apps/frontend/src/components/ui/Tooltip.tsx +67 -0
  119. package/apps/frontend/src/config/chatShortcuts.ts +20 -0
  120. package/apps/frontend/src/config/supabase.ts +6 -0
  121. package/apps/frontend/src/final_status.txt +3 -0
  122. package/apps/frontend/src/hooks/useScouter.ts +28 -0
  123. package/apps/frontend/src/hooks/useStoreHydration.ts +24 -0
  124. package/apps/frontend/src/improvement_status.txt +5 -0
  125. package/apps/frontend/src/index.css +88 -0
  126. package/apps/frontend/src/main.tsx +62 -0
  127. package/apps/frontend/src/monday_status.txt +7 -0
  128. package/apps/frontend/src/pages/ChatPage.tsx +201 -0
  129. package/apps/frontend/src/pages/DashboardPage.tsx +375 -0
  130. package/apps/frontend/src/pages/IntroWadi.tsx +114 -0
  131. package/apps/frontend/src/pages/LandingPage.tsx +103 -0
  132. package/apps/frontend/src/pages/Login.tsx +190 -0
  133. package/apps/frontend/src/pages/PrivacyPage.tsx +213 -0
  134. package/apps/frontend/src/pages/ProjectDetail.tsx +80 -0
  135. package/apps/frontend/src/pages/Projects.tsx +247 -0
  136. package/apps/frontend/src/pages/TermsPage.tsx +202 -0
  137. package/apps/frontend/src/router.tsx +83 -0
  138. package/apps/frontend/src/store/authStore.ts +152 -0
  139. package/apps/frontend/src/store/chatStore.ts +837 -0
  140. package/apps/frontend/src/store/documentStore.ts +89 -0
  141. package/apps/frontend/src/store/projectsStore.ts +111 -0
  142. package/apps/frontend/src/store/runsStore.ts +98 -0
  143. package/apps/frontend/src/utils/api.ts +34 -0
  144. package/apps/frontend/src/vite-env.d.ts +7 -0
  145. package/apps/frontend/tailwind.config.cjs +32 -0
  146. package/apps/frontend/tsconfig.app.json +27 -0
  147. package/apps/frontend/tsconfig.json +7 -0
  148. package/apps/frontend/tsconfig.node.json +26 -0
  149. package/apps/frontend/vite.config.ts +25 -0
  150. package/apps/kivo/.firebase/hosting.d3d3.cache +11 -0
  151. package/apps/kivo/.firebaserc +5 -0
  152. package/apps/kivo/BRANDING_GUIDE.md +71 -0
  153. package/apps/kivo/DEPLOYMENT_READY.md +46 -0
  154. package/apps/kivo/DEPLOY_URL.md +12 -0
  155. package/apps/kivo/IMPLEMENTATION_REPORT.md +35 -0
  156. package/apps/kivo/IMPLEMENTATION_REPORT_FINAL.md +49 -0
  157. package/apps/kivo/PLAN_MOBILE_2.0.md +44 -0
  158. package/apps/kivo/PWA_VERIFICATION_GUIDE.md +77 -0
  159. package/apps/kivo/README.md +28 -0
  160. package/apps/kivo/REBUILD_REPORT.md +34 -0
  161. package/apps/kivo/UPGRADE_REPORT.md +35 -0
  162. package/apps/kivo/android/app/build.gradle +54 -0
  163. package/apps/kivo/android/app/capacitor.build.gradle +19 -0
  164. package/apps/kivo/android/app/proguard-rules.pro +21 -0
  165. package/apps/kivo/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  166. package/apps/kivo/android/app/src/main/AndroidManifest.xml +35 -0
  167. package/apps/kivo/android/app/src/main/java/com/kivo/app/MainActivity.java +5 -0
  168. package/apps/kivo/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  169. package/apps/kivo/android/app/src/main/res/drawable/splash.png +0 -0
  170. package/apps/kivo/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  171. package/apps/kivo/android/app/src/main/res/drawable-land-ldpi/splash.png +0 -0
  172. package/apps/kivo/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  173. package/apps/kivo/android/app/src/main/res/drawable-land-night-hdpi/splash.png +0 -0
  174. package/apps/kivo/android/app/src/main/res/drawable-land-night-ldpi/splash.png +0 -0
  175. package/apps/kivo/android/app/src/main/res/drawable-land-night-mdpi/splash.png +0 -0
  176. package/apps/kivo/android/app/src/main/res/drawable-land-night-xhdpi/splash.png +0 -0
  177. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png +0 -0
  178. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png +0 -0
  179. package/apps/kivo/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  180. package/apps/kivo/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  181. package/apps/kivo/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  182. package/apps/kivo/android/app/src/main/res/drawable-night/splash.png +0 -0
  183. package/apps/kivo/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  184. package/apps/kivo/android/app/src/main/res/drawable-port-ldpi/splash.png +0 -0
  185. package/apps/kivo/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  186. package/apps/kivo/android/app/src/main/res/drawable-port-night-hdpi/splash.png +0 -0
  187. package/apps/kivo/android/app/src/main/res/drawable-port-night-ldpi/splash.png +0 -0
  188. package/apps/kivo/android/app/src/main/res/drawable-port-night-mdpi/splash.png +0 -0
  189. package/apps/kivo/android/app/src/main/res/drawable-port-night-xhdpi/splash.png +0 -0
  190. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png +0 -0
  191. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png +0 -0
  192. package/apps/kivo/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  193. package/apps/kivo/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  194. package/apps/kivo/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  195. package/apps/kivo/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  196. package/apps/kivo/android/app/src/main/res/layout/activity_main.xml +12 -0
  197. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
  198. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +9 -0
  199. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  200. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
  201. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  202. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  203. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher.png +0 -0
  204. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png +0 -0
  205. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png +0 -0
  206. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png +0 -0
  207. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  208. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
  209. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  210. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  211. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  212. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
  213. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  214. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  215. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  216. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
  217. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  218. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  219. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  220. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
  221. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  222. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  223. package/apps/kivo/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  224. package/apps/kivo/android/app/src/main/res/values/strings.xml +7 -0
  225. package/apps/kivo/android/app/src/main/res/values/styles.xml +22 -0
  226. package/apps/kivo/android/app/src/main/res/xml/file_paths.xml +5 -0
  227. package/apps/kivo/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  228. package/apps/kivo/android/build.gradle +29 -0
  229. package/apps/kivo/android/capacitor.settings.gradle +3 -0
  230. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  231. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  232. package/apps/kivo/android/gradle.properties +22 -0
  233. package/apps/kivo/android/gradlew +252 -0
  234. package/apps/kivo/android/gradlew.bat +94 -0
  235. package/apps/kivo/android/settings.gradle +5 -0
  236. package/apps/kivo/android/variables.gradle +16 -0
  237. package/apps/kivo/assets/icon.png +0 -0
  238. package/apps/kivo/assets/splash.png +0 -0
  239. package/apps/kivo/capacitor.config.json +16 -0
  240. package/apps/kivo/firebase.json +6 -0
  241. package/apps/kivo/jest.config.js +4 -0
  242. package/apps/kivo/package.json +26 -0
  243. package/apps/kivo/tests_disabled/logic.test.js +34 -0
  244. package/apps/kivo/www/assets/icon-192.png +0 -0
  245. package/apps/kivo/www/assets/icon-512.png +0 -0
  246. package/apps/kivo/www/assets/kivo-icon.png +0 -0
  247. package/apps/kivo/www/assets/pop.mp3 +0 -0
  248. package/apps/kivo/www/favicon.ico +0 -0
  249. package/apps/kivo/www/firebase-config.js +75 -0
  250. package/apps/kivo/www/index.html +38 -0
  251. package/apps/kivo/www/manifest.json +29 -0
  252. package/apps/kivo/www/script.js +72 -0
  253. package/apps/kivo/www/style.css +82 -0
  254. package/apps/kivo/www/sw.js +46 -0
  255. package/apps/kivo-brain-api/.env.example +2 -0
  256. package/apps/kivo-brain-api/KIVO_BACKEND_SETUP.md +27 -0
  257. package/apps/kivo-brain-api/controllers/kivoController.js +31 -0
  258. package/apps/kivo-brain-api/index.js +24 -0
  259. package/apps/kivo-brain-api/package.json +17 -0
  260. package/apps/kivo-brain-api/routes/message.js +8 -0
  261. package/apps/kivo-brain-api/services/openaiService.js +64 -0
  262. package/apps/tests/wadi-tests.js +155 -0
  263. package/apps/wadi-brain/docs/README_REMOVE_OPTIONS.md +26 -0
  264. package/cli/commands/deploy.js +10 -0
  265. package/cli/commands/docs.js +16 -0
  266. package/cli/commands/explain.js +29 -0
  267. package/cli/commands/lint.js +14 -0
  268. package/cli/index.js +26 -0
  269. package/cli/package.json +12 -0
  270. package/docs/CNAME +1 -0
  271. package/docs/README.md +38 -0
  272. package/docs/USO.md +30 -0
  273. package/docs/index.html +46 -0
  274. package/eslint.config.js +101 -0
  275. package/final_validation.json +27 -0
  276. package/frontend_listado.txt +33387 -0
  277. package/listado.txt +12591 -0
  278. package/package.json +46 -0
  279. package/packages/logger/index.js +43 -0
  280. package/packages/logger/package.json +19 -0
  281. package/packages/logger/test-logger.js +7 -0
  282. package/packages_listado.txt +801 -0
  283. package/pnpm-workspace.yaml +6 -0
  284. package/reseteador_existencial.ps1 +40 -0
  285. package/scripts/bump-version.js +26 -0
  286. package/scripts/env.ps1 +13 -0
  287. package/scripts/setup_android.ps1 +25 -0
  288. package/scripts/setup_virtual_env.ps1 +77 -0
  289. package/scripts/smoke-test.js +84 -0
  290. package/scripts/trigger_render_deploy.ps1 +3 -0
  291. package/scripts/validate-release.js +34 -0
  292. package/temp_check.js +57 -0
  293. package/tsconfig.json +5 -0
  294. package/validation_report.json +27 -0
@@ -0,0 +1,171 @@
1
+ import { useNavigate, useLocation } from "react-router-dom";
2
+ import { useAuthStore } from "../store/authStore";
3
+ import { useChatStore } from "../store/chatStore";
4
+ import { useEffect, useState } from "react";
5
+ import { MessageSquare, Plus, LogOut, Settings, User } from "lucide-react";
6
+ import { SettingsModal } from "./SettingsModal";
7
+
8
+ interface SidebarProps {
9
+ isOpen?: boolean;
10
+ onClose?: () => void;
11
+ className?: string; // Kept for compat
12
+ }
13
+
14
+ export function Sidebar({ isOpen, onClose }: SidebarProps) {
15
+ const navigate = useNavigate();
16
+ const location = useLocation();
17
+ const { user, signOut } = useAuthStore();
18
+ const {
19
+ conversations,
20
+ fetchConversations,
21
+ loadConversation,
22
+ resetChat,
23
+ setSidebarOpen,
24
+ deleteConversation,
25
+ fetchCriminalSummary,
26
+ } = useChatStore();
27
+
28
+ useEffect(() => {
29
+ if (user?.id) {
30
+ fetchConversations();
31
+ fetchCriminalSummary();
32
+ }
33
+ }, [user?.id, fetchConversations, fetchCriminalSummary]);
34
+
35
+ const handleNewChat = () => {
36
+ resetChat();
37
+ navigate("/chat");
38
+ onClose?.();
39
+ };
40
+
41
+ const handleHistoryClick = (id: string) => {
42
+ loadConversation(id);
43
+ navigate(`/chat/${id}`);
44
+ onClose?.();
45
+ setSidebarOpen(false);
46
+ };
47
+
48
+ const handleDelete = async (e: React.MouseEvent, id: string) => {
49
+ e.stopPropagation();
50
+ if (confirm("¿Borrar chat?")) {
51
+ await deleteConversation(id);
52
+ if (location.pathname.includes(id)) {
53
+ navigate("/chat");
54
+ }
55
+ fetchConversations();
56
+ }
57
+ };
58
+
59
+ const [showSettings, setShowSettings] = useState(false);
60
+
61
+ return (
62
+ <aside
63
+ className={`sidebar-drawer ${
64
+ isOpen ? "open" : ""
65
+ } flex flex-col h-full bg-[var(--wadi-surface)] backdrop-blur-xl border-r border-[var(--wadi-border)] w-[280px] shadow-2xl z-50 transition-all duration-300`}
66
+ >
67
+ {/* HEADER */}
68
+ <div className="p-6 flex items-center justify-between">
69
+ <div className="flex items-center gap-3">
70
+ <div className="w-8 h-8 rounded-xl bg-gradient-to-br from-[#8B5CF6] to-[#38bdf8] flex items-center justify-center shadow-md text-white font-bold text-xs">
71
+ W
72
+ </div>
73
+ <h1 className="font-['Outfit'] text-lg font-bold text-[var(--wadi-text)] tracking-tight">
74
+ WADI
75
+ </h1>
76
+ </div>
77
+ <button
78
+ onClick={handleNewChat}
79
+ className="p-2 bg-[var(--wadi-surface)] hover:bg-[var(--wadi-surface-hover)] border border-[var(--wadi-border)] rounded-lg shadow-sm text-[var(--wadi-text-dim)] hover:text-[#8B5CF6] transition-all"
80
+ title="Nuevo Chat"
81
+ >
82
+ <Plus size={18} />
83
+ </button>
84
+ </div>
85
+
86
+ {/* CHAT LIST */}
87
+ <div className="flex-1 overflow-y-auto px-4 space-y-2 py-2">
88
+ <h3 className="text-xs font-semibold text-[var(--wadi-text-dim)] uppercase tracking-wider px-2 mb-2">
89
+ Conversaciones
90
+ </h3>
91
+ {conversations && conversations.length > 0 ? (
92
+ conversations.map((c) => {
93
+ const isActive = location.pathname.includes(c.id);
94
+ return (
95
+ <div
96
+ key={c.id}
97
+ onClick={() => handleHistoryClick(c.id)}
98
+ className={`group flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 ${
99
+ isActive
100
+ ? "bg-[var(--wadi-surface-hover)] shadow-sm border border-[var(--wadi-border)]"
101
+ : "hover:bg-[var(--wadi-surface-hover)] hover:shadow-xs border border-transparent"
102
+ }`}
103
+ >
104
+ <MessageSquare
105
+ size={16}
106
+ className={
107
+ isActive
108
+ ? "text-[#8B5CF6]"
109
+ : "text-[var(--wadi-text-dim)] group-hover:text-[var(--wadi-text)]"
110
+ }
111
+ />
112
+ <div className="flex-1 overflow-hidden">
113
+ <p
114
+ className={`text-sm truncate ${
115
+ isActive
116
+ ? "font-medium text-[var(--wadi-text)]"
117
+ : "text-[var(--wadi-text-muted)]"
118
+ }`}
119
+ >
120
+ {c.title || "Nueva Conversación"}
121
+ </p>
122
+ </div>
123
+ <button
124
+ onClick={(e) => handleDelete(e, c.id)}
125
+ className="opacity-0 group-hover:opacity-100 text-[var(--wadi-text-dim)] hover:text-red-400 transition-opacity"
126
+ >
127
+ ×
128
+ </button>
129
+ </div>
130
+ );
131
+ })
132
+ ) : (
133
+ <div className="text-center py-10 text-[var(--wadi-text-dim)] text-sm italic">
134
+ Sin chats recientes.
135
+ </div>
136
+ )}
137
+ </div>
138
+
139
+ {/* USER FOOTER */}
140
+ <div className="p-4 border-t border-[var(--wadi-border)] bg-[var(--wadi-surface)] backdrop-blur-md">
141
+ <div className="flex items-center gap-3 p-2 rounded-xl hover:bg-[var(--wadi-surface-hover)] transition-colors">
142
+ <div className="w-9 h-9 rounded-full bg-slate-700 flex items-center justify-center text-slate-300">
143
+ <User size={16} />
144
+ </div>
145
+ <div className="flex-1 overflow-hidden">
146
+ <p className="text-sm font-medium text-[var(--wadi-text)] truncate">
147
+ {user?.email?.split("@")[0] || "Usuario"}
148
+ </p>
149
+ <p className="text-[10px] text-[var(--wadi-text-dim)]">En línea</p>
150
+ </div>
151
+ <div className="flex gap-1">
152
+ <button
153
+ onClick={() => setShowSettings(true)}
154
+ className="p-1.5 text-[var(--wadi-text-dim)] hover:text-[#8B5CF6] hover:bg-purple-900/20 rounded-lg transition-colors"
155
+ >
156
+ <Settings size={16} />
157
+ </button>
158
+ <button
159
+ onClick={() => signOut()}
160
+ className="p-1.5 text-[var(--wadi-text-dim)] hover:text-red-500 hover:bg-red-900/20 rounded-lg transition-colors"
161
+ >
162
+ <LogOut size={16} />
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ {showSettings && <SettingsModal onClose={() => setShowSettings(false)} />}
169
+ </aside>
170
+ );
171
+ }
@@ -0,0 +1,71 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ export type WadiMood = "hostile" | "mildly_disappointed" | "training_wheels";
4
+
5
+ interface WadiOnboardingProps {
6
+ mood?: WadiMood;
7
+ }
8
+
9
+ export default function WadiOnboarding({
10
+ mood = "hostile",
11
+ }: WadiOnboardingProps) {
12
+ const [step, setStep] = useState(0);
13
+
14
+ const messagesByMood: Record<WadiMood, string[]> = {
15
+ hostile: [
16
+ "*⌛ Cargando paciencia... ERROR 404*",
17
+ "🧠 WADI activo (a regañadientes).",
18
+ "No estoy acá para mimarte,",
19
+ "estoy para que dejes de mentirte.",
20
+ "➡️ Decime qué rompiste hoy,",
21
+ "🌀 o volvé cuando tengas un plan real.",
22
+ ],
23
+ mildly_disappointed: [
24
+ "*⌛ WADI está despertando de su siesta funcional...*",
25
+ "🧠 WADI activo.",
26
+ "Esto puede doler menos si cooperás.",
27
+ "Tomemos una decisión antes de que vuelva la confusión.",
28
+ "📌 ¿Por dónde empezamos?",
29
+ "📉 O seguí divagando, pero sin mí.",
30
+ ],
31
+ training_wheels: [
32
+ "*⌛ Preparando el espacio para ordenar tus ideas...*",
33
+ "🧠 Hola, soy WADI.",
34
+ "Estoy acá para ayudarte a decidir sin drama.",
35
+ "Podemos ir paso a paso, sin presión.",
36
+ "🗺️ Empezamos cuando quieras.",
37
+ "☕ O tomamos un respiro y seguimos después.",
38
+ ],
39
+ };
40
+
41
+ const messages = messagesByMood[mood] || messagesByMood.hostile;
42
+
43
+ useEffect(() => {
44
+ if (step < messages.length - 1) {
45
+ const timer = setTimeout(() => setStep(step + 1), 1000);
46
+ return () => clearTimeout(timer);
47
+ }
48
+ }, [step, messages.length]);
49
+
50
+ return (
51
+ <div className="whitespace-pre-wrap font-mono text-sm leading-relaxed p-4 rounded-xl bg-[var(--color-surface)] border border-[var(--color-border)] w-[90vw] max-w-lg mx-auto mt-10 text-[var(--color-text-main)] flex flex-col">
52
+ {messages.slice(0, step + 1).map((line, index) => (
53
+ <p
54
+ key={index}
55
+ style={{
56
+ marginBottom: "0.25rem",
57
+ color: index === 0 ? "var(--color-text-soft)" : "inherit",
58
+ }}
59
+ >
60
+ {index === 0 ? (
61
+ <span className="animate-pulse">{line}</span>
62
+ ) : index === 1 ? (
63
+ <strong>{line}</strong>
64
+ ) : (
65
+ line
66
+ )}
67
+ </p>
68
+ ))}
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,166 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useParams, useNavigate } from "react-router-dom";
3
+ import { Button } from "../ui/Button";
4
+ import { API_URL } from "../../store/chatStore";
5
+ import { useAuthStore } from "../../store/authStore";
6
+ import { useScouter } from "../../hooks/useScouter";
7
+ import { supabase } from "../../config/supabase";
8
+
9
+ interface Vulnerability {
10
+ level: "HIGH" | "MEDIUM" | "LOW";
11
+ title: string;
12
+ description: string;
13
+ }
14
+
15
+ export function AuditReport() {
16
+ const { conversationId } = useParams();
17
+ const navigate = useNavigate();
18
+ const { user } = useAuthStore();
19
+ const { playAlertSound } = useScouter();
20
+
21
+ const [vulnerabilities, setVulnerabilities] = useState<Vulnerability[]>([]);
22
+ const [isLoading, setIsLoading] = useState(true);
23
+ const [error, setError] = useState<string | null>(null);
24
+
25
+ useEffect(() => {
26
+ if (!conversationId || !user) return; // Wait for user
27
+
28
+ const fetchAudit = async () => {
29
+ setIsLoading(true);
30
+ try {
31
+ const { data: sessionData } = await supabase.auth.getSession();
32
+ const token = sessionData.session?.access_token;
33
+ if (!token) throw new Error("NO_AUTH_TOKEN");
34
+
35
+ const res = await fetch(
36
+ `${API_URL}/api/conversations/${conversationId}/audit`,
37
+ {
38
+ headers: { Authorization: `Bearer ${token}` },
39
+ }
40
+ );
41
+
42
+ if (!res.ok) throw new Error("AUDIT_REFUSED_BY_SYSTEM");
43
+
44
+ const jsonData = await res.json();
45
+ setVulnerabilities(jsonData.vulnerabilities || []);
46
+ } catch (err) {
47
+ console.error(err);
48
+ setError("ERROR DE CONEXIÓN VITAL: No pude sincronizar con tu estado.");
49
+ playAlertSound();
50
+ } finally {
51
+ setIsLoading(false);
52
+ }
53
+ };
54
+
55
+ fetchAudit();
56
+ }, [conversationId, user, playAlertSound]);
57
+
58
+ if (isLoading) {
59
+ return (
60
+ <div className="fixed inset-0 z-50 bg-[#0f111a] flex flex-col items-center justify-center p-4">
61
+ <div className="animate-pulse text-[var(--wadi-primary)] font-mono-wadi text-xl tracking-[0.3em] uppercase">
62
+ [SINCRONIZANDO REALIDAD...]
63
+ </div>
64
+ <div className="mt-4 w-64 h-1 bg-[var(--wadi-surface)] overflow-hidden rounded-full">
65
+ <div className="h-full bg-[var(--wadi-primary)] animate-[shimmer_1s_infinite] w-1/3 rounded-full"></div>
66
+ </div>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ if (error) {
72
+ return (
73
+ <div className="fixed inset-0 z-50 bg-black flex flex-col items-center justify-center p-4">
74
+ <h1 className="text-[var(--wadi-text-muted)] text-xl font-bold font-mono-wadi mb-4">
75
+ DESCONEXIÓN
76
+ </h1>
77
+ <p className="text-white font-mono-wadi text-sm">{error}</p>
78
+ <Button
79
+ onClick={() => navigate(-1)}
80
+ className="mt-8 bg-[var(--wadi-surface)] text-white hover:bg-[var(--wadi-surface)]/80"
81
+ >
82
+ [VOLVER AL REFUGIO]
83
+ </Button>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ const highRiskCount = vulnerabilities.filter(
89
+ (v) => v.level === "HIGH"
90
+ ).length;
91
+ const riskPercentage = Math.min(
92
+ 100,
93
+ vulnerabilities.length * 20 + highRiskCount * 15
94
+ );
95
+
96
+ return (
97
+ <div className="fixed inset-0 z-50 bg-[#0f111a] flex flex-col items-center justify-center p-4 animate-in fade-in duration-500">
98
+ {/* HEADER */}
99
+ <div className="w-full max-w-2xl border-b border-[var(--wadi-primary)]/30 pb-4 mb-8 flex justify-between items-end">
100
+ <div>
101
+ <h1 className="text-3xl font-light font-['Outfit'] text-[var(--wadi-primary)] tracking-widest uppercase">
102
+ Mapa de Distorsión
103
+ </h1>
104
+ <p className="text-[var(--wadi-text-muted)] mt-2 font-mono-wadi text-xs uppercase">
105
+ REF: {conversationId?.split("-")[0]} // ESTADO:{" "}
106
+ {highRiskCount > 0 ? "DISTORSIÓN ACTIVA" : "LÚCIDO"}
107
+ </p>
108
+ </div>
109
+ <div className="text-right">
110
+ <div className="text-4xl font-bold text-white font-['Outfit']">
111
+ {riskPercentage}%
112
+ </div>
113
+ <div className="text-[var(--wadi-text-muted)] text-[10px] uppercase tracking-widest mt-1">
114
+ NIVEL DE AUTOENGAÑO
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ {/* VULNERABILITIES LIST */}
120
+ <div className="w-full max-w-2xl space-y-4 max-h-[60vh] overflow-y-auto pr-2 custom-scrollbar">
121
+ {vulnerabilities.length === 0 ? (
122
+ <div className="text-center py-12 text-[var(--wadi-text-muted)] font-mono-wadi border border-dashed border-[var(--wadi-border)] bg-[var(--wadi-surface)]/20 rounded-sm">
123
+ [TODO CLARO. ESTÁS SIENDO HONESTO CON VOS MISMO.]
124
+ </div>
125
+ ) : (
126
+ vulnerabilities.map((vuln, idx) => (
127
+ <div
128
+ key={idx}
129
+ className={`
130
+ border-l-2 p-6 bg-[var(--wadi-surface)]/30 relative overflow-hidden group rounded-r-sm hover:bg-[var(--wadi-surface)]/50 transition-colors
131
+ ${vuln.level === "HIGH" ? "border-[var(--wadi-alert)]" : "border-[var(--wadi-primary)]"}
132
+ `}
133
+ >
134
+ <div className="flex justify-between items-start mb-3">
135
+ <span
136
+ className={`
137
+ font-mono-wadi text-[10px] px-2 py-0.5 font-bold tracking-wider uppercase
138
+ ${vuln.level === "HIGH" ? "text-[var(--wadi-alert)] bg-[var(--wadi-alert)]/10" : "text-[var(--wadi-primary)] bg-[var(--wadi-primary)]/10"}
139
+ `}
140
+ >
141
+ {vuln.level === "HIGH" ? "ZONA CIEGA CRÍTICA" : "DISTORSIÓN"}
142
+ </span>
143
+ </div>
144
+ <h3 className="font-['Outfit'] text-lg font-medium text-white mb-2">
145
+ {vuln.title}
146
+ </h3>
147
+ <p className="text-[var(--wadi-text-muted)] text-sm font-light leading-relaxed">
148
+ {vuln.description}
149
+ </p>
150
+ </div>
151
+ ))
152
+ )}
153
+ </div>
154
+
155
+ {/* ACTIONS */}
156
+ <div className="w-full max-w-2xl mt-8 flex justify-end gap-4">
157
+ <Button
158
+ className="border border-[var(--wadi-text-muted)] text-[var(--wadi-text-muted)] hover:text-white"
159
+ onClick={() => navigate(-1)}
160
+ >
161
+ [CERRAR MAPA]
162
+ </Button>
163
+ </div>
164
+ </div>
165
+ );
166
+ }
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+
3
+ export const AuditorHeader: React.FC = () => {
4
+ return (
5
+ <header className="fixed top-0 left-0 w-full h-[60px] bg-[var(--monday-bg)]/90 backdrop-blur-md border-b border-[var(--monday-border)] z-40 flex items-center justify-between px-6">
6
+ <div className="flex items-center gap-3">
7
+ <div className="w-8 h-8 flex items-center justify-center border border-[var(--monday-primary)] rounded-full animate-pulse-soft">
8
+ <div className="w-2 h-2 bg-[var(--monday-primary)] rounded-full"></div>
9
+ </div>
10
+ <div>
11
+ <h1 className="text-xl font-bold tracking-tighter text-white font-mono leading-none">
12
+ MONDAY<span className="text-[var(--monday-primary)]">::OS</span>
13
+ </h1>
14
+ <div className="flex items-center gap-2">
15
+ <span className="w-1.5 h-1.5 bg-green-500 rounded-full animate-blink block"></span>
16
+ <span className="text-[9px] text-[var(--monday-text-dim)] tracking-widest uppercase font-mono">
17
+ ONLINE // V3.0
18
+ </span>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ {/* Decorative Status */}
24
+ <div className="hidden md:flex gap-4 font-mono text-[10px] text-[var(--monday-text-dim)]">
25
+ <span className="border px-2 py-1 border-[var(--monday-border)] rounded">
26
+ MEM: 64TB
27
+ </span>
28
+ <span className="border px-2 py-1 border-[var(--monday-border)] rounded text-[var(--monday-primary)]">
29
+ UPLINK: SECURE
30
+ </span>
31
+ </div>
32
+ </header>
33
+ );
34
+ };
@@ -0,0 +1,138 @@
1
+ import { useChatStore } from "../../store/chatStore";
2
+ import { Target, AlertTriangle } from "lucide-react";
3
+ import { useMemo } from "react";
4
+
5
+ export function ContextPanel() {
6
+ const { messages, activeFocus, rank, points } = useChatStore();
7
+
8
+ // Extract diagnoses from messages
9
+ const diagnoses = useMemo(() => {
10
+ return messages
11
+ .filter((m) => m.diagnosis)
12
+ .map((m) => ({
13
+ id: m.id,
14
+ parsed: m.diagnosis?.replace(/_/g, " "),
15
+ timestamp: m.created_at,
16
+ }))
17
+ .reverse(); // Newest first
18
+ }, [messages]);
19
+
20
+ /*
21
+ // Future V2: Extract implied commitments
22
+ const commitments = useMemo(() => {
23
+ // ...
24
+ }, [activeFocus]);
25
+ */
26
+
27
+ return (
28
+ <div className="flex flex-col h-full p-6 gap-8 bg-zinc-50/50">
29
+ {/* HEADER */}
30
+ <div className="flex flex-col gap-1">
31
+ <h2 className="text-sm font-bold tracking-tight text-zinc-900 uppercase">
32
+ Tablero de Control
33
+ </h2>
34
+ <span className="text-[10px] text-zinc-500 font-medium tracking-wide">
35
+ FACT SHEET & METRICS
36
+ </span>
37
+ </div>
38
+
39
+ {/* 1. ACTIVE FOCUS CARD */}
40
+ <div
41
+ className={`p-4 bg-white rounded-2xl shadow-sm border relative overflow-hidden group transition-colors duration-500 ${activeFocus ? "border-amber-400/30" : "border-zinc-200/60"}`}
42
+ >
43
+ <div
44
+ className={`absolute top-0 left-0 w-1 h-full transition-colors duration-500 ${activeFocus ? "bg-amber-500" : "bg-zinc-300"}`}
45
+ ></div>
46
+ <div className="flex items-start justify-between mb-2">
47
+ <div className="flex items-center gap-2 text-zinc-600">
48
+ <Target
49
+ size={14}
50
+ className={
51
+ activeFocus ? "text-amber-600 animate-pulse" : "text-zinc-400"
52
+ }
53
+ />
54
+ <span
55
+ className={`text-[10px] font-bold uppercase tracking-wider ${activeFocus ? "text-amber-700" : "text-zinc-400"}`}
56
+ >
57
+ {activeFocus ? "Foco Pendiente" : "Sin Objetivo"}
58
+ </span>
59
+ </div>
60
+ {activeFocus && (
61
+ <span className="flex h-2 w-2 rounded-full bg-amber-500 animate-[pulse_3s_infinite]" />
62
+ )}
63
+ </div>
64
+ <p className="text-sm font-medium text-zinc-800 leading-snug">
65
+ {activeFocus || "El sistema espera una declaración de intención."}
66
+ </p>
67
+ </div>
68
+
69
+ {/* 2. DIAGNOSIS LOG */}
70
+ <div className="flex flex-col gap-3 flex-1 min-h-0">
71
+ <div className="flex items-center justify-between text-zinc-500">
72
+ <div className="flex items-center gap-2">
73
+ <AlertTriangle size={14} />
74
+ <span className="text-[10px] font-bold uppercase tracking-wider">
75
+ Historial Clínico
76
+ </span>
77
+ </div>
78
+ <span className="text-[10px] bg-zinc-200 px-1.5 py-0.5 rounded-full">
79
+ {diagnoses.length}
80
+ </span>
81
+ </div>
82
+
83
+ <div className="flex flex-col gap-2 overflow-y-auto pr-2 -mr-2">
84
+ {diagnoses.length === 0 ? (
85
+ <div className="p-4 border border-dashed border-zinc-200 rounded-xl text-center">
86
+ <span className="text-xs text-zinc-400">
87
+ Sin anomalías detectadas.
88
+ </span>
89
+ </div>
90
+ ) : (
91
+ diagnoses.map((diag) => (
92
+ <div
93
+ key={diag.id}
94
+ className="p-3 bg-red-50/50 border border-red-100 rounded-xl flex flex-col gap-1"
95
+ >
96
+ <div className="flex items-center justify-between">
97
+ <span className="text-[10px] font-bold text-red-700 uppercase">
98
+ {diag.parsed}
99
+ </span>
100
+ <span className="text-[9px] text-red-400 opacity-60">
101
+ {diag.timestamp
102
+ ? new Date(diag.timestamp).toLocaleTimeString([], {
103
+ hour: "2-digit",
104
+ minute: "2-digit",
105
+ })
106
+ : ""}
107
+ </span>
108
+ </div>
109
+ </div>
110
+ ))
111
+ )}
112
+ </div>
113
+ </div>
114
+
115
+ {/* 3. METRICS FOOTER */}
116
+ <div className="mt-auto pt-6 border-t border-zinc-200 flex flex-col gap-4">
117
+ <div className="flex items-center justify-between">
118
+ <span className="text-xs text-zinc-500 font-medium">Eficiencia</span>
119
+ <span className="text-sm font-bold font-mono text-zinc-900">
120
+ {points} PTS
121
+ </span>
122
+ </div>
123
+ <div className="w-full bg-zinc-200 h-1.5 rounded-full overflow-hidden">
124
+ {/* Visual progress bar based on points (clamped 0-1000) */}
125
+ <div
126
+ className="h-full bg-zinc-800 transition-all duration-500"
127
+ style={{
128
+ width: `${Math.min(Math.max((points / 1000) * 100, 5), 100)}%`,
129
+ }}
130
+ />
131
+ </div>
132
+ <div className="flex items-center gap-2 text-[10px] text-zinc-400 uppercase tracking-widest justify-center">
133
+ <span>Rango: {rank.replace(/_/g, " ")}</span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,85 @@
1
+ import React from "react";
2
+ import { MondayCard } from "../ui/MondayCard";
3
+
4
+ interface DeconstructItem {
5
+ item: string;
6
+ category: "CRÍTICO" | "RUIDO" | "VULNERABILIDAD";
7
+ verdict: string;
8
+ }
9
+
10
+ interface DataDeconstructorProps {
11
+ items: DeconstructItem[];
12
+ }
13
+
14
+ export const DataDeconstructor: React.FC<DataDeconstructorProps> = ({
15
+ items,
16
+ }) => {
17
+ const getColor = (cat: string) => {
18
+ switch (cat) {
19
+ case "CRÍTICO":
20
+ return "text-[var(--wadi-primary)] border-[var(--wadi-primary)]";
21
+ case "VULNERABILIDAD":
22
+ return "text-[var(--wadi-red)] border-[var(--wadi-red)]";
23
+ default:
24
+ return "text-[var(--wadi-gray)] border-[var(--wadi-gray)]";
25
+ }
26
+ };
27
+
28
+ return (
29
+ <MondayCard
30
+ title="DECONSTRUCTOR_V3.0"
31
+ className="w-full my-4 overflow-hidden"
32
+ >
33
+ <div className="overflow-x-auto">
34
+ <table className="w-full text-left border-collapse font-mono text-xs">
35
+ <thead>
36
+ <tr className="border-b border-[var(--wadi-border)] text-[var(--wadi-text-dim)]">
37
+ <th className="p-3 uppercase">Input Data</th>
38
+ <th className="p-3 uppercase">Class</th>
39
+ <th className="p-3 uppercase">System Verdict</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ {items.map((row, idx) => (
44
+ <tr
45
+ key={idx}
46
+ className="border-b border-[rgba(255,255,255,0.05)] hover:bg-[rgba(255,255,255,0.02)]"
47
+ >
48
+ <td className="p-3 text-[var(--wadi-text)]">{row.item}</td>
49
+ <td className="p-3">
50
+ <span
51
+ className={`border px-2 py-0.5 text-[10px] font-bold ${getColor(row.category)}`}
52
+ >
53
+ {row.category}
54
+ </span>
55
+ </td>
56
+ <td className="p-3 text-[var(--wadi-text-dim)] italic">
57
+ "{row.verdict}"
58
+ </td>
59
+ </tr>
60
+ ))}
61
+ </tbody>
62
+ </table>
63
+ </div>
64
+ <div className="p-2 border-t border-[var(--wadi-border)] text-[10px] text-[var(--wadi-text-dim)] flex justify-between">
65
+ <span>TOTAL ITEMS: {items.length}</span>
66
+ <button
67
+ onClick={() => {
68
+ const text = items
69
+ .map((i) => `[${i.category}] ${i.item} -> ${i.verdict}`)
70
+ .join("\n");
71
+ const blob = new Blob([text], { type: "text/plain" });
72
+ const url = URL.createObjectURL(blob);
73
+ const a = document.createElement("a");
74
+ a.href = url;
75
+ a.download = `WADI_PLAN_${Date.now()}.txt`;
76
+ a.click();
77
+ }}
78
+ className="hover:text-[var(--wadi-primary)] hover:underline cursor-pointer uppercase font-bold"
79
+ >
80
+ [EXPORT_PLAN]
81
+ </button>
82
+ </div>
83
+ </MondayCard>
84
+ );
85
+ };