@geminilight/mindos 0.1.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 (282) hide show
  1. package/.env.local.example +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +423 -0
  4. package/README_zh.md +423 -0
  5. package/app/README.md +152 -0
  6. package/app/app/api/ask/route.ts +170 -0
  7. package/app/app/api/ask-sessions/route.ts +90 -0
  8. package/app/app/api/auth/route.ts +37 -0
  9. package/app/app/api/backlinks/route.ts +22 -0
  10. package/app/app/api/bootstrap/route.ts +37 -0
  11. package/app/app/api/extract-pdf/route.ts +82 -0
  12. package/app/app/api/file/route.ts +138 -0
  13. package/app/app/api/files/route.ts +12 -0
  14. package/app/app/api/git/route.ts +42 -0
  15. package/app/app/api/graph/route.ts +113 -0
  16. package/app/app/api/recent-files/route.ts +10 -0
  17. package/app/app/api/search/route.ts +17 -0
  18. package/app/app/api/settings/reset-token/route.ts +21 -0
  19. package/app/app/api/settings/route.ts +123 -0
  20. package/app/app/error.tsx +33 -0
  21. package/app/app/globals.css +368 -0
  22. package/app/app/icon.svg +35 -0
  23. package/app/app/layout.tsx +103 -0
  24. package/app/app/login/page.tsx +120 -0
  25. package/app/app/page.tsx +12 -0
  26. package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
  27. package/app/app/view/[...path]/error.tsx +33 -0
  28. package/app/app/view/[...path]/loading.tsx +15 -0
  29. package/app/app/view/[...path]/page.tsx +93 -0
  30. package/app/components/AskFab.tsx +59 -0
  31. package/app/components/AskModal.tsx +398 -0
  32. package/app/components/Backlinks.tsx +75 -0
  33. package/app/components/Breadcrumb.tsx +31 -0
  34. package/app/components/CsvView.tsx +325 -0
  35. package/app/components/DirView.tsx +138 -0
  36. package/app/components/Editor.tsx +124 -0
  37. package/app/components/EditorWrapper.tsx +17 -0
  38. package/app/components/ErrorBoundary.tsx +53 -0
  39. package/app/components/FileTree.tsx +369 -0
  40. package/app/components/HomeContent.tsx +262 -0
  41. package/app/components/JsonView.tsx +27 -0
  42. package/app/components/MarkdownEditor.tsx +95 -0
  43. package/app/components/MarkdownView.tsx +118 -0
  44. package/app/components/SearchModal.tsx +193 -0
  45. package/app/components/SettingsModal.tsx +237 -0
  46. package/app/components/Sidebar.tsx +136 -0
  47. package/app/components/SidebarLayout.tsx +36 -0
  48. package/app/components/TableOfContents.tsx +150 -0
  49. package/app/components/ThemeToggle.tsx +34 -0
  50. package/app/components/WysiwygEditor.tsx +75 -0
  51. package/app/components/ask/FileChip.tsx +30 -0
  52. package/app/components/ask/MentionPopover.tsx +52 -0
  53. package/app/components/ask/MessageList.tsx +126 -0
  54. package/app/components/ask/SessionHistory.tsx +49 -0
  55. package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
  56. package/app/components/renderers/BacklinksRenderer.tsx +147 -0
  57. package/app/components/renderers/ConfigRenderer.tsx +236 -0
  58. package/app/components/renderers/CsvRenderer.tsx +77 -0
  59. package/app/components/renderers/DiffRenderer.tsx +310 -0
  60. package/app/components/renderers/GraphRenderer.tsx +428 -0
  61. package/app/components/renderers/SummaryRenderer.tsx +251 -0
  62. package/app/components/renderers/TimelineRenderer.tsx +213 -0
  63. package/app/components/renderers/TodoRenderer.tsx +474 -0
  64. package/app/components/renderers/WorkflowRenderer.tsx +404 -0
  65. package/app/components/renderers/csv/BoardView.tsx +146 -0
  66. package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
  67. package/app/components/renderers/csv/EditableCell.tsx +43 -0
  68. package/app/components/renderers/csv/GalleryView.tsx +40 -0
  69. package/app/components/renderers/csv/TableView.tsx +164 -0
  70. package/app/components/renderers/csv/types.ts +87 -0
  71. package/app/components/settings/AiTab.tsx +111 -0
  72. package/app/components/settings/AppearanceTab.tsx +101 -0
  73. package/app/components/settings/KnowledgeTab.tsx +157 -0
  74. package/app/components/settings/PluginsTab.tsx +82 -0
  75. package/app/components/settings/Primitives.tsx +60 -0
  76. package/app/components/settings/ShortcutsTab.tsx +22 -0
  77. package/app/components/settings/types.ts +41 -0
  78. package/app/components/ui/button.tsx +60 -0
  79. package/app/components/ui/dialog.tsx +157 -0
  80. package/app/components/ui/input.tsx +20 -0
  81. package/app/components/ui/scroll-area.tsx +55 -0
  82. package/app/components/ui/toggle.tsx +44 -0
  83. package/app/components/ui/tooltip.tsx +66 -0
  84. package/app/components.json +25 -0
  85. package/app/data/pages/home-dark.png +0 -0
  86. package/app/data/pages/home-mobile-crop.png +0 -0
  87. package/app/data/pages/home-mobile.png +0 -0
  88. package/app/data/pages/home.png +0 -0
  89. package/app/data/pages/view-dir.png +0 -0
  90. package/app/data/pages/view-file-bot.png +0 -0
  91. package/app/data/pages/view-file-dark-crop.png +0 -0
  92. package/app/data/pages/view-file-dark.png +0 -0
  93. package/app/data/pages/view-file-mobile.png +0 -0
  94. package/app/data/pages/view-file-sm.png +0 -0
  95. package/app/data/pages/view-file-top.png +0 -0
  96. package/app/data/pages/view-file.png +0 -0
  97. package/app/eslint.config.mjs +18 -0
  98. package/app/hooks/useAskSession.ts +181 -0
  99. package/app/hooks/useFileUpload.ts +126 -0
  100. package/app/hooks/useMention.ts +65 -0
  101. package/app/lib/LocaleContext.tsx +40 -0
  102. package/app/lib/actions.ts +40 -0
  103. package/app/lib/agent/index.ts +3 -0
  104. package/app/lib/agent/model.ts +18 -0
  105. package/app/lib/agent/prompt.ts +32 -0
  106. package/app/lib/agent/tools.ts +151 -0
  107. package/app/lib/api.ts +55 -0
  108. package/app/lib/core/backlinks.ts +40 -0
  109. package/app/lib/core/csv.ts +28 -0
  110. package/app/lib/core/fs-ops.ts +118 -0
  111. package/app/lib/core/git.ts +50 -0
  112. package/app/lib/core/index.ts +58 -0
  113. package/app/lib/core/lines.ts +89 -0
  114. package/app/lib/core/search.ts +79 -0
  115. package/app/lib/core/security.ts +43 -0
  116. package/app/lib/core/tree.ts +113 -0
  117. package/app/lib/core/types.ts +40 -0
  118. package/app/lib/fs.ts +467 -0
  119. package/app/lib/i18n.ts +300 -0
  120. package/app/lib/jwt.ts +58 -0
  121. package/app/lib/renderers/index.ts +79 -0
  122. package/app/lib/renderers/registry.ts +70 -0
  123. package/app/lib/settings.ts +150 -0
  124. package/app/lib/types.ts +32 -0
  125. package/app/lib/utils.ts +34 -0
  126. package/app/next-env.d.ts +6 -0
  127. package/app/next.config.ts +10 -0
  128. package/app/package-lock.json +15306 -0
  129. package/app/package.json +71 -0
  130. package/app/postcss.config.mjs +7 -0
  131. package/app/proxy.ts +64 -0
  132. package/app/public/file.svg +1 -0
  133. package/app/public/globe.svg +1 -0
  134. package/app/public/landing/index.html +353 -0
  135. package/app/public/landing/style.css +216 -0
  136. package/app/public/logo-square.svg +37 -0
  137. package/app/public/logo.svg +37 -0
  138. package/app/public/next.svg +1 -0
  139. package/app/public/vercel.svg +1 -0
  140. package/app/public/window.svg +1 -0
  141. package/app/scripts/extract-pdf.cjs +56 -0
  142. package/app/tsconfig.json +34 -0
  143. package/app/vitest.config.ts +14 -0
  144. package/assets/demo-flow-zh.html +622 -0
  145. package/assets/images/demo-flow-dark.png +0 -0
  146. package/assets/images/demo-flow-light.png +0 -0
  147. package/assets/images/demo-flow-zh-dark.png +0 -0
  148. package/assets/images/demo-flow-zh-light.png +0 -0
  149. package/assets/images/gui-sync-cv.png +0 -0
  150. package/assets/logo-square.svg +37 -0
  151. package/bin/cli.js +894 -0
  152. package/mcp/README.md +113 -0
  153. package/mcp/package-lock.json +1717 -0
  154. package/mcp/package.json +18 -0
  155. package/mcp/src/index.ts +494 -0
  156. package/mcp/tsconfig.json +13 -0
  157. package/package.json +49 -0
  158. package/scripts/setup.js +675 -0
  159. package/scripts/upgrade-prompt.md +147 -0
  160. package/skills/mindos/SKILL.md +319 -0
  161. package/skills/mindos-zh/SKILL.md +318 -0
  162. package/templates/README.md +31 -0
  163. package/templates/empty/CHANGELOG.md +9 -0
  164. package/templates/empty/CONFIG.json +197 -0
  165. package/templates/empty/CONFIG.md +73 -0
  166. package/templates/empty/INSTRUCTION.md +177 -0
  167. package/templates/empty/README.md +27 -0
  168. package/templates/en/CHANGELOG.md +9 -0
  169. package/templates/en/CONFIG.json +197 -0
  170. package/templates/en/CONFIG.md +73 -0
  171. package/templates/en/INSTRUCTION.md +177 -0
  172. package/templates/en/README.md +27 -0
  173. package/templates/en/TODO.md +13 -0
  174. package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
  175. package/templates/en//360/237/221/244 Profile/README.md" +15 -0
  176. package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
  177. package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
  178. package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
  179. package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
  180. package/templates/en//360/237/223/232 Resources/README.md" +21 -0
  181. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
  182. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
  183. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
  184. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
  185. package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
  186. package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
  187. package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
  188. package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
  189. package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
  190. package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
  191. package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
  192. package/templates/en//360/237/223/235 Notes/README.md" +24 -0
  193. package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
  194. package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
  195. package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
  196. package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
  197. package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
  198. package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
  199. package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
  200. package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
  201. package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
  202. package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
  203. package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
  204. package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
  205. package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
  206. package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
  207. package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
  208. package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
  209. package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
  210. package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
  211. package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
  212. package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
  213. package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
  214. package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
  215. package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
  216. package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
  217. package/templates/en//360/237/224/227 Connections/README.md" +20 -0
  218. package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
  219. package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
  220. package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
  221. package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
  222. package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
  223. package/templates/en//360/237/232/200 Projects/README.md" +21 -0
  224. package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
  225. package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
  226. package/templates/template-generation-skill.md +79 -0
  227. package/templates/zh/CHANGELOG.md +9 -0
  228. package/templates/zh/CONFIG.json +197 -0
  229. package/templates/zh/CONFIG.md +66 -0
  230. package/templates/zh/INSTRUCTION.md +177 -0
  231. package/templates/zh/README.md +27 -0
  232. package/templates/zh/TODO.md +13 -0
  233. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
  234. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
  235. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
  236. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
  237. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
  238. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
  239. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
  240. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
  241. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +1 -0
  242. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +1 -0
  243. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
  244. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
  245. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
  246. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
  247. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256//360/237/247/252_example_/344/274/232/350/256/256/347/272/252/350/246/201.md" +17 -0
  248. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210/README.md" +8 -0
  249. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210//360/237/247/252_example_/345/276/205/345/217/215/351/246/210/344/272/213/351/241/271.md" +16 -0
  250. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
  251. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +16 -0
  252. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261/README.md" +8 -0
  253. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261//360/237/247/252_example_/344/270/264/346/227/266/351/200/237/350/256/260.md" +13 -0
  254. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
  255. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
  256. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
  257. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257//360/237/247/252_example_/344/277/241/346/201/257/351/207/207/351/233/206/346/265/201/347/250/213.md" +13 -0
  258. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
  259. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223//360/237/247/252_example_/345/206/205/345/256/271/345/217/221/345/270/203/346/265/201/347/250/213.md" +13 -0
  260. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
  261. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224//360/237/247/252_example_/346/226/207/347/214/256/347/273/274/350/277/260/346/265/201/347/250/213.md" +16 -0
  262. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
  263. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +26 -0
  264. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
  265. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
  266. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/205/263/347/263/273/346/200/273/350/247/210.csv" +5 -0
  267. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
  268. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213//360/237/247/252_example_/345/220/214/344/272/213/350/265/265/344/270/200/350/276/260.md" +16 -0
  269. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
  270. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246//360/237/247/252_example_/345/220/214/345/255/246/351/231/210/347/253/213/346/254/247.md" +16 -0
  271. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
  272. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272//360/237/247/252_example_/345/256/266/344/272/272/347/216/213/345/273/272/345/233/275.md" +16 -0
  273. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
  274. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213//360/237/247/252_example_/346/234/213/345/217/213/346/236/227/345/260/217/344/270/275.md" +16 -0
  275. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
  276. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
  277. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
  278. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +20 -0
  279. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243/README.md" +9 -0
  280. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243//360/237/247/252_example_/345/275/222/346/241/243/351/241/271/347/233/256/350/256/260/345/275/225.md" +15 -0
  281. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
  282. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224//360/237/247/252_example_/347/247/221/347/240/224/351/241/271/347/233/256/347/256/200/346/212/245.md" +16 -0
