kennel 0.2.3 → 0.2.5

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 (237) hide show
  1. package/README.md +23 -13
  2. package/dist/cli/dispatch.js +16 -3
  3. package/dist/cli/src/cli/dispatch.js +16 -3
  4. package/dist/cli/src/cli/usage.js +1 -1
  5. package/dist/cli/src/core/command.js +1 -0
  6. package/dist/cli/src/core/init.js +5 -5
  7. package/dist/cli/src/plugins/external.d.ts +28 -2
  8. package/dist/cli/src/plugins/external.js +75 -21
  9. package/dist/cli/src/site/server-plugins.d.ts +2 -7
  10. package/dist/cli/src/site/viewer.js +1 -0
  11. package/dist/cli/usage.js +1 -1
  12. package/dist/core/command.js +1 -0
  13. package/dist/core/init.js +5 -5
  14. package/dist/core/runtime.js +357 -3207
  15. package/dist/package/preview.js +1232 -5113
  16. package/dist/plugins/external.d.ts +28 -2
  17. package/dist/plugins/external.js +75 -21
  18. package/dist/site/server-plugins.d.ts +2 -7
  19. package/dist/site/server-plugins.js +552 -10447
  20. package/dist/site/viewer.js +1 -0
  21. package/kernel/apps/web/package.json +1 -5
  22. package/kernel/apps/web/scripts/dev.mjs +53 -0
  23. package/kernel/apps/web/src/app.css +0 -3
  24. package/kernel/apps/web/src/main.tsx +0 -1
  25. package/kernel/apps/web/src/plugins.ts +0 -30
  26. package/kernel/apps/web/tsconfig.json +1 -2
  27. package/kernel/apps/web/vite.config.ts +31 -13
  28. package/kernel/packages/api/package.json +0 -4
  29. package/kernel/packages/api/src/server/folder-metadata-plugin.ts +13 -38
  30. package/kernel/packages/api/src/server/index.ts +0 -1
  31. package/kernel/packages/api/src/server/plugins.ts +49 -16
  32. package/kernel/packages/api/test/core.test.ts +13 -25
  33. package/kernel/packages/markdown/README.md +1 -1
  34. package/package.json +1 -1
  35. package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
  36. package/web/assets/app-CX20nNIa.js +2 -0
  37. package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
  38. package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
  39. package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
  40. package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
  41. package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
  42. package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
  43. package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
  44. package/web/assets/channel-X7WHyxxT.js +1 -0
  45. package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
  46. package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
  47. package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
  48. package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
  49. package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
  50. package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
  51. package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
  52. package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
  53. package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
  54. package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
  55. package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
  56. package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
  57. package/web/assets/client-7OWw5kW2.js +6 -0
  58. package/web/assets/client-DYj_td4Q.js +17 -0
  59. package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
  60. package/web/assets/command-zkzH4hVz.js +38 -0
  61. package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
  62. package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
  63. package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
  64. package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
  65. package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
  66. package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
  67. package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
  68. package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
  69. package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
  70. package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
  71. package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
  72. package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
  73. package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
  74. package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
  75. package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
  76. package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
  77. package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
  78. package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
  79. package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
  80. package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
  81. package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
  82. package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
  83. package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
  84. package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
  85. package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
  86. package/web/assets/index-CegvVQ1C.js +1 -0
  87. package/web/assets/index-Dwhf8F_U.css +1 -0
  88. package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
  89. package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
  90. package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
  91. package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
  92. package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
  93. package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
  94. package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
  95. package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
  96. package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
  97. package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
  98. package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
  99. package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
  100. package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
  101. package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
  102. package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
  103. package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
  104. package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
  105. package/web/assets/select-CtXC_PLC.js +30 -0
  106. package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
  107. package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
  108. package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
  109. package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
  110. package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
  111. package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
  112. package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
  113. package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
  114. package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
  115. package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
  116. package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
  117. package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
  118. package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
  119. package/web/index.html +2 -2
  120. package/kernel/packages/api/test/partial-loading.test.ts +0 -43
  121. package/kernel/plugins/calendar/SKILL.md +0 -173
  122. package/kernel/plugins/calendar/eslint.config.js +0 -10
  123. package/kernel/plugins/calendar/package.json +0 -73
  124. package/kernel/plugins/calendar/schema.json +0 -137
  125. package/kernel/plugins/calendar/src/calendar.tsx +0 -328
  126. package/kernel/plugins/calendar/src/client.tsx +0 -139
  127. package/kernel/plugins/calendar/src/context.ts +0 -48
  128. package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
  129. package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
  130. package/kernel/plugins/calendar/src/index.ts +0 -4
  131. package/kernel/plugins/calendar/src/manifest.ts +0 -7
  132. package/kernel/plugins/calendar/src/parse.ts +0 -610
  133. package/kernel/plugins/calendar/src/server.ts +0 -160
  134. package/kernel/plugins/calendar/src/utils.ts +0 -80
  135. package/kernel/plugins/calendar/src/views.tsx +0 -418
  136. package/kernel/plugins/calendar/test/context.test.mjs +0 -60
  137. package/kernel/plugins/calendar/test/package.test.mjs +0 -43
  138. package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
  139. package/kernel/plugins/calendar/test/server.test.mjs +0 -126
  140. package/kernel/plugins/calendar/tsconfig.json +0 -17
  141. package/kernel/plugins/mail/SKILL.md +0 -97
  142. package/kernel/plugins/mail/eslint.config.js +0 -10
  143. package/kernel/plugins/mail/package.json +0 -79
  144. package/kernel/plugins/mail/src/api.ts +0 -359
  145. package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
  146. package/kernel/plugins/mail/src/client.tsx +0 -14
  147. package/kernel/plugins/mail/src/flock.ts +0 -137
  148. package/kernel/plugins/mail/src/folders.ts +0 -37
  149. package/kernel/plugins/mail/src/history.ts +0 -485
  150. package/kernel/plugins/mail/src/index.ts +0 -3
  151. package/kernel/plugins/mail/src/mail-card.tsx +0 -123
  152. package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
  153. package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
  154. package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
  155. package/kernel/plugins/mail/src/mail-page.tsx +0 -109
  156. package/kernel/plugins/mail/src/mail-request.ts +0 -78
  157. package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
  158. package/kernel/plugins/mail/src/mailos.ts +0 -723
  159. package/kernel/plugins/mail/src/manifest.ts +0 -7
  160. package/kernel/plugins/mail/src/node.ts +0 -171
  161. package/kernel/plugins/mail/src/parse.ts +0 -139
  162. package/kernel/plugins/mail/src/server.ts +0 -125
  163. package/kernel/plugins/mail/src/types.ts +0 -10
  164. package/kernel/plugins/mail/src/viewer.tsx +0 -147
  165. package/kernel/plugins/mail/test/flock.test.mjs +0 -156
  166. package/kernel/plugins/mail/test/folders.test.mjs +0 -138
  167. package/kernel/plugins/mail/test/history.test.mjs +0 -460
  168. package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
  169. package/kernel/plugins/mail/test/node.test.mjs +0 -207
  170. package/kernel/plugins/mail/test/package.test.mjs +0 -54
  171. package/kernel/plugins/mail/test/parse.test.mjs +0 -32
  172. package/kernel/plugins/mail/test/server.test.mjs +0 -116
  173. package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
  174. package/kernel/plugins/mail/tsconfig.json +0 -17
  175. package/kernel/plugins/projects/SKILL.md +0 -73
  176. package/kernel/plugins/projects/eslint.config.js +0 -10
  177. package/kernel/plugins/projects/package.json +0 -70
  178. package/kernel/plugins/projects/src/client.tsx +0 -17
  179. package/kernel/plugins/projects/src/data/model.ts +0 -129
  180. package/kernel/plugins/projects/src/index.ts +0 -17
  181. package/kernel/plugins/projects/src/manifest.ts +0 -7
  182. package/kernel/plugins/projects/src/project-context.ts +0 -41
  183. package/kernel/plugins/projects/src/server.ts +0 -276
  184. package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
  185. package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
  186. package/kernel/plugins/projects/test/package.test.mjs +0 -51
  187. package/kernel/plugins/projects/tsconfig.json +0 -17
  188. package/kernel/plugins/tasks/SKILL.md +0 -341
  189. package/kernel/plugins/tasks/eslint.config.js +0 -10
  190. package/kernel/plugins/tasks/package.json +0 -74
  191. package/kernel/plugins/tasks/schema.json +0 -939
  192. package/kernel/plugins/tasks/src/api.ts +0 -212
  193. package/kernel/plugins/tasks/src/client.tsx +0 -23
  194. package/kernel/plugins/tasks/src/data/parse.ts +0 -544
  195. package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
  196. package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
  197. package/kernel/plugins/tasks/src/index.ts +0 -34
  198. package/kernel/plugins/tasks/src/manifest.ts +0 -7
  199. package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
  200. package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
  201. package/kernel/plugins/tasks/src/server.ts +0 -447
  202. package/kernel/plugins/tasks/src/styles.css +0 -9
  203. package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
  204. package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
  205. package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
  206. package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
  207. package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
  208. package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
  209. package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
  210. package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
  211. package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
  212. package/kernel/plugins/tasks/test/api.test.mjs +0 -99
  213. package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
  214. package/kernel/plugins/tasks/test/package.test.mjs +0 -54
  215. package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
  216. package/kernel/plugins/tasks/test/server.test.mjs +0 -386
  217. package/kernel/plugins/tasks/tsconfig.json +0 -17
  218. package/web/assets/app-DoGr2Zi7.js +0 -2
  219. package/web/assets/channel-uMEjV8qc.js +0 -1
  220. package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
  221. package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
  222. package/web/assets/client-C6Nxdfif.js +0 -2
  223. package/web/assets/client-CXYfBko5.js +0 -17
  224. package/web/assets/client-CoKtOq-X.js +0 -7
  225. package/web/assets/client-DyRQz5RU.js +0 -53
  226. package/web/assets/client-OY6HUzuD.js +0 -98
  227. package/web/assets/client-YOYWZIFi.js +0 -34
  228. package/web/assets/command-BugZBGXE.js +0 -38
  229. package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
  230. package/web/assets/index-BaRnbYRw.js +0 -1
  231. package/web/assets/index-BexmLxIP.css +0 -1
  232. package/web/assets/index-DDirZMgx.js +0 -1
  233. package/web/assets/note-card-DBfX6gW1.js +0 -6
  234. package/web/assets/select-D8cqNgcp.js +0 -30
  235. package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
  236. package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
  237. package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
