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,160 +0,0 @@
1
- import { readdir, readFile } from "node:fs/promises"
2
- import { basename, dirname, relative, resolve, sep } from "node:path"
3
-
4
- import type {
5
- FolderPluginContext,
6
- FolderPluginData,
7
- FolderPluginDetection,
8
- } from "@raggle-ai/folder-plugin/server"
9
-
10
- import type { CalendarSource } from "./parse.ts"
11
- import { calendarManifest } from "./manifest.ts"
12
- import {
13
- calendarIssueSummary,
14
- eventCount,
15
- mergeCalendars,
16
- parseCalendarJson,
17
- parseCalendarSource,
18
- starterCalendarSource,
19
- } from "./parse.ts"
20
-
21
- /**
22
- * Recursively discover valid `calendar.json` sources under a folder.
23
- * Hidden directories and `node_modules` are skipped, and directory symlinks
24
- * are not followed, so discovered files stay contained inside the folder.
25
- *
26
- * Returns every found file with its validated issues (empty for valid files)
27
- * plus the tolerated calendar for valid files. A file that triggers
28
- * validation issues still parses tolerantly when possible, so the view can
29
- * show the good events while listing what is wrong.
30
- */
31
- type CalendarSourceLoad = {
32
- issues: Array<{
33
- file: string
34
- problems: Array<{ path: string; message: string }>
35
- }>
36
- sources: CalendarSource[]
37
- }
38
-
39
- async function loadSources(folder: string): Promise<CalendarSourceLoad> {
40
- const visit = async (current: string): Promise<string[]> => {
41
- const entries = await readdir(current, { withFileTypes: true }).catch(
42
- () => [],
43
- )
44
- const paths: string[] = []
45
- for (const entry of entries) {
46
- if (entry.isFile() && entry.name === "calendar.json") {
47
- paths.push(resolve(current, entry.name))
48
- continue
49
- }
50
- if (
51
- !entry.isDirectory() ||
52
- entry.name.startsWith(".") ||
53
- entry.name === "node_modules"
54
- )
55
- continue
56
- paths.push(...(await visit(resolve(current, entry.name))))
57
- }
58
- return paths
59
- }
60
-
61
- const paths = (await visit(folder)).sort((left, right) =>
62
- relative(folder, left).localeCompare(relative(folder, right)),
63
- )
64
- const issues: CalendarSourceLoad["issues"] = []
65
- const sources: CalendarSource[] = []
66
- await Promise.all(
67
- paths.map(async (path) => {
68
- const sourceText = await readFile(path, "utf8")
69
- const file = relative(folder, path).split(sep).join("/")
70
- const validation = parseCalendarSource(sourceText)
71
- if (!validation.ok) {
72
- issues.push({ file, problems: validation.issues })
73
- return
74
- }
75
- const calendar = parseCalendarJson(sourceText)
76
- if (!calendar) {
77
- issues.push({
78
- file,
79
- problems: [{ path: "#", message: "could not be read as a calendar" }],
80
- })
81
- return
82
- }
83
- const directory = dirname(file)
84
- sources.push({
85
- calendar,
86
- folder: dirname(path),
87
- group: directory === "." ? basename(folder) : directory,
88
- path: file,
89
- })
90
- }),
91
- )
92
- issues.sort((left, right) => left.file.localeCompare(right.file))
93
- return { issues, sources }
94
- }
95
-
96
- function flattenIssues(load: CalendarSourceLoad) {
97
- return load.issues.flatMap(({ file, problems }) =>
98
- problems.map((problem) => ({
99
- path: problem.path === "#" ? file : `${file}: ${problem.path}`,
100
- message: problem.message,
101
- })),
102
- )
103
- }
104
-
105
- function detectionFor(load: CalendarSourceLoad) {
106
- const noContent = load.sources.length === 0 && load.issues.length === 0
107
- if (noContent) return { status: "unavailable" as const }
108
- if (load.issues.length > 0) {
109
- return {
110
- status: "error" as const,
111
- error: calendarIssueSummary(flattenIssues(load)),
112
- }
113
- }
114
- return {
115
- count: eventCount(mergeCalendars(load.sources)),
116
- status: "available" as const,
117
- }
118
- }
119
-
120
- export function createCalendarPlugin() {
121
- return {
122
- ...calendarManifest,
123
- async detect(context: FolderPluginContext): Promise<FolderPluginDetection> {
124
- return detectionFor(await loadSources(context.folder))
125
- },
126
- async handle(
127
- context: FolderPluginContext,
128
- subpath: string,
129
- ): Promise<FolderPluginData> {
130
- if (subpath !== "") return { kind: "unavailable" }
131
-
132
- const load = await loadSources(context.folder)
133
- const detection = detectionFor(load)
134
- if (detection.status === "unavailable") return { kind: "unavailable" }
135
- const calendar = mergeCalendars(load.sources)
136
- const issues = flattenIssues(load)
137
- return {
138
- kind: "available",
139
- data: {
140
- calendar: {
141
- count: calendar.events.length,
142
- items: calendar.events,
143
- startDate: calendar.startDate,
144
- title: calendar.title,
145
- },
146
- folder: context.folder,
147
- issues: issues.length > 0 ? issues : undefined,
148
- // Broken files are flagged but excluded from the rendered dataset;
149
- // valid sibling files still render next to the error panel.
150
- status: issues.length > 0 ? "error" : "available",
151
- },
152
- }
153
- },
154
- async init(context: FolderPluginContext) {
155
- return {
156
- files: [{ path: "calendar.json", content: starterCalendarSource() }],
157
- }
158
- },
159
- }
160
- }
@@ -1,80 +0,0 @@
1
- import type { CalendarColor, CalendarEvent } from "./parse"
2
-
3
- type CalendarTone = {
4
- badge: string
5
- dot: string
6
- selected: string
7
- soft: string
8
- }
9
-
10
- const tones: Record<CalendarColor, CalendarTone> = {
11
- blue: {
12
- badge:
13
- "border-blue-300 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200",
14
- dot: "bg-blue-500",
15
- selected:
16
- "border-blue-600 bg-blue-600 !text-white shadow-sm hover:bg-blue-700",
17
- soft: "border-blue-200 bg-blue-50 text-blue-800 hover:bg-blue-100 dark:border-blue-900 dark:bg-blue-950 dark:text-blue-200",
18
- },
19
- green: {
20
- badge:
21
- "border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
22
- dot: "bg-emerald-500",
23
- selected:
24
- "border-emerald-600 bg-emerald-600 !text-white shadow-sm hover:bg-emerald-700",
25
- soft: "border-emerald-200 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-200",
26
- },
27
- orange: {
28
- badge:
29
- "border-orange-300 bg-orange-50 text-orange-800 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-200",
30
- dot: "bg-orange-500",
31
- selected:
32
- "border-orange-600 bg-orange-600 !text-white shadow-sm hover:bg-orange-700",
33
- soft: "border-orange-200 bg-orange-50 text-orange-800 hover:bg-orange-100 dark:border-orange-900 dark:bg-orange-950 dark:text-orange-200",
34
- },
35
- purple: {
36
- badge:
37
- "border-purple-300 bg-purple-50 text-purple-800 dark:border-purple-800 dark:bg-purple-950 dark:text-purple-200",
38
- dot: "bg-purple-500",
39
- selected:
40
- "border-purple-600 bg-purple-600 !text-white shadow-sm hover:bg-purple-700",
41
- soft: "border-purple-200 bg-purple-50 text-purple-800 hover:bg-purple-100 dark:border-purple-900 dark:bg-purple-950 dark:text-purple-200",
42
- },
43
- red: {
44
- badge:
45
- "border-red-300 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200",
46
- dot: "bg-red-500",
47
- selected:
48
- "border-red-600 bg-red-600 !text-white shadow-sm hover:bg-red-700",
49
- soft: "border-red-200 bg-red-50 text-red-800 hover:bg-red-100 dark:border-red-900 dark:bg-red-950 dark:text-red-200",
50
- },
51
- yellow: {
52
- badge:
53
- "border-amber-300 bg-amber-50 text-amber-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-100",
54
- dot: "bg-amber-500",
55
- selected:
56
- "border-amber-500 bg-amber-500 !text-amber-950 shadow-sm hover:bg-amber-600",
57
- soft: "border-amber-200 bg-amber-50 text-amber-900 hover:bg-amber-100 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100",
58
- },
59
- }
60
-
61
- export function calendarTone(color?: CalendarColor) {
62
- return tones[color ?? "blue"]
63
- }
64
-
65
- export function eventTone(event: CalendarEvent) {
66
- return calendarTone(event.color ?? event.groupColor)
67
- }
68
-
69
- export function cn(...values: Array<false | null | string | undefined>) {
70
- return values.filter(Boolean).join(" ")
71
- }
72
-
73
- export function eventSpan(event: CalendarEvent) {
74
- return {
75
- allDay:
76
- event.allDay ?? event.end.getTime() - event.start.getTime() >= 86_399_000,
77
- end: event.end,
78
- start: event.start,
79
- }
80
- }
@@ -1,418 +0,0 @@
1
- import type { ReactNode } from "react"
2
- import { useEffect, useRef } from "react"
3
- import {
4
- addDays,
5
- format,
6
- isSameDay,
7
- isSameMonth,
8
- startOfDay,
9
- startOfMonth,
10
- startOfWeek,
11
- } from "date-fns"
12
- import { CalendarDays } from "lucide-react"
13
-
14
- import type { CalendarEvent } from "./parse"
15
- import { EventDialog } from "./event-dialog"
16
- import { cn, eventSpan, eventTone } from "./utils"
17
-
18
- const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
19
- const weekStart = { weekStartsOn: 1 as const }
20
- const hourHeight = 48
21
-
22
- type LaneItem = {
23
- endCol: number // inclusive last covered column
24
- event: CalendarEvent
25
- lane: number
26
- startCol: number
27
- }
28
-
29
- // Place events intersecting one week row into horizontal lanes. Multi-day events
30
- // keep one lane across their covered columns so they render as a single bar.
31
- function layoutLanes(days: Date[], events: CalendarEvent[]): LaneItem[] {
32
- if (days.length === 0) return []
33
- const first = startOfDay(days[0]!)
34
- const last = addDays(startOfDay(days[days.length - 1]!), 1)
35
- const relevant = events
36
- .filter((event) => event.end > first && event.start < last)
37
- .sort(
38
- (left, right) =>
39
- left.start.getTime() - right.start.getTime() ||
40
- right.end.getTime() -
41
- right.start.getTime() -
42
- (left.end.getTime() - left.start.getTime()),
43
- )
44
-
45
- const laneEnds: number[] = []
46
- const items: LaneItem[] = []
47
- for (const event of relevant) {
48
- const startCol = Math.max(
49
- 0,
50
- days.findIndex(
51
- (day) => day.getTime() >= startOfDay(event.start).getTime(),
52
- ),
53
- )
54
- let endCol = startCol
55
- for (let index = startCol; index < days.length; index++) {
56
- if (days[index]!.getTime() >= event.end.getTime()) break
57
- endCol = index
58
- }
59
- let lane = laneEnds.findIndex((end) => end <= startCol)
60
- if (lane === -1) {
61
- lane = laneEnds.length
62
- laneEnds.push(-1)
63
- }
64
- laneEnds[lane] = endCol + 1
65
- items.push({ endCol, event, lane, startCol })
66
- }
67
- return items
68
- }
69
-
70
- function LaneGrid({ columns, items }: { columns: number; items: LaneItem[] }) {
71
- if (items.length === 0) return null
72
- const lanes = Math.max(...items.map((item) => item.lane)) + 1
73
-
74
- return (
75
- <div
76
- className="relative grid"
77
- style={{
78
- gridTemplateColumns: `repeat(${columns}, 1fr)`,
79
- gridTemplateRows: `repeat(${lanes}, auto)`,
80
- }}
81
- >
82
- {items.map((item) => (
83
- <div
84
- className="min-w-0 pr-0.5 pb-0.5"
85
- key={item.event.id}
86
- style={{
87
- gridColumn: `${item.startCol + 1} / ${item.endCol + 2}`,
88
- gridRow: item.lane + 1,
89
- }}
90
- >
91
- <EventDialog
92
- event={item.event}
93
- variant={item.endCol > item.startCol ? "bar" : "chip"}
94
- />
95
- </div>
96
- ))}
97
- </div>
98
- )
99
- }
100
-
101
- export function MonthView({
102
- date,
103
- events,
104
- }: {
105
- date: Date
106
- events: CalendarEvent[]
107
- }) {
108
- const monthStart = startOfMonth(date)
109
- const start = startOfWeek(monthStart, weekStart)
110
- const weeks = Array.from({ length: 6 }, (_, week) =>
111
- Array.from({ length: 7 }, (_, day) => addDays(start, week * 7 + day)),
112
- )
113
-
114
- return (
115
- <div>
116
- <div className="grid grid-cols-7">
117
- {weekdays.map((label) => (
118
- <div
119
- className="text-muted-foreground border-b py-1 text-center text-xs
120
- font-medium"
121
- key={label}
122
- >
123
- {label}
124
- </div>
125
- ))}
126
- </div>
127
- {weeks.map((days) => (
128
- <MonthWeek
129
- days={days}
130
- events={events}
131
- key={days[0]!.toISOString()}
132
- monthStart={monthStart}
133
- />
134
- ))}
135
- </div>
136
- )
137
- }
138
-
139
- function MonthWeek({
140
- days,
141
- events,
142
- monthStart,
143
- }: {
144
- days: Date[]
145
- events: CalendarEvent[]
146
- monthStart: Date
147
- }) {
148
- const items = layoutLanes(days, events)
149
-
150
- return (
151
- <div className="min-h-20 border-b last:border-b-0">
152
- <div className="grid grid-cols-7">
153
- {days.map((day) => {
154
- const isCurrentMonth = isSameMonth(day, monthStart)
155
- const isToday = isSameDay(day, new Date())
156
- return (
157
- <div
158
- className={cn(
159
- "flex justify-end border-r px-1.5 py-1 last:border-r-0",
160
- !isCurrentMonth && "bg-muted/30",
161
- )}
162
- key={day.toISOString()}
163
- >
164
- <span
165
- className={cn(
166
- "flex size-5 items-center justify-center rounded-full text-xs",
167
- isToday
168
- ? "bg-primary text-primary-foreground font-semibold"
169
- : "text-muted-foreground",
170
- )}
171
- >
172
- {format(day, "d")}
173
- </span>
174
- </div>
175
- )
176
- })}
177
- </div>
178
- <div className="relative min-h-14 pt-0.5">
179
- <div
180
- aria-hidden="true"
181
- className="pointer-events-none absolute inset-0 grid grid-cols-7"
182
- >
183
- {days.map((day) => (
184
- <div
185
- className={cn(
186
- "border-r last:border-r-0",
187
- !isSameMonth(day, monthStart) && "bg-muted/30",
188
- )}
189
- key={day.toISOString()}
190
- />
191
- ))}
192
- </div>
193
- <LaneGrid columns={days.length} items={items} />
194
- </div>
195
- </div>
196
- )
197
- }
198
-
199
- function eventsByHour(events: CalendarEvent[], day: Date) {
200
- const start = startOfDay(day)
201
- const next = addDays(start, 1)
202
- const hours = new Map<number, CalendarEvent[]>()
203
- for (const event of events.filter(
204
- (item) => item.end > start && item.start < next && !eventSpan(item).allDay,
205
- )) {
206
- const first = Math.max(0, Math.floor(event.start.getHours()))
207
- const last = Math.min(23, event.end.getHours())
208
- for (let hour = first; hour <= last; hour++) {
209
- const list = hours.get(hour) ?? []
210
- list.push(event)
211
- hours.set(hour, list)
212
- }
213
- }
214
- return hours
215
- }
216
-
217
- function HourGrid({
218
- columns,
219
- columnHeaders,
220
- events,
221
- }: {
222
- columns: Date[]
223
- columnHeaders?: ReactNode[]
224
- events: CalendarEvent[]
225
- }) {
226
- const area = useRef<HTMLDivElement>(null)
227
- const grid = columns.map((column) => ({
228
- hours: eventsByHour(events, column),
229
- }))
230
- const allDayItems = layoutLanes(
231
- columns,
232
- events.filter((event) => eventSpan(event).allDay),
233
- )
234
- const hasAllDay = allDayItems.length > 0
235
- const first = events
236
- .filter((event) => !eventSpan(event).allDay)
237
- .sort((left, right) => left.start.getTime() - right.start.getTime())[0]
238
-
239
- useEffect(() => {
240
- const frame = requestAnimationFrame(() => {
241
- const viewport = area.current?.querySelector<HTMLElement>(
242
- '[data-slot="scroll-area-viewport"]',
243
- )
244
- const event = viewport?.querySelector<HTMLElement>(
245
- '[data-calendar-first-event="true"]',
246
- )
247
- if (!viewport || !event) return
248
-
249
- const viewportRect = viewport.getBoundingClientRect()
250
- const eventRect = event.getBoundingClientRect()
251
- viewport.scrollTo({
252
- behavior: "auto",
253
- top: Math.max(
254
- 0,
255
- viewport.scrollTop +
256
- eventRect.top -
257
- viewportRect.top -
258
- viewport.clientHeight / 2 +
259
- eventRect.height / 2,
260
- ),
261
- })
262
- })
263
-
264
- return () => cancelAnimationFrame(frame)
265
- }, [first?.id, first?.start])
266
-
267
- return (
268
- <div className="h-[calc(100vh-240px)] min-h-80" ref={area}>
269
- <div
270
- className="size-full overflow-auto rounded-[inherit]"
271
- data-slot="scroll-area-viewport"
272
- >
273
- <div className="min-w-[600px]">
274
- {columnHeaders ? (
275
- <div
276
- className="grid border-b"
277
- style={{
278
- gridTemplateColumns: `52px repeat(${columns.length}, 1fr)`,
279
- }}
280
- >
281
- <div />
282
- {columnHeaders.map((header, index) => (
283
- <div
284
- className="border-l px-2 py-2 font-medium"
285
- key={columns[index]?.toISOString()}
286
- >
287
- {header}
288
- </div>
289
- ))}
290
- </div>
291
- ) : null}
292
- {hasAllDay ? (
293
- <div
294
- className="grid border-b"
295
- style={{
296
- gridTemplateColumns: `52px 1fr`,
297
- }}
298
- >
299
- <div
300
- className="text-muted-foreground px-1 py-1 text-right
301
- text-[0.65rem] uppercase"
302
- >
303
- All day
304
- </div>
305
- <div className="relative min-h-8 py-0.5">
306
- <div
307
- aria-hidden="true"
308
- className="pointer-events-none absolute inset-0 grid"
309
- style={{
310
- gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
311
- }}
312
- >
313
- {columns.map((column) => (
314
- <div className="border-l" key={column.toISOString()} />
315
- ))}
316
- </div>
317
- <LaneGrid columns={columns.length} items={allDayItems} />
318
- </div>
319
- </div>
320
- ) : null}
321
- <div
322
- className="grid"
323
- style={{
324
- gridTemplateColumns: `52px repeat(${columns.length}, 1fr)`,
325
- }}
326
- >
327
- <div>
328
- {Array.from({ length: 24 }, (_, hour) => (
329
- <div
330
- className="text-muted-foreground relative pr-2 text-right
331
- text-[0.65rem]"
332
- key={hour}
333
- style={{ height: hourHeight }}
334
- >
335
- <span className="absolute top-0 right-1 -translate-y-1/2">
336
- {hour === 0 ? "" : format(new Date(2020, 0, 1, hour), "ha")}
337
- </span>
338
- </div>
339
- ))}
340
- </div>
341
- {grid.map((column, index) => (
342
- <div className="border-l" key={columns[index]?.toISOString()}>
343
- {Array.from({ length: 24 }, (_, hour) => (
344
- <div
345
- className="relative border-t px-1"
346
- key={hour}
347
- style={{ height: hourHeight }}
348
- >
349
- {column.hours.get(hour)?.map((event) => (
350
- <div
351
- className={cn(
352
- "mb-0.5 truncate rounded border-l-2 text-xs last:mb-0",
353
- eventTone(event).badge,
354
- )}
355
- data-calendar-first-event={
356
- event.id === first?.id ? "true" : undefined
357
- }
358
- key={event.id}
359
- >
360
- <EventDialog event={event} />
361
- </div>
362
- )) ?? null}
363
- </div>
364
- ))}
365
- </div>
366
- ))}
367
- </div>
368
- </div>
369
- </div>
370
- </div>
371
- )
372
- }
373
-
374
- export function DayView({
375
- date,
376
- events,
377
- }: {
378
- date: Date
379
- events: CalendarEvent[]
380
- }) {
381
- return (
382
- <HourGrid
383
- columnHeaders={[
384
- <div className="flex items-center gap-1.5" key="day">
385
- <CalendarDays aria-hidden="true" className="size-4" />
386
- {format(date, "EEE MMM d")}
387
- </div>,
388
- ]}
389
- columns={[startOfDay(date)]}
390
- events={events}
391
- />
392
- )
393
- }
394
-
395
- export function WeekView({
396
- date,
397
- events,
398
- }: {
399
- date: Date
400
- events: CalendarEvent[]
401
- }) {
402
- const start = startOfWeek(date, weekStart)
403
- const days = Array.from({ length: 7 }, (_, index) => addDays(start, index))
404
- const headers = days.map((day) => (
405
- <div
406
- className={cn(
407
- "leading-tight",
408
- isSameDay(day, new Date()) && "text-primary",
409
- )}
410
- key={day.toISOString()}
411
- >
412
- <strong>{format(day, "EEE")}</strong>{" "}
413
- <span className="text-muted-foreground">{format(day, "d")}</span>
414
- </div>
415
- ))
416
-
417
- return <HourGrid columnHeaders={headers} columns={days} events={events} />
418
- }