@@ -0,0 +1,113 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextResponse } from 'next/server';
3
+ import path from 'path';
4
+ import { collectAllFiles, getFileContent } from '@/lib/fs';
5
+
6
+ export interface GraphNode {
7
+ id: string; // relative file path
8
+ label: string; // basename without extension
9
+ folder: string; // dirname
10
+ }
11
+
12
+ export interface GraphEdge {
13
+ source: string;
14
+ target: string;
15
+ }
16
+
17
+ export interface GraphData {
18
+ nodes: GraphNode[];
19
+ edges: GraphEdge[];
20
+ }
21
+
22
+ function extractLinks(content: string, sourcePath: string, fileSet: Set<string>, basenameMap: Map<string, string[]>): string[] {
23
+ const targets: string[] = [];
24
+ const sourceDir = path.dirname(sourcePath);
25
+
26
+ // WikiLinks: [[target]] or [[target|alias]] or [[target#section]]
27
+ const wikiRe = /\[\[([^\]|#]+)(?:[|#][^\]]*)?/g;
28
+ let m: RegExpExecArray | null;
29
+ while ((m = wikiRe.exec(content)) !== null) {
30
+ const raw = m[1].trim();
31
+ if (!raw) continue;
32
+
33
+ // Try exact match
34
+ if (fileSet.has(raw)) {
35
+ targets.push(raw);
36
+ continue;
37
+ }
38
+ // Try with .md
39
+ const withMd = raw.endsWith('.md') ? raw : raw + '.md';
40
+ if (fileSet.has(withMd)) {
41
+ targets.push(withMd);
42
+ continue;
43
+ }
44
+ // Try basename lookup (case-insensitive)
45
+ const lower = path.basename(withMd).toLowerCase();
46
+ const candidates = basenameMap.get(lower);
47
+ if (candidates && candidates.length === 1) {
48
+ targets.push(candidates[0]);
49
+ }
50
+ // skip if ambiguous (multiple candidates)
51
+ }
52
+
53
+ // Markdown links: [text](relative/path.md) or [text](relative/path.md#section)
54
+ const mdLinkRe = /\[[^\]]+\]\(([^)]+\.md)(?:#[^)]*)?\)/g;
55
+ while ((m = mdLinkRe.exec(content)) !== null) {
56
+ const raw = m[1].trim();
57
+ if (!raw || raw.startsWith('http')) continue;
58
+ const resolved = path.normalize(path.join(sourceDir, raw));
59
+ if (fileSet.has(resolved)) {
60
+ targets.push(resolved);
61
+ }
62
+ }
63
+
64
+ return targets;
65
+ }
66
+
67
+ export async function GET() {
68
+ try {
69
+ const allFiles = collectAllFiles().filter(f => f.endsWith('.md'));
70
+ const fileSet = new Set(allFiles);
71
+
72
+ // Build basename → relPath[] lookup
73
+ const basenameMap = new Map<string, string[]>();
74
+ for (const f of allFiles) {
75
+ const key = path.basename(f).toLowerCase();
76
+ if (!basenameMap.has(key)) basenameMap.set(key, []);
77
+ basenameMap.get(key)!.push(f);
78
+ }
79
+
80
+ const nodes: GraphNode[] = allFiles.map(f => ({
81
+ id: f,
82
+ label: path.basename(f, '.md'),
83
+ folder: path.dirname(f),
84
+ }));
85
+
86
+ const edgeSet = new Set<string>();
87
+ const edges: GraphEdge[] = [];
88
+
89
+ for (const filePath of allFiles) {
90
+ let content: string;
91
+ try {
92
+ content = getFileContent(filePath);
93
+ } catch {
94
+ continue;
95
+ }
96
+
97
+ const targets = extractLinks(content, filePath, fileSet, basenameMap);
98
+ for (const target of targets) {
99
+ if (target === filePath) continue; // skip self-edges
100
+ const key = `${filePath}||${target}`;
101
+ if (!edgeSet.has(key)) {
102
+ edgeSet.add(key);
103
+ edges.push({ source: filePath, target });
104
+ }
105
+ }
106
+ }
107
+
108
+ return NextResponse.json({ nodes, edges } satisfies GraphData);
109
+ } catch (err) {
110
+ console.error('[graph] Error building graph:', err);
111
+ return NextResponse.json({ error: 'Failed to build graph' }, { status: 500 });
112
+ }
113
+ }
@@ -0,0 +1,10 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { getRecentlyModified } from '@/lib/fs';
4
+
5
+ export async function GET(req: NextRequest) {
6
+ const limitParam = req.nextUrl.searchParams.get('limit');
7
+ const limit = limitParam ? parseInt(limitParam, 10) : 10;
8
+ const files = getRecentlyModified(Math.min(limit, 30));
9
+ return NextResponse.json(files);
10
+ }
@@ -0,0 +1,17 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { searchFiles } from '@/lib/fs';
4
+
5
+ export async function GET(request: NextRequest) {
6
+ const q = request.nextUrl.searchParams.get('q') || '';
7
+ if (!q.trim()) {
8
+ return NextResponse.json([]);
9
+ }
10
+ try {
11
+ const results = searchFiles(q);
12
+ return NextResponse.json(results);
13
+ } catch (err) {
14
+ console.error('Search error:', err);
15
+ return NextResponse.json({ error: 'Search failed' }, { status: 500 });
16
+ }
17
+ }
@@ -0,0 +1,21 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextResponse } from 'next/server';
3
+ import { readSettings, writeSettings } from '@/lib/settings';
4
+ import { randomBytes } from 'crypto';
5
+
6
+ function generateToken(): string {
7
+ const hex = randomBytes(12).toString('hex'); // 24 hex chars
8
+ return (hex.match(/.{4}/g) as string[]).join('-');
9
+ }
10
+
11
+ // POST /api/settings/reset-token — generate a new auth token and persist it
12
+ export async function POST() {
13
+ try {
14
+ const current = readSettings();
15
+ const newToken = generateToken();
16
+ writeSettings({ ...current, authToken: newToken });
17
+ return NextResponse.json({ ok: true, token: newToken });
18
+ } catch (err) {
19
+ return NextResponse.json({ error: String(err) }, { status: 500 });
20
+ }
21
+ }
@@ -0,0 +1,123 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
4
+ import { invalidateCache } from '@/lib/fs';
5
+
6
+ function maskToken(token: string | undefined): string {
7
+ if (!token) return '';
8
+ // Show first 4 and last 4 chars: xxxx-••••-••••-••••-••••-xxxx
9
+ const parts = token.split('-');
10
+ if (parts.length >= 2) {
11
+ return parts[0] + '-' + parts.slice(1, -1).map(() => '••••').join('-') + '-' + parts[parts.length - 1];
12
+ }
13
+ return token.length > 8 ? token.slice(0, 4) + '••••••••' + token.slice(-4) : '***set***';
14
+ }
15
+
16
+ export async function GET() {
17
+ const settings = readSettings();
18
+
19
+ const envValues = {
20
+ AI_PROVIDER: process.env.AI_PROVIDER || '',
21
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ? '***set***' : '',
22
+ ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL || '',
23
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY ? '***set***' : '',
24
+ OPENAI_MODEL: process.env.OPENAI_MODEL || '',
25
+ OPENAI_BASE_URL: process.env.OPENAI_BASE_URL || '',
26
+ MIND_ROOT: process.env.MIND_ROOT || '',
27
+ };
28
+
29
+ // Mask API keys
30
+ const anthropic = settings.ai.providers.anthropic;
31
+ const openai = settings.ai.providers.openai;
32
+
33
+ const masked = {
34
+ ai: {
35
+ provider: settings.ai.provider,
36
+ providers: {
37
+ anthropic: {
38
+ apiKey: anthropic?.apiKey ? '***set***' : '',
39
+ model: anthropic?.model ?? '',
40
+ },
41
+ openai: {
42
+ apiKey: openai?.apiKey ? '***set***' : '',
43
+ model: openai?.model ?? '',
44
+ baseUrl: openai?.baseUrl ?? '',
45
+ },
46
+ },
47
+ },
48
+ mindRoot: settings.mindRoot,
49
+ webPassword: settings.webPassword ? '***set***' : '',
50
+ authToken: maskToken(settings.authToken),
51
+ mcpPort: settings.mcpPort ?? 8787,
52
+ envOverrides: {
53
+ AI_PROVIDER: !!process.env.AI_PROVIDER,
54
+ ANTHROPIC_API_KEY: !!process.env.ANTHROPIC_API_KEY,
55
+ ANTHROPIC_MODEL: !!process.env.ANTHROPIC_MODEL,
56
+ OPENAI_API_KEY: !!process.env.OPENAI_API_KEY,
57
+ OPENAI_MODEL: !!process.env.OPENAI_MODEL,
58
+ OPENAI_BASE_URL: !!process.env.OPENAI_BASE_URL,
59
+ MIND_ROOT: !!process.env.MIND_ROOT,
60
+ },
61
+ envValues,
62
+ };
63
+ return NextResponse.json(masked);
64
+ }
65
+
66
+ export async function POST(req: NextRequest) {
67
+ try {
68
+ const body = await req.json() as Partial<ServerSettings>;
69
+ const current = readSettings();
70
+
71
+ // Merge providers, preserving masked keys
72
+ const incomingAnthropic = body.ai?.providers?.anthropic;
73
+ const incomingOpenai = body.ai?.providers?.openai;
74
+ const curAnthropic = current.ai.providers.anthropic;
75
+ const curOpenai = current.ai.providers.openai;
76
+
77
+ // Resolve webPassword: '***set***' means keep existing, '' means clear, anything else = new value
78
+ const incomingWebPassword = body.webPassword;
79
+ const resolvedWebPassword = incomingWebPassword === '***set***'
80
+ ? current.webPassword
81
+ : (incomingWebPassword ?? current.webPassword);
82
+
83
+ // authToken is read-only via POST (use /api/settings/reset-token to regenerate)
84
+ // but allow clearing it by passing empty string
85
+ const incomingAuthToken = body.authToken;
86
+ const resolvedAuthToken = (incomingAuthToken === '' || incomingAuthToken === undefined)
87
+ ? (incomingAuthToken === '' ? '' : current.authToken)
88
+ : current.authToken;
89
+
90
+ const next: ServerSettings = {
91
+ ai: {
92
+ provider: body.ai?.provider ?? current.ai.provider,
93
+ providers: {
94
+ anthropic: {
95
+ ...curAnthropic,
96
+ ...(incomingAnthropic ?? {}),
97
+ apiKey: incomingAnthropic?.apiKey === '***set***'
98
+ ? (curAnthropic.apiKey ?? '')
99
+ : (incomingAnthropic?.apiKey ?? curAnthropic.apiKey ?? ''),
100
+ model: incomingAnthropic?.model ?? curAnthropic.model ?? 'claude-sonnet-4-6',
101
+ },
102
+ openai: {
103
+ ...curOpenai,
104
+ ...(incomingOpenai ?? {}),
105
+ apiKey: incomingOpenai?.apiKey === '***set***'
106
+ ? (curOpenai.apiKey ?? '')
107
+ : (incomingOpenai?.apiKey ?? curOpenai.apiKey ?? ''),
108
+ model: incomingOpenai?.model ?? curOpenai.model ?? 'gpt-5.4',
109
+ },
110
+ },
111
+ },
112
+ mindRoot: body.mindRoot ?? current.mindRoot,
113
+ webPassword: resolvedWebPassword,
114
+ authToken: resolvedAuthToken,
115
+ };
116
+
117
+ writeSettings(next);
118
+ if (next.mindRoot !== current.mindRoot) invalidateCache();
119
+ return NextResponse.json({ ok: true });
120
+ } catch (err) {
121
+ return NextResponse.json({ error: String(err) }, { status: 500 });
122
+ }
123
+ }
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ export default function Error({
6
+ error,
7
+ reset,
8
+ }: {
9
+ error: Error & { digest?: string };
10
+ reset: () => void;
11
+ }) {
12
+ useEffect(() => {
13
+ console.error('Page error:', error);
14
+ }, [error]);
15
+
16
+ return (
17
+ <div className="flex flex-col items-center justify-center min-h-[50vh] gap-4 px-6">
18
+ <div className="text-center">
19
+ <h2 className="text-lg font-semibold text-foreground mb-2">Something went wrong</h2>
20
+ <p className="text-sm text-muted-foreground max-w-md">
21
+ {error.message || 'An unexpected error occurred while rendering this page.'}
22
+ </p>
23
+ </div>
24
+ <button
25
+ onClick={reset}
26
+ className="px-4 py-2 rounded-md text-sm font-medium transition-colors"
27
+ style={{ background: 'var(--muted)', color: 'var(--foreground)' }}
28
+ >
29
+ Try again
30
+ </button>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,368 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --font-sans: var(--font-sans);
11
+ --font-mono: var(--font-geist-mono);
12
+ --color-sidebar-ring: var(--sidebar-ring);
13
+ --color-sidebar-border: var(--sidebar-border);
14
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
15
+ --color-sidebar-accent: var(--sidebar-accent);
16
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
17
+ --color-sidebar-primary: var(--sidebar-primary);
18
+ --color-sidebar-foreground: var(--sidebar-foreground);
19
+ --color-sidebar: var(--sidebar);
20
+ --color-chart-5: var(--chart-5);
21
+ --color-chart-4: var(--chart-4);
22
+ --color-chart-3: var(--chart-3);
23
+ --color-chart-2: var(--chart-2);
24
+ --color-chart-1: var(--chart-1);
25
+ --color-ring: var(--ring);
26
+ --color-input: var(--input);
27
+ --color-border: var(--border);
28
+ --color-destructive: var(--destructive);
29
+ --color-accent-foreground: var(--accent-foreground);
30
+ --color-accent: var(--accent);
31
+ --color-muted-foreground: var(--muted-foreground);
32
+ --color-muted: var(--muted);
33
+ --color-secondary-foreground: var(--secondary-foreground);
34
+ --color-secondary: var(--secondary);
35
+ --color-primary-foreground: var(--primary-foreground);
36
+ --color-primary: var(--primary);
37
+ --color-popover-foreground: var(--popover-foreground);
38
+ --color-popover: var(--popover);
39
+ --color-card-foreground: var(--card-foreground);
40
+ --color-card: var(--card);
41
+ --radius-sm: calc(var(--radius) * 0.6);
42
+ --radius-md: calc(var(--radius) * 0.8);
43
+ --radius-lg: var(--radius);
44
+ --radius-xl: calc(var(--radius) * 1.4);
45
+ --radius-2xl: calc(var(--radius) * 1.8);
46
+ --radius-3xl: calc(var(--radius) * 2.2);
47
+ --radius-4xl: calc(var(--radius) * 2.6);
48
+ }
49
+
50
+ body {
51
+ font-family: 'IBM Plex Sans', var(--font-geist-sans), system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji';
52
+ }
53
+
54
+ :root {
55
+ --background: #f8f6f1;
56
+ --foreground: #1c1a17;
57
+ --card: #f2efe9;
58
+ --card-foreground: #1c1a17;
59
+ --popover: #f2efe9;
60
+ --popover-foreground: #1c1a17;
61
+ --primary: #1c1a17;
62
+ --primary-foreground: #f8f6f1;
63
+ --secondary: #e8e4db;
64
+ --secondary-foreground: #1c1a17;
65
+ --muted: #e8e4db;
66
+ --muted-foreground: #7a7568;
67
+ --accent: #d9d3c6;
68
+ --accent-foreground: #1c1a17;
69
+ --destructive: oklch(0.58 0.22 27);
70
+ --border: rgba(28, 26, 23, 0.1);
71
+ --input: rgba(28, 26, 23, 0.12);
72
+ --ring: rgba(28, 26, 23, 0.3);
73
+ --radius: 0.5rem;
74
+ --amber: #c8873a;
75
+ --amber-dim: rgba(200, 135, 58, 0.12);
76
+ --sidebar: #ede9e1;
77
+ --sidebar-foreground: #1c1a17;
78
+ --sidebar-primary: #1c1a17;
79
+ --sidebar-primary-foreground: #f8f6f1;
80
+ --sidebar-accent: #d9d3c6;
81
+ --sidebar-accent-foreground: #1c1a17;
82
+ --sidebar-border: rgba(28, 26, 23, 0.08);
83
+ --sidebar-ring: rgba(28, 26, 23, 0.3);
84
+ }
85
+
86
+ .dark {
87
+ --background: #131210;
88
+ --foreground: #e8e4dc;
89
+ --card: #1c1a17;
90
+ --card-foreground: #e8e4dc;
91
+ --popover: #1c1a17;
92
+ --popover-foreground: #e8e4dc;
93
+ --primary: #e8e4dc;
94
+ --primary-foreground: #131210;
95
+ --secondary: #252219;
96
+ --secondary-foreground: #e8e4dc;
97
+ --muted: #252219;
98
+ --muted-foreground: #8a8275;
99
+ --accent: #2e2b22;
100
+ --accent-foreground: #e8e4dc;
101
+ --destructive: oklch(0.704 0.191 22.216);
102
+ --border: rgba(232, 228, 220, 0.08);
103
+ --input: rgba(232, 228, 220, 0.1);
104
+ --ring: rgba(232, 228, 220, 0.2);
105
+ --amber: #d4954a;
106
+ --amber-dim: rgba(212, 149, 74, 0.12);
107
+ --sidebar: #1c1a17;
108
+ --sidebar-foreground: #e8e4dc;
109
+ --sidebar-primary: #d4954a;
110
+ --sidebar-primary-foreground: #131210;
111
+ --sidebar-accent: #252219;
112
+ --sidebar-accent-foreground: #e8e4dc;
113
+ --sidebar-border: rgba(232, 228, 220, 0.07);
114
+ --sidebar-ring: rgba(232, 228, 220, 0.2);
115
+ }
116
+
117
+ @layer base {
118
+ * { @apply border-border outline-ring/50; }
119
+ body { @apply bg-background text-foreground; }
120
+ }
121
+
122
+ /* Content width — controlled by Settings > Appearance */
123
+ :root { --content-width: 780px; }
124
+ .content-width { max-width: var(--content-width-override, var(--content-width)); margin-left: auto; margin-right: auto; }
125
+
126
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
127
+ ::-webkit-scrollbar-track { background: transparent; }
128
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 99px; }
129
+
130
+ .sidebar-panel {
131
+ }
132
+ .sidebar-panel::before {
133
+ content: '';
134
+ position: absolute;
135
+ left: 0;
136
+ top: 15%;
137
+ bottom: 15%;
138
+ width: 1.5px;
139
+ background: linear-gradient(to bottom, transparent, var(--amber), transparent);
140
+ opacity: 0.45;
141
+ border-radius: 99px;
142
+ pointer-events: none;
143
+ z-index: 1;
144
+ }
145
+
146
+ .prose {
147
+ font-family: var(--prose-font-override, 'Lora', Georgia, serif);
148
+ color: var(--prose-body);
149
+ line-height: 1.85;
150
+ font-size: 0.95rem;
151
+ }
152
+ @media (min-width: 640px) {
153
+ .prose { font-size: 1rem; }
154
+ }
155
+ .prose h1, .prose h2, .prose h3, .prose h4 {
156
+ font-family: 'IBM Plex Sans', sans-serif;
157
+ color: var(--prose-heading);
158
+ font-weight: 600;
159
+ line-height: 1.3;
160
+ margin-top: 1.5em;
161
+ margin-bottom: 0.6em;
162
+ letter-spacing: -0.01em;
163
+ }
164
+ @media (min-width: 640px) {
165
+ .prose h1, .prose h2, .prose h3, .prose h4 {
166
+ margin-top: 2em;
167
+ margin-bottom: 0.75em;
168
+ }
169
+ }
170
+ .prose h1 { font-size: 1.85rem; margin-top: 0; }
171
+ .prose h2 { font-size: 1.35rem; border-bottom: 1px solid var(--prose-border); padding-bottom: 0.4em; }
172
+ .prose h3 { font-size: 1.15rem; }
173
+ .prose h4 { font-size: 1.05rem; }
174
+ .prose p { margin-top: 0; margin-bottom: 1.1em; }
175
+ .prose a { color: var(--amber); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(200,135,58,0.35); transition: text-decoration-color 0.15s; }
176
+ .prose a:hover { text-decoration-color: var(--amber); }
177
+ .prose strong { color: var(--prose-heading); font-weight: 600; }
178
+ .prose em { font-style: italic; color: var(--prose-muted); }
179
+ .prose ul, .prose ol { margin: 0.5em 0 1.1em 0; padding-left: 1.6em; }
180
+ .prose ul { list-style-type: disc; }
181
+ .prose ol { list-style-type: decimal; }
182
+ .prose li { margin-bottom: 0.3em; }
183
+ .prose blockquote { border-left: 2px solid var(--amber); padding-left: 1.1em; margin: 1.2em 0; color: var(--prose-subtle); font-style: italic; }
184
+ .prose code:not(pre code) { background: var(--prose-code-border); color: var(--prose-heading); padding: 0.15em 0.45em; border-radius: 4px; font-size: 0.855em; font-family: 'IBM Plex Mono', monospace; }
185
+ .prose pre { background: var(--prose-pre-bg); border: 1px solid var(--prose-code-border); border-radius: 8px; padding: 0.9em 1em; overflow-x: auto; margin: 1.2em 0; color: var(--prose-pre-color); max-width: 100%; }
186
+ @media (min-width: 640px) {
187
+ .prose pre { padding: 1.1em 1.3em; }
188
+ }
189
+ .prose pre code { background: transparent; color: inherit; padding: 0; font-size: 0.82em; font-family: 'IBM Plex Mono', monospace; word-break: break-all; }
190
+ @media (min-width: 640px) {
191
+ .prose pre code { font-size: 0.855em; word-break: normal; }
192
+ }
193
+ .prose table { width: 100%; border-collapse: collapse; margin: 1.2em 0; font-size: 0.85em; font-family: 'IBM Plex Sans', sans-serif; display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
194
+ @media (min-width: 640px) {
195
+ .prose table { font-size: 0.9em; display: table; overflow-x: visible; }
196
+ }
197
+ .prose thead th { background: var(--prose-th-bg); color: var(--prose-heading); font-weight: 600; padding: 0.6em 0.9em; border: 1px solid var(--prose-border); text-align: left; font-size: 0.78em; letter-spacing: 0.05em; text-transform: uppercase; }
198
+ .prose tbody td { padding: 0.55em 0.9em; border: 1px solid var(--prose-code-border); color: var(--prose-muted); }
199
+ .prose tbody tr:nth-child(even) { background: var(--prose-th-bg); }
200
+ .prose img { max-width: 100%; border-radius: 6px; border: 1px solid var(--prose-code-border); }
201
+ .prose hr { border: none; border-top: 1px solid var(--prose-border); margin: 2.5em 0; }
202
+
203
+ :root {
204
+ --prose-body: #3a3730;
205
+ --prose-heading: #1c1a17;
206
+ --prose-muted: #5a5750;
207
+ --prose-subtle: #7a7568;
208
+ --prose-border: #ddd9d0;
209
+ --prose-code-border: rgba(28,26,23,0.1);
210
+ --prose-th-bg: rgba(28,26,23,0.03);
211
+ --prose-pre-bg: #eae6de;
212
+ --prose-pre-color: #2a2724;
213
+ --hljs-comment: #8a8275;
214
+ --hljs-keyword: #9b4a1a;
215
+ --hljs-string: #4a7a46;
216
+ --hljs-variable: #7a6830;
217
+ --hljs-number: #2a5a8a;
218
+ --hljs-title: #6a3a8a;
219
+ --hljs-builtin: #2a6a40;
220
+ --hljs-deletion: #8a3030;
221
+ }
222
+ .dark {
223
+ --prose-body: #c8c2b8;
224
+ --prose-heading: #e8e4dc;
225
+ --prose-muted: #9a9488;
226
+ --prose-subtle: #7a7568;
227
+ --prose-border: rgba(232, 228, 220, 0.1);
228
+ --prose-code-border: rgba(232, 228, 220, 0.08);
229
+ --prose-th-bg: rgba(232, 228, 220, 0.03);
230
+ --prose-pre-bg: #0a0906;
231
+ --prose-pre-color: #c8c2b8;
232
+ --hljs-comment: #6a6560;
233
+ --hljs-keyword: #d4954a;
234
+ --hljs-string: #a5c4a0;
235
+ --hljs-variable: #d4c08a;
236
+ --hljs-number: #8ab4d8;
237
+ --hljs-title: #c8a0d8;
238
+ --hljs-builtin: #7aad80;
239
+ --hljs-deletion: #c86060;
240
+ }
241
+
242
+ .hljs { color: var(--prose-pre-color); background: var(--prose-pre-bg); }
243
+ .hljs-comment, .hljs-meta { color: var(--hljs-comment); font-style: italic; }
244
+ .hljs-keyword, .hljs-selector-tag, .hljs-tag { color: var(--hljs-keyword); }
245
+ .hljs-string, .hljs-attr { color: var(--hljs-string); }
246
+ .hljs-template-variable, .hljs-variable { color: var(--hljs-variable); }
247
+ .hljs-number, .hljs-literal, .hljs-type { color: var(--hljs-number); }
248
+ .hljs-title, .hljs-name, .hljs-selector-id, .hljs-selector-class { color: var(--hljs-title); }
249
+ .hljs-symbol, .hljs-bullet, .hljs-link { color: var(--hljs-keyword); }
250
+ .hljs-built_in, .hljs-addition { color: var(--hljs-builtin); }
251
+ .hljs-deletion { color: var(--hljs-deletion); }
252
+
253
+ @keyframes fadeSlideUp {
254
+ from { opacity: 0; transform: translateY(5px); }
255
+ to { opacity: 1; transform: translateY(0); }
256
+ }
257
+ .animate-in { animation: fadeSlideUp 0.22s ease both; }
258
+
259
+ /* Mobile bottom-sheet modal entrance */
260
+ @keyframes slideUp {
261
+ from { transform: translateY(100%); }
262
+ to { transform: translateY(0); }
263
+ }
264
+ @media (max-width: 767px) {
265
+ .modal-backdrop > [role="dialog"] {
266
+ animation: slideUp 0.3s ease-out both;
267
+ }
268
+ }
269
+
270
+ .modal-backdrop {
271
+ background: rgba(10, 9, 6, 0.72);
272
+ backdrop-filter: blur(8px);
273
+ -webkit-backdrop-filter: blur(8px);
274
+ }
275
+
276
+ /* Hide scrollbar but keep scroll functionality */
277
+ .scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
278
+ .scrollbar-none::-webkit-scrollbar { display: none; }
279
+
280
+ /* iOS safe areas */
281
+ @supports (padding: env(safe-area-inset-bottom)) {
282
+ .safe-area-bottom { padding-bottom: env(safe-area-inset-bottom); }
283
+ }
284
+
285
+ /* Tap highlight removal for mobile */
286
+ @media (hover: none) {
287
+ button, a { -webkit-tap-highlight-color: transparent; }
288
+ }
289
+
290
+ /* ─── Tiptap WYSIWYG editor ─────────────────────────────────────────── */
291
+ .wysiwyg-wrapper {
292
+ height: 100%;
293
+ cursor: text;
294
+ }
295
+
296
+ /* Tiptap uses ProseMirror internally */
297
+ .wysiwyg-editor {
298
+ min-height: 100%;
299
+ caret-color: var(--amber);
300
+ color: var(--foreground);
301
+ }
302
+
303
+ /* Placeholder */
304
+ .wysiwyg-editor p.is-editor-empty:first-child::before {
305
+ content: attr(data-placeholder);
306
+ color: var(--muted-foreground);
307
+ opacity: 0.45;
308
+ float: left;
309
+ pointer-events: none;
310
+ height: 0;
311
+ }
312
+
313
+ /* Selection */
314
+ .wysiwyg-editor ::selection {
315
+ background: var(--amber);
316
+ color: #131210;
317
+ opacity: 0.35;
318
+ }
319
+
320
+ /* Task list */
321
+ .wysiwyg-editor ul[data-type="taskList"] {
322
+ list-style: none;
323
+ padding: 0;
324
+ }
325
+ .wysiwyg-editor ul[data-type="taskList"] li {
326
+ display: flex;
327
+ align-items: baseline;
328
+ gap: 0.5rem;
329
+ }
330
+ .wysiwyg-editor ul[data-type="taskList"] li > label {
331
+ flex-shrink: 0;
332
+ margin-top: 2px;
333
+ }
334
+ .wysiwyg-editor ul[data-type="taskList"] li > label input[type="checkbox"] {
335
+ accent-color: var(--amber);
336
+ width: 14px;
337
+ height: 14px;
338
+ cursor: pointer;
339
+ }
340
+ .wysiwyg-editor ul[data-type="taskList"] li[data-checked="true"] > div {
341
+ text-decoration: line-through;
342
+ opacity: 0.5;
343
+ }
344
+
345
+ /* Tables */
346
+ .wysiwyg-editor table {
347
+ border-collapse: collapse;
348
+ width: 100%;
349
+ margin: 1.5rem 0;
350
+ }
351
+ .wysiwyg-editor th,
352
+ .wysiwyg-editor td {
353
+ border: 1px solid var(--border);
354
+ padding: 0.5rem 0.75rem;
355
+ min-width: 60px;
356
+ }
357
+ .wysiwyg-editor th {
358
+ background: var(--muted);
359
+ font-weight: 600;
360
+ }
361
+ .wysiwyg-editor .selectedCell {
362
+ background: color-mix(in srgb, var(--amber) 12%, transparent);
363
+ }
364
+
365
+ /* Focus ring on the editor area */
366
+ .wysiwyg-wrapper:focus-within {
367
+ outline: none;
368
+ }