kennel 0.2.2 → 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 +29 -3
  8. package/dist/cli/src/plugins/external.js +78 -24
  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 +29 -3
  17. package/dist/plugins/external.js +78 -24
  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,328 +0,0 @@
1
- import { useState } from "react"
2
- import {
3
- addDays,
4
- addMonths,
5
- addWeeks,
6
- endOfWeek,
7
- format,
8
- isSameMonth,
9
- startOfDay,
10
- startOfMonth,
11
- startOfWeek,
12
- } from "date-fns"
13
- import {
14
- CalendarDays,
15
- Check,
16
- ChevronLeft,
17
- ChevronRight,
18
- Loader2,
19
- } from "lucide-react"
20
-
21
- import type { CalendarColor, CalendarEvent } from "./parse"
22
- import { dayContext } from "./context"
23
- import { CopyButton } from "./copy-button"
24
- import { eventsInRange, resolveColors } from "./parse"
25
- import { calendarTone, cn } from "./utils"
26
- import { DayView, MonthView, WeekView } from "./views"
27
-
28
- type View = "month" | "week" | "day"
29
-
30
- const weekStart = { weekStartsOn: 1 as const }
31
-
32
- function rangeLabel(view: View, date: Date) {
33
- if (view === "day") return format(date, "EEEE, MMM d, yyyy")
34
- if (view === "week") {
35
- const start = startOfWeek(date, weekStart)
36
- const end = endOfWeek(date, weekStart)
37
- return isSameMonth(start, end)
38
- ? `${format(start, "MMM d")} - ${format(end, "d, yyyy")}`
39
- : `${format(start, "MMM d")} - ${format(end, "MMM d, yyyy")}`
40
- }
41
- return format(date, "MMMM yyyy")
42
- }
43
-
44
- function Header({
45
- date,
46
- onNavigate,
47
- onToday,
48
- onView,
49
- view,
50
- }: {
51
- date: Date
52
- onNavigate: (direction: -1 | 1) => void
53
- onToday: () => void
54
- onView: (view: View) => void
55
- view: View
56
- }) {
57
- return (
58
- <div className="flex flex-wrap items-center gap-2 border-b px-3 py-2">
59
- <button
60
- className="hover:bg-accent h-8 rounded-md border px-3 text-sm"
61
- onClick={onToday}
62
- type="button"
63
- >
64
- Today
65
- </button>
66
- <div className="flex items-center gap-0.5">
67
- <button
68
- aria-label="Previous"
69
- className="hover:bg-accent flex size-8 items-center justify-center
70
- rounded-md"
71
- onClick={() => onNavigate(-1)}
72
- type="button"
73
- >
74
- <ChevronLeft aria-hidden="true" className="size-4" />
75
- </button>
76
- <button
77
- aria-label="Next"
78
- className="hover:bg-accent flex size-8 items-center justify-center
79
- rounded-md"
80
- onClick={() => onNavigate(1)}
81
- type="button"
82
- >
83
- <ChevronRight aria-hidden="true" className="size-4" />
84
- </button>
85
- </div>
86
- <span className="min-w-0 flex-1 truncate text-sm font-medium">
87
- {rangeLabel(view, date)}
88
- </span>
89
- <div
90
- aria-label="Calendar view"
91
- className="bg-muted flex shrink-0 rounded-md p-1"
92
- role="tablist"
93
- >
94
- {(["month", "week", "day"] as const).map((item) => (
95
- <button
96
- aria-selected={view === item}
97
- className={cn(
98
- "rounded px-2 py-1 text-xs capitalize",
99
- view === item && "bg-background shadow-sm",
100
- )}
101
- key={item}
102
- onClick={() => onView(item)}
103
- role="tab"
104
- type="button"
105
- >
106
- {item}
107
- </button>
108
- ))}
109
- </div>
110
- </div>
111
- )
112
- }
113
-
114
- function anchorDate(initialDate?: Date | string) {
115
- if (!initialDate) return new Date()
116
- const anchor = new Date(initialDate)
117
- return Number.isNaN(anchor.getTime()) ? new Date() : anchor
118
- }
119
-
120
- function visible(events: CalendarEvent[], view: View, date: Date) {
121
- if (view === "month") {
122
- const start = startOfWeek(startOfMonth(date), weekStart)
123
- return eventsInRange(events, start, addDays(start, 42))
124
- }
125
- if (view === "week") {
126
- const start = startOfWeek(date, weekStart)
127
- return eventsInRange(events, start, addDays(start, 7))
128
- }
129
- const start = startOfDay(date)
130
- return eventsInRange(events, start, addDays(start, 1))
131
- }
132
-
133
- function EmptyView() {
134
- return (
135
- <div
136
- className="flex flex-col items-center justify-center gap-2 px-6 py-16
137
- text-center"
138
- >
139
- <CalendarDays
140
- aria-hidden="true"
141
- className="text-muted-foreground size-8"
142
- />
143
- <p className="text-muted-foreground text-sm">No events in this view.</p>
144
- </div>
145
- )
146
- }
147
-
148
- function LoadingView() {
149
- return (
150
- <div
151
- aria-label="Loading calendar"
152
- className="flex min-h-80 items-center justify-center"
153
- >
154
- <Loader2
155
- aria-label="Loading"
156
- className="text-muted-foreground size-6 animate-spin"
157
- role="status"
158
- />
159
- </div>
160
- )
161
- }
162
-
163
- function GroupFilter({
164
- active,
165
- groups,
166
- onChange,
167
- }: {
168
- active?: string
169
- groups: Array<{ color?: CalendarColor; name: string }>
170
- onChange: (group?: string) => void
171
- }) {
172
- if (groups.length < 2) return null
173
-
174
- return (
175
- <div
176
- aria-label="Calendar groups"
177
- className="flex min-w-0 flex-1 gap-1 overflow-x-auto"
178
- >
179
- <button
180
- aria-pressed={!active}
181
- className={cn(
182
- `focus-visible:ring-ring flex shrink-0 items-center gap-1.5
183
- rounded-full border px-2.5 py-1 text-xs font-medium transition-colors
184
- focus-visible:ring-2 focus-visible:outline-none`,
185
- active
186
- ? "bg-background hover:bg-muted"
187
- : `border-slate-900 bg-slate-900 !text-white shadow-sm
188
- dark:border-slate-100 dark:bg-slate-100 dark:!text-slate-900`,
189
- )}
190
- onClick={() => onChange(undefined)}
191
- type="button"
192
- >
193
- {!active ? <Check aria-hidden="true" className="size-3" /> : null}
194
- All
195
- </button>
196
- {groups.map((group) => {
197
- const selected = active === group.name
198
- const tone = calendarTone(group.color)
199
- return (
200
- <button
201
- aria-pressed={selected}
202
- className={cn(
203
- `focus-visible:ring-ring flex shrink-0 items-center gap-1.5
204
- rounded-full border px-2.5 py-1 text-xs font-medium
205
- transition-colors focus-visible:ring-2 focus-visible:outline-none`,
206
- selected ? tone.selected : tone.soft,
207
- )}
208
- key={group.name}
209
- onClick={() => onChange(selected ? undefined : group.name)}
210
- type="button"
211
- >
212
- {selected ? (
213
- <Check aria-hidden="true" className="size-3" />
214
- ) : (
215
- <span
216
- aria-hidden="true"
217
- className={cn("size-2 rounded-full", tone.dot)}
218
- />
219
- )}
220
- {group.name}
221
- </button>
222
- )
223
- })}
224
- </div>
225
- )
226
- }
227
-
228
- export function Calendar({
229
- events,
230
- className,
231
- initialDate,
232
- initialView = "month",
233
- loading = false,
234
- }: {
235
- events: CalendarEvent[]
236
- className?: string
237
- initialDate?: Date | string
238
- initialView?: View
239
- loading?: boolean
240
- }) {
241
- const [date, setDate] = useState(() => anchorDate(initialDate))
242
- const [view, setView] = useState<View>(initialView)
243
- const [group, setGroup] = useState<string>()
244
-
245
- const navigate = (direction: -1 | 1) => {
246
- setDate((current) => {
247
- if (view === "month") return addMonths(current, direction)
248
- if (view === "week") return addWeeks(current, direction)
249
- return addDays(current, direction)
250
- })
251
- }
252
-
253
- const resolved = resolveColors(events)
254
- const selectGroup = (next?: string) => {
255
- setGroup(next)
256
- if (!next) return
257
- const first = resolved
258
- .filter((event) => event.group === next)
259
- .map((event) => event.start)
260
- .sort((left, right) => left.getTime() - right.getTime())[0]
261
- if (first) setDate(startOfDay(first))
262
- }
263
-
264
- const groupMap = new Map<string, CalendarColor | undefined>()
265
- const firstStart = new Map<string, number>()
266
- for (const event of resolved) {
267
- if (!event.group || groupMap.has(event.group)) continue
268
- groupMap.set(event.group, event.color)
269
- firstStart.set(event.group, event.start.getTime())
270
- }
271
- // Chips follow the trip order: each group's earliest event date first.
272
- const groups = [...groupMap]
273
- .map(([name, color]) => ({ color, name }))
274
- .sort(
275
- (left, right) =>
276
- (firstStart.get(left.name) ?? 0) - (firstStart.get(right.name) ?? 0) ||
277
- left.name.localeCompare(right.name),
278
- )
279
- const filtered = group
280
- ? resolved.filter((event) => event.group === group)
281
- : resolved
282
- const shown = visible(filtered, view, date)
283
-
284
- return (
285
- <div
286
- className={cn(
287
- "calendar-view overflow-hidden rounded-xl border",
288
- className,
289
- )}
290
- >
291
- <Header
292
- date={date}
293
- onNavigate={navigate}
294
- onToday={() => setDate(new Date())}
295
- onView={setView}
296
- view={view}
297
- />
298
- <div className="flex items-center gap-2 border-b px-3 py-2">
299
- {view === "day" ? (
300
- <CopyButton
301
- iconOnly
302
- label="Copy day context"
303
- value={dayContext(date, shown)}
304
- />
305
- ) : null}
306
- <GroupFilter active={group} groups={groups} onChange={selectGroup} />
307
- <span className="ml-auto rounded-md border px-2 py-0.5 text-xs">
308
- {filtered.length}
309
- {group ? ` of ${events.length}` : ""}{" "}
310
- {filtered.length === 1 ? "event" : "events"}
311
- </span>
312
- </div>
313
- {loading ? (
314
- <LoadingView />
315
- ) : filtered.length === 0 || shown.length === 0 ? (
316
- <EmptyView />
317
- ) : view === "month" ? (
318
- <MonthView date={date} events={shown} />
319
- ) : view === "week" ? (
320
- <WeekView date={date} events={shown} />
321
- ) : (
322
- <DayView date={date} events={shown} />
323
- )}
324
- </div>
325
- )
326
- }
327
-
328
- export type { View as CalendarView }
@@ -1,139 +0,0 @@
1
- import { useMemo } from "react"
2
- import { useQuery } from "@tanstack/react-query"
3
-
4
- import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
5
- import { FolderFilterBar } from "@raggle-ai/ui/components/folder-filter-bar"
6
- import { useFolderFilter } from "@raggle-ai/ui/hooks/use-folder-filter"
7
-
8
- import { Calendar } from "./calendar"
9
- import { calendarManifest } from "./manifest"
10
- import { parseEvents } from "./parse"
11
-
12
- // Structurally identical to `ContentIssue` from `@raggle-ai/folder-plugin`;
13
- // kept local so the calendar package stays free of ui/library deps.
14
- export type CalendarIssue = {
15
- path: string
16
- message: string
17
- }
18
-
19
- export type CalendarPluginData = {
20
- calendar?: {
21
- count: number
22
- items: Array<Record<string, unknown>>
23
- startDate?: string
24
- title?: string
25
- }
26
- folder: string
27
- issues?: CalendarIssue[]
28
- status?: "available" | "error" | "unavailable"
29
- }
30
-
31
- export function CalendarIssues({ issues }: { issues: CalendarIssue[] }) {
32
- return (
33
- <div className="rounded-md border border-[#9c3a3a]/40 p-4">
34
- <p className="m-0 text-sm font-semibold text-[#9c3a3a]">
35
- calendar.json is not valid.
36
- </p>
37
- <ul className="m-0 mt-2 list-disc space-y-1 pl-5 text-sm text-[#9c3a3a]">
38
- {issues.map((issue, index) => (
39
- <li key={index}>
40
- <code className="text-[13px]">{issue.path}</code>: {issue.message}
41
- </li>
42
- ))}
43
- </ul>
44
- </div>
45
- )
46
- }
47
-
48
- export function CalendarPlugin({
49
- folder,
50
- folderId,
51
- request,
52
- }: FolderPluginViewProps) {
53
- const query = useQuery({
54
- enabled: Boolean(folder),
55
- gcTime: 5 * 60_000,
56
- queryFn: () => request(""),
57
- queryKey: ["folder", folderId, "calendar", ""],
58
- staleTime: 15_000,
59
- })
60
- const data = query.data as CalendarPluginData | undefined
61
- const available = data?.status === "available"
62
- const events = useMemo(
63
- () => parseEvents(data?.calendar?.items ?? []),
64
- [data?.calendar?.items],
65
- )
66
- // Shared folder filter over the event's owning `calendar.json` directory:
67
- // a workspace root merges nested calendars, so chips narrow to folders.
68
- const filter = useFolderFilter(events, {
69
- getFolder: (event) => event.folder ?? null,
70
- rootFolder: folder,
71
- })
72
-
73
- // Opening date: the calendar's own `startDate` when it sets one, otherwise
74
- // today when it falls inside the event range, else the first event date so
75
- // a calendar far from today opens on its content, not an empty view.
76
- const initialDate = useMemo(() => {
77
- const anchor = data?.calendar?.startDate
78
- if (anchor) {
79
- const date = new Date(anchor)
80
- if (!Number.isNaN(date.getTime())) return date
81
- }
82
- if (!events.length) return undefined
83
- const now = new Date()
84
- const first = Math.min(...events.map((event) => event.start.getTime()))
85
- const last = Math.max(...events.map((event) => event.end.getTime()))
86
- return now.getTime() >= first && now.getTime() <= last
87
- ? now
88
- : new Date(first)
89
- }, [data?.calendar?.startDate, events])
90
-
91
- return (
92
- <div
93
- className="mx-auto grid w-full max-w-[1100px] min-w-0 grid-cols-1 gap-4
94
- px-4 py-5"
95
- >
96
- {query.isPending ? (
97
- <p className="text-muted-foreground m-0 text-sm">Loading calendar…</p>
98
- ) : null}
99
- {query.error ? (
100
- <p className="m-0 text-sm text-[#9c3a3a]">Could not load calendar.</p>
101
- ) : null}
102
- {!query.error && data?.status === "error" && data.issues?.length ? (
103
- <CalendarIssues issues={data.issues} />
104
- ) : null}
105
- {!query.isPending && !query.error && data && !available ? (
106
- <p className="text-muted-foreground m-0 text-sm">
107
- No calendar found in this folder.
108
- </p>
109
- ) : null}
110
- {available && data?.calendar ? (
111
- <>
112
- <h1 className="m-0 text-xl font-semibold">
113
- {data.calendar.title ?? "Calendar"}
114
- </h1>
115
- {filter.choices.length > 1 ? (
116
- <FolderFilterBar
117
- ariaLabel="Filter by folder"
118
- choices={filter.choices}
119
- onSelect={filter.setSelected}
120
- selected={filter.effectiveSelected}
121
- total={filter.total}
122
- />
123
- ) : null}
124
- <Calendar
125
- className="bg-background"
126
- events={filter.visibleItems}
127
- initialDate={initialDate}
128
- loading={query.isPending}
129
- />
130
- </>
131
- ) : null}
132
- </div>
133
- )
134
- }
135
-
136
- export const calendarClient = {
137
- ...calendarManifest,
138
- component: CalendarPlugin,
139
- }
@@ -1,48 +0,0 @@
1
- import { format } from "date-fns"
2
-
3
- import type { CalendarEvent } from "./parse"
4
- import { eventSpan } from "./utils"
5
-
6
- export function eventContext(event: CalendarEvent) {
7
- const lines = [
8
- `## ${event.title}`,
9
- "",
10
- `- ID: ${event.id}`,
11
- `- Start: ${event.start.toISOString()}`,
12
- `- End: ${event.end.toISOString()}`,
13
- `- All day: ${eventSpan(event).allDay ? "Yes" : "No"}`,
14
- event.group ? `- Group: ${event.group}` : null,
15
- event.status ? `- Status: ${event.status}` : null,
16
- event.location ? `- Location: ${event.location}` : null,
17
- event.organizer ? `- Organizer: ${event.organizer}` : null,
18
- event.attendees?.length
19
- ? `- Attendees: ${event.attendees.join(", ")}`
20
- : null,
21
- event.repeating ? `- Repeats: ${event.repeating}` : null,
22
- event.repeatUntil
23
- ? `- Repeat until: ${event.repeatUntil.toISOString()}`
24
- : null,
25
- event.excludeDates?.length
26
- ? `- Excluded dates: ${event.excludeDates.map((date) => date.toISOString()).join(", ")}`
27
- : null,
28
- event.reminders?.length
29
- ? `- Reminders: ${event.reminders.map((minutes) => (minutes === 0 ? "at start" : `${minutes} minutes before`)).join(", ")}`
30
- : null,
31
- event.tags?.length ? `- Tags: ${event.tags.join(", ")}` : null,
32
- event.color ? `- Color: ${event.color}` : null,
33
- event.url ? `- URL: ${event.url}` : null,
34
- event.description ? "" : null,
35
- event.description ?? null,
36
- ]
37
-
38
- return lines.filter((line): line is string => line !== null).join("\n")
39
- }
40
-
41
- export function dayContext(date: Date, events: CalendarEvent[]) {
42
- const header = `# Calendar context for ${format(date, "EEEE, MMMM d, yyyy")}`
43
- if (!events.length) return `${header}\n\nNo events.`
44
- return `${header}\n\n${[...events]
45
- .sort((left, right) => left.start.getTime() - right.start.getTime())
46
- .map(eventContext)
47
- .join("\n\n")}`
48
- }
@@ -1,66 +0,0 @@
1
- import { useState } from "react"
2
- import { Check, Clipboard } from "lucide-react"
3
-
4
- import { cn } from "./utils"
5
-
6
- function fallbackCopy(value: string) {
7
- const area = document.createElement("textarea")
8
- area.value = value
9
- area.setAttribute("readonly", "")
10
- area.style.cssText = "position:fixed;left:-9999px;top:0"
11
- document.body.append(area)
12
- area.select()
13
- const copied = document.execCommand("copy")
14
- area.remove()
15
- return copied
16
- }
17
-
18
- function copyText(value: string) {
19
- const fallback = fallbackCopy(value)
20
- if (!navigator.clipboard?.writeText) return Promise.resolve(fallback)
21
- return navigator.clipboard.writeText(value).then(
22
- () => true,
23
- () => fallback,
24
- )
25
- }
26
-
27
- export function CopyButton({
28
- className,
29
- iconOnly = false,
30
- label,
31
- value,
32
- }: {
33
- className?: string
34
- iconOnly?: boolean
35
- label: string
36
- value: string
37
- }) {
38
- const [copied, setCopied] = useState(false)
39
-
40
- return (
41
- <button
42
- aria-label={label}
43
- className={cn(
44
- "hover:bg-accent flex items-center gap-1 rounded-md border text-xs",
45
- iconOnly ? "size-8 justify-center p-0" : "px-2 py-1",
46
- className,
47
- )}
48
- onClick={() => {
49
- void copyText(value).then((success) => {
50
- if (!success) return
51
- setCopied(true)
52
- window.setTimeout(() => setCopied(false), 1_800)
53
- })
54
- }}
55
- title={label}
56
- type="button"
57
- >
58
- {copied ? (
59
- <Check aria-hidden="true" className="size-3.5" />
60
- ) : (
61
- <Clipboard aria-hidden="true" className="size-3.5" />
62
- )}
63
- {iconOnly ? null : copied ? "Copied" : label}
64
- </button>
65
- )
66
- }