@@ -1,109 +0,0 @@
1
- import { RefreshCw } from "lucide-react"
2
-
3
- import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
4
- import { folderLabel } from "@raggle-ai/mail/folders"
5
- import {
6
- MailFolderFilter,
7
- mailItemContext,
8
- MailList,
9
- } from "@raggle-ai/mail/viewer"
10
- import { CopyContextButton } from "@raggle-ai/ui/components/copy-context"
11
- import { useFolderFilter } from "@raggle-ai/ui/hooks/use-folder-filter"
12
-
13
- import { useMailList } from "./mail-hooks.js"
14
- import { mailErrorText } from "./mail-request.js"
15
-
16
- export function MailPage({
17
- folder,
18
- href,
19
- }: Pick<FolderPluginViewProps, "folder" | "href">) {
20
- const query = useMailList(folder)
21
-
22
- const items = query.data?.items ?? []
23
- const hasItems = Boolean(query.data?.items.length)
24
- // Shared folder-filter state: derives the chips from the drafts' owning
25
- // folders and filters the list. A selection whose folder no longer has
26
- // drafts falls back to All folders rather than showing a dead-end list.
27
- const { choices, effectiveSelected, setSelected, total, visibleItems } =
28
- useFolderFilter(items, {
29
- getFolder: (item) => item.folder,
30
- rootFolder: folder ?? "",
31
- })
32
-
33
- if (!folder) {
34
- return (
35
- <div
36
- className="mx-auto grid w-full max-w-3xl min-w-0 grid-cols-1 gap-4 px-4
37
- py-5"
38
- >
39
- <p className="m-0 text-sm text-[#6c7a6d]">Open mail from a folder.</p>
40
- </div>
41
- )
42
- }
43
-
44
- return (
45
- <div
46
- className="mx-auto grid w-full max-w-3xl min-w-0 grid-cols-1 gap-4 px-4
47
- py-5"
48
- >
49
- <div className="flex flex-wrap items-center justify-between gap-3">
50
- <h1 className="m-0 text-2xl font-semibold text-[#233025]">Mail</h1>
51
- <button
52
- type="button"
53
- className="inline-flex items-center gap-2 rounded-md border px-3
54
- py-1.5 text-sm disabled:pointer-events-none disabled:opacity-50"
55
- onClick={() => void query.refetch()}
56
- disabled={query.isFetching}
57
- >
58
- <RefreshCw
59
- className={query.isFetching ? "size-4 animate-spin" : "size-4"}
60
- />
61
- Refresh
62
- </button>
63
- </div>
64
-
65
- {hasItems ? (
66
- <MailFolderFilter
67
- choices={choices}
68
- onSelect={setSelected}
69
- selected={effectiveSelected}
70
- total={total}
71
- />
72
- ) : null}
73
-
74
- {query.isPending ? (
75
- <p className="m-0 text-sm text-[#6c7a6d]">Loading…</p>
76
- ) : null}
77
- {query.error ? (
78
- <p className="m-0 text-sm text-[#9c3a3a]">
79
- {mailErrorText(query.error)}
80
- </p>
81
- ) : null}
82
-
83
- {!query.isPending && !query.error && !query.data?.items.length ? (
84
- <p className="m-0 text-sm text-[#6c7a6d]">No mail found.</p>
85
- ) : null}
86
-
87
- {hasItems ? (
88
- visibleItems.length ? (
89
- <MailList
90
- actions={(item) => (
91
- <CopyContextButton
92
- className="text-[#6c7a6d] hover:text-[#2f382f]"
93
- context={() => mailItemContext(item)}
94
- label={`Copy context for ${item.subject ?? "this draft"}`}
95
- />
96
- )}
97
- folderLabel={(mailFolder) => folderLabel(mailFolder, folder)}
98
- items={visibleItems}
99
- mailHref={href}
100
- />
101
- ) : (
102
- <p className="m-0 text-sm text-[#6c7a6d]">
103
- No drafts in this folder.
104
- </p>
105
- )
106
- ) : null}
107
- </div>
108
- )
109
- }
@@ -1,78 +0,0 @@
1
- export type MailRequestOptions = {
2
- body?: unknown
3
- method?: "GET" | "POST" | "DELETE"
4
- search?: Record<string, string | undefined>
5
- signal?: AbortSignal
6
- }
7
-
8
- export class MailRequestFailure extends Error {
9
- readonly status: number
10
-
11
- constructor(status: number, message: string) {
12
- super(message)
13
- this.name = "MailRequestFailure"
14
- this.status = status
15
- }
16
- }
17
-
18
- function structuredMessage(text: string) {
19
- try {
20
- const parsed = JSON.parse(text) as {
21
- error?: { code?: string; message?: string }
22
- }
23
- if (parsed.error?.message) return parsed.error.message
24
- } catch {
25
- // Non-JSON error bodies fall back to the raw text.
26
- }
27
- return text.trim() || "Mail request failed."
28
- }
29
-
30
- /**
31
- * Shared typed request helper for the mail API. GET requests never carry a
32
- * body (the server forbids mutations over GET); mutations send JSON which the
33
- * server's same-origin guard accepts only for same-site pages. JSON responses
34
- * are enforced and structured server errors become readable messages.
35
- */
36
- export async function mailRequest<T>(
37
- path: string,
38
- options: MailRequestOptions = {},
39
- ): Promise<T> {
40
- const search = new URLSearchParams()
41
- for (const [key, value] of Object.entries(options.search ?? {})) {
42
- if (value !== undefined) search.set(key, value)
43
- }
44
- const query = search.toString()
45
- const url = `${path}${query ? `?${query}` : ""}`
46
-
47
- const headers: Record<string, string> = {}
48
- let body: string | undefined
49
- if (options.body !== undefined) {
50
- headers["content-type"] = "application/json"
51
- body = JSON.stringify(options.body)
52
- }
53
-
54
- const response = await fetch(url, {
55
- body,
56
- headers,
57
- method: options.method ?? "GET",
58
- signal: options.signal,
59
- })
60
-
61
- const text = await response.text()
62
- if (!response.ok) {
63
- throw new MailRequestFailure(response.status, structuredMessage(text))
64
- }
65
- if (!response.headers.get("content-type")?.includes("application/json")) {
66
- throw new MailRequestFailure(
67
- response.status,
68
- "The server returned a non-JSON response.",
69
- )
70
- }
71
- return JSON.parse(text) as T
72
- }
73
-
74
- export function mailErrorText(error: unknown) {
75
- if (error instanceof MailRequestFailure) return error.message
76
- if (error instanceof Error) return error.message
77
- return "Mail request failed."
78
- }
@@ -1,175 +0,0 @@
1
- import { useEffect, useState } from "react"
2
- import { CheckCircle2, Loader2, Send, XCircle } from "lucide-react"
3
-
4
- import type { MailSendPreviewPayload } from "./mail-hooks.js"
5
- import { useMailSend } from "./mail-hooks.js"
6
- import { mailErrorText } from "./mail-request.js"
7
-
8
- type SendPhase =
9
- "idle" | "previewing" | "confirm" | "sending" | "sent" | "failed"
10
-
11
- /**
12
- * The preview-then-confirm send flow for one draft. State resets when the
13
- * active draft changes so navigation never carries a stale preview, and every
14
- * action disables the buttons while a request is in flight (double
15
- * submissions cannot start a send twice).
16
- */
17
- export function MailSendBox({
18
- folder,
19
- mailId,
20
- status,
21
- }: {
22
- folder: string | null | undefined
23
- mailId: string | null | undefined
24
- status: string | null | undefined
25
- }) {
26
- const [phase, setPhase] = useState<SendPhase>("idle")
27
- const [preview, setPreview] = useState<MailSendPreviewPayload | null>(null)
28
- const [error, setError] = useState<string | null>(null)
29
- const send = useMailSend(folder)
30
-
31
- useEffect(() => {
32
- setPhase("idle")
33
- setPreview(null)
34
- setError(null)
35
- }, [mailId])
36
-
37
- const pending =
38
- send.isPending || phase === "previewing" || phase === "sending"
39
-
40
- async function runPreview() {
41
- if (!mailId || pending) return
42
- setError(null)
43
- setPhase("previewing")
44
- try {
45
- const result = await send.mutateAsync({ dryRun: true, id: mailId })
46
- if (!("revision" in result)) {
47
- throw new Error("MailOS did not return a revision.")
48
- }
49
- setPreview(result)
50
- setPhase("confirm")
51
- } catch (cause) {
52
- setError(mailErrorText(cause))
53
- setPhase("failed")
54
- }
55
- }
56
-
57
- async function runSend() {
58
- if (!mailId || !preview || pending) return
59
- setError(null)
60
- setPhase("sending")
61
- try {
62
- await send.mutateAsync({
63
- dryRun: false,
64
- id: mailId,
65
- revision: preview.revision,
66
- })
67
- setPhase("sent")
68
- } catch (cause) {
69
- setError(mailErrorText(cause))
70
- setPhase("failed")
71
- }
72
- }
73
-
74
- return (
75
- <div
76
- className="rounded-xl border border-[#e7ebe7] bg-white px-4 py-3
77
- shadow-[0_1px_2px_rgb(31_41_55/0.05)]"
78
- >
79
- {phase === "idle" ? (
80
- <div className="flex flex-wrap items-center gap-3">
81
- <button
82
- type="button"
83
- className="inline-flex w-fit items-center gap-2 rounded-md
84
- bg-[#395641] px-3 py-1.5 text-sm text-white hover:bg-[#2f4b38]
85
- disabled:pointer-events-none disabled:opacity-50"
86
- onClick={() => void runPreview()}
87
- disabled={status === "sent" || pending}
88
- >
89
- <Send className="size-4" />
90
- {status === "sent" ? "Already sent" : "Send"}
91
- </button>
92
- <span className="text-[0.82rem] text-[#6c7a6d]">
93
- Will preview for confirmation before delivering.
94
- </span>
95
- </div>
96
- ) : null}
97
-
98
- {phase === "previewing" || phase === "sending" ? (
99
- <p className="m-0 flex items-center gap-2 text-sm text-[#4a554b]">
100
- <Loader2 className="size-4 animate-spin" />
101
- {phase === "previewing" ? "Previewing…" : "Sending…"}
102
- </p>
103
- ) : null}
104
-
105
- {phase === "confirm" && preview ? (
106
- <div className="grid gap-3">
107
- <p className="m-0 text-sm text-[#4a554b]">
108
- Ready to send this draft to its recipients.
109
- </p>
110
- <div className="flex flex-wrap items-center gap-2">
111
- <button
112
- type="button"
113
- className="inline-flex w-fit items-center gap-2 rounded-md
114
- bg-[#395641] px-3 py-1.5 text-sm text-white hover:bg-[#2f4b38]
115
- disabled:pointer-events-none disabled:opacity-50"
116
- onClick={() => void runSend()}
117
- disabled={pending}
118
- >
119
- <Send className="size-4" />
120
- Send now
121
- </button>
122
- <button
123
- type="button"
124
- className="w-fit rounded-md border border-[#dde1dc] bg-white px-3
125
- py-1.5 text-sm text-[#4a554b] hover:bg-[#f3f5f2]
126
- disabled:pointer-events-none disabled:opacity-50"
127
- onClick={() => {
128
- setPhase("idle")
129
- setPreview(null)
130
- }}
131
- disabled={pending}
132
- >
133
- Cancel
134
- </button>
135
- </div>
136
- <details className="text-[0.82rem] text-[#6c7a6d]">
137
- <summary className="cursor-pointer">Preview output</summary>
138
- <pre
139
- className="mt-2 max-h-48 overflow-auto rounded-md bg-[#f3f5f2] p-2
140
- text-[0.75rem] whitespace-pre-wrap text-[#4a554b]"
141
- >
142
- {preview.output}
143
- </pre>
144
- </details>
145
- </div>
146
- ) : null}
147
-
148
- {phase === "sent" ? (
149
- <p className="m-0 flex items-center gap-2 text-sm text-[#2f5b3a]">
150
- <CheckCircle2 className="size-4" />
151
- Sent.
152
- </p>
153
- ) : null}
154
-
155
- {phase === "failed" ? (
156
- <div className="grid gap-3">
157
- <p className="m-0 flex items-center gap-2 text-sm text-[#9c3a3a]">
158
- <XCircle className="size-4 shrink-0" />
159
- <span className="break-words">{error}</span>
160
- </p>
161
- <button
162
- type="button"
163
- className="inline-flex w-fit items-center gap-2 rounded-md border
164
- border-[#dde1dc] bg-white px-3 py-1.5 text-sm text-[#4a554b]
165
- hover:bg-[#f3f5f2]"
166
- onClick={() => void runPreview()}
167
- >
168
- <Send className="size-4" />
169
- Try again
170
- </button>
171
- </div>
172
- ) : null}
173
- </div>
174
- )
175
- }