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.
- package/README.md +23 -13
- package/dist/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/usage.js +1 -1
- package/dist/cli/src/core/command.js +1 -0
- package/dist/cli/src/core/init.js +5 -5
- package/dist/cli/src/plugins/external.d.ts +28 -2
- package/dist/cli/src/plugins/external.js +75 -21
- package/dist/cli/src/site/server-plugins.d.ts +2 -7
- package/dist/cli/src/site/viewer.js +1 -0
- package/dist/cli/usage.js +1 -1
- package/dist/core/command.js +1 -0
- package/dist/core/init.js +5 -5
- package/dist/core/runtime.js +357 -3207
- package/dist/package/preview.js +1232 -5113
- package/dist/plugins/external.d.ts +28 -2
- package/dist/plugins/external.js +75 -21
- package/dist/site/server-plugins.d.ts +2 -7
- package/dist/site/server-plugins.js +552 -10447
- package/dist/site/viewer.js +1 -0
- package/kernel/apps/web/package.json +1 -5
- package/kernel/apps/web/scripts/dev.mjs +53 -0
- package/kernel/apps/web/src/app.css +0 -3
- package/kernel/apps/web/src/main.tsx +0 -1
- package/kernel/apps/web/src/plugins.ts +0 -30
- package/kernel/apps/web/tsconfig.json +1 -2
- package/kernel/apps/web/vite.config.ts +31 -13
- package/kernel/packages/api/package.json +0 -4
- package/kernel/packages/api/src/server/folder-metadata-plugin.ts +13 -38
- package/kernel/packages/api/src/server/index.ts +0 -1
- package/kernel/packages/api/src/server/plugins.ts +49 -16
- package/kernel/packages/api/test/core.test.ts +13 -25
- package/kernel/packages/markdown/README.md +1 -1
- package/package.json +1 -1
- package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
- package/web/assets/app-CX20nNIa.js +2 -0
- package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
- package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
- package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
- package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
- package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
- package/web/assets/channel-X7WHyxxT.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
- package/web/assets/client-7OWw5kW2.js +6 -0
- package/web/assets/client-DYj_td4Q.js +17 -0
- package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
- package/web/assets/command-zkzH4hVz.js +38 -0
- package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
- package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
- package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
- package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
- package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
- package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
- package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
- package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
- package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
- package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
- package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
- package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
- package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
- package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
- package/web/assets/index-CegvVQ1C.js +1 -0
- package/web/assets/index-Dwhf8F_U.css +1 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
- package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
- package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
- package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
- package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
- package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
- package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
- package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
- package/web/assets/select-CtXC_PLC.js +30 -0
- package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
- package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
- package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
- package/web/index.html +2 -2
- package/kernel/packages/api/test/partial-loading.test.ts +0 -43
- package/kernel/plugins/calendar/SKILL.md +0 -173
- package/kernel/plugins/calendar/eslint.config.js +0 -10
- package/kernel/plugins/calendar/package.json +0 -73
- package/kernel/plugins/calendar/schema.json +0 -137
- package/kernel/plugins/calendar/src/calendar.tsx +0 -328
- package/kernel/plugins/calendar/src/client.tsx +0 -139
- package/kernel/plugins/calendar/src/context.ts +0 -48
- package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
- package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
- package/kernel/plugins/calendar/src/index.ts +0 -4
- package/kernel/plugins/calendar/src/manifest.ts +0 -7
- package/kernel/plugins/calendar/src/parse.ts +0 -610
- package/kernel/plugins/calendar/src/server.ts +0 -160
- package/kernel/plugins/calendar/src/utils.ts +0 -80
- package/kernel/plugins/calendar/src/views.tsx +0 -418
- package/kernel/plugins/calendar/test/context.test.mjs +0 -60
- package/kernel/plugins/calendar/test/package.test.mjs +0 -43
- package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
- package/kernel/plugins/calendar/test/server.test.mjs +0 -126
- package/kernel/plugins/calendar/tsconfig.json +0 -17
- package/kernel/plugins/mail/SKILL.md +0 -97
- package/kernel/plugins/mail/eslint.config.js +0 -10
- package/kernel/plugins/mail/package.json +0 -79
- package/kernel/plugins/mail/src/api.ts +0 -359
- package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
- package/kernel/plugins/mail/src/client.tsx +0 -14
- package/kernel/plugins/mail/src/flock.ts +0 -137
- package/kernel/plugins/mail/src/folders.ts +0 -37
- package/kernel/plugins/mail/src/history.ts +0 -485
- package/kernel/plugins/mail/src/index.ts +0 -3
- package/kernel/plugins/mail/src/mail-card.tsx +0 -123
- package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
- package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
- package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
- package/kernel/plugins/mail/src/mail-page.tsx +0 -109
- package/kernel/plugins/mail/src/mail-request.ts +0 -78
- package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
- package/kernel/plugins/mail/src/mailos.ts +0 -723
- package/kernel/plugins/mail/src/manifest.ts +0 -7
- package/kernel/plugins/mail/src/node.ts +0 -171
- package/kernel/plugins/mail/src/parse.ts +0 -139
- package/kernel/plugins/mail/src/server.ts +0 -125
- package/kernel/plugins/mail/src/types.ts +0 -10
- package/kernel/plugins/mail/src/viewer.tsx +0 -147
- package/kernel/plugins/mail/test/flock.test.mjs +0 -156
- package/kernel/plugins/mail/test/folders.test.mjs +0 -138
- package/kernel/plugins/mail/test/history.test.mjs +0 -460
- package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
- package/kernel/plugins/mail/test/node.test.mjs +0 -207
- package/kernel/plugins/mail/test/package.test.mjs +0 -54
- package/kernel/plugins/mail/test/parse.test.mjs +0 -32
- package/kernel/plugins/mail/test/server.test.mjs +0 -116
- package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
- package/kernel/plugins/mail/tsconfig.json +0 -17
- package/kernel/plugins/projects/SKILL.md +0 -73
- package/kernel/plugins/projects/eslint.config.js +0 -10
- package/kernel/plugins/projects/package.json +0 -70
- package/kernel/plugins/projects/src/client.tsx +0 -17
- package/kernel/plugins/projects/src/data/model.ts +0 -129
- package/kernel/plugins/projects/src/index.ts +0 -17
- package/kernel/plugins/projects/src/manifest.ts +0 -7
- package/kernel/plugins/projects/src/project-context.ts +0 -41
- package/kernel/plugins/projects/src/server.ts +0 -276
- package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
- package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
- package/kernel/plugins/projects/test/package.test.mjs +0 -51
- package/kernel/plugins/projects/tsconfig.json +0 -17
- package/kernel/plugins/tasks/SKILL.md +0 -341
- package/kernel/plugins/tasks/eslint.config.js +0 -10
- package/kernel/plugins/tasks/package.json +0 -74
- package/kernel/plugins/tasks/schema.json +0 -939
- package/kernel/plugins/tasks/src/api.ts +0 -212
- package/kernel/plugins/tasks/src/client.tsx +0 -23
- package/kernel/plugins/tasks/src/data/parse.ts +0 -544
- package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
- package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
- package/kernel/plugins/tasks/src/index.ts +0 -34
- package/kernel/plugins/tasks/src/manifest.ts +0 -7
- package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
- package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
- package/kernel/plugins/tasks/src/server.ts +0 -447
- package/kernel/plugins/tasks/src/styles.css +0 -9
- package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
- package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
- package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
- package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
- package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
- package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
- package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
- package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
- package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
- package/kernel/plugins/tasks/test/api.test.mjs +0 -99
- package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
- package/kernel/plugins/tasks/test/package.test.mjs +0 -54
- package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
- package/kernel/plugins/tasks/test/server.test.mjs +0 -386
- package/kernel/plugins/tasks/tsconfig.json +0 -17
- package/web/assets/app-DoGr2Zi7.js +0 -2
- package/web/assets/channel-uMEjV8qc.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
- package/web/assets/client-C6Nxdfif.js +0 -2
- package/web/assets/client-CXYfBko5.js +0 -17
- package/web/assets/client-CoKtOq-X.js +0 -7
- package/web/assets/client-DyRQz5RU.js +0 -53
- package/web/assets/client-OY6HUzuD.js +0 -98
- package/web/assets/client-YOYWZIFi.js +0 -34
- package/web/assets/command-BugZBGXE.js +0 -38
- package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
- package/web/assets/index-BaRnbYRw.js +0 -1
- package/web/assets/index-BexmLxIP.css +0 -1
- package/web/assets/index-DDirZMgx.js +0 -1
- package/web/assets/note-card-DBfX6gW1.js +0 -6
- package/web/assets/select-D8cqNgcp.js +0 -30
- package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
- package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export { tasksManifest } from "./manifest.ts"
|
|
2
|
-
export type { TodoItemProps } from "./ui/todo-item.js"
|
|
3
|
-
export type {
|
|
4
|
-
TodoItemData,
|
|
5
|
-
TodoItemLink,
|
|
6
|
-
TodoItemRelation,
|
|
7
|
-
TodoItemStatus,
|
|
8
|
-
TodoItemStep,
|
|
9
|
-
} from "./data/todo-model.js"
|
|
10
|
-
export type {
|
|
11
|
-
TodoDataset,
|
|
12
|
-
TodoDatasetOptions,
|
|
13
|
-
TodoFilter,
|
|
14
|
-
TodoFilterSelection,
|
|
15
|
-
TodoState,
|
|
16
|
-
UiTodoItem,
|
|
17
|
-
} from "./data/todo-model.js"
|
|
18
|
-
export {
|
|
19
|
-
decodeTodoDataset,
|
|
20
|
-
TodoDatasetSchema,
|
|
21
|
-
todoItems,
|
|
22
|
-
} from "./data/todo-model.js"
|
|
23
|
-
export type { CompactTodoListProps } from "./ui/compact-todo-list.js"
|
|
24
|
-
export { CompactTodoList } from "./ui/compact-todo-list.js"
|
|
25
|
-
export type { TodoFilterBarProps } from "./ui/todo-filter-bar.js"
|
|
26
|
-
export { TodoFilterBar } from "./ui/todo-filter-bar.js"
|
|
27
|
-
export type { TodoStatusFilterProps } from "./ui/todo-status-filter.js"
|
|
28
|
-
export { TodoStatusFilter } from "./ui/todo-status-filter.js"
|
|
29
|
-
export type { TodoAgentFilterProps } from "./ui/todo-agent-filter.js"
|
|
30
|
-
export { TodoAgentFilter } from "./ui/todo-agent-filter.js"
|
|
31
|
-
export type { TodoTagFilterProps } from "./ui/todo-tag-filter.js"
|
|
32
|
-
export { TodoTagFilter } from "./ui/todo-tag-filter.js"
|
|
33
|
-
export { TodoItem } from "./ui/todo-item.js"
|
|
34
|
-
export { TodoWarningsTrigger } from "./ui/todo-warnings.tsx"
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
|
|
2
|
-
|
|
3
|
-
import { TaskPage, useTaskPage } from "../ui/task-page"
|
|
4
|
-
import { TodoItem } from "../ui/todo-item"
|
|
5
|
-
import { TodoWarningsTrigger } from "../ui/todo-warnings"
|
|
6
|
-
|
|
7
|
-
/** Individual task page: the full single task behind a list row link. */
|
|
8
|
-
export function TaskDetailPage({
|
|
9
|
-
documentHref,
|
|
10
|
-
folder,
|
|
11
|
-
folderId,
|
|
12
|
-
listHref,
|
|
13
|
-
request,
|
|
14
|
-
subpath,
|
|
15
|
-
}: FolderPluginViewProps) {
|
|
16
|
-
const page = useTaskPage({
|
|
17
|
-
folder,
|
|
18
|
-
folderId,
|
|
19
|
-
noun: "task",
|
|
20
|
-
request,
|
|
21
|
-
subpath,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<TaskPage
|
|
26
|
-
body={
|
|
27
|
-
page.item ? (
|
|
28
|
-
<TodoItem
|
|
29
|
-
item={page.item}
|
|
30
|
-
documentHref={documentHref}
|
|
31
|
-
canToggle={!page.busy}
|
|
32
|
-
onToggle={() => {
|
|
33
|
-
if (subpath) page.toggle(subpath)
|
|
34
|
-
}}
|
|
35
|
-
/>
|
|
36
|
-
) : undefined
|
|
37
|
-
}
|
|
38
|
-
extra={
|
|
39
|
-
<div className="flex justify-end">
|
|
40
|
-
<TodoWarningsTrigger warnings={page.data?.warnings} folder={folder} />
|
|
41
|
-
</div>
|
|
42
|
-
}
|
|
43
|
-
list={{ href: listHref, name: "Tasks" }}
|
|
44
|
-
missingMessage="Task not found."
|
|
45
|
-
page={page}
|
|
46
|
-
/>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react"
|
|
2
|
-
|
|
3
|
-
import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
|
|
4
|
-
import { FolderFilterBar } from "@raggle-ai/ui/components/folder-filter-bar"
|
|
5
|
-
import { useFolderFilter } from "@raggle-ai/ui/hooks/use-folder-filter"
|
|
6
|
-
|
|
7
|
-
import type { UiTodoItem } from "../data/todo-model"
|
|
8
|
-
import { qualifiedTaskId } from "../data/task-id"
|
|
9
|
-
import { CompactTodoList } from "../ui/compact-todo-list"
|
|
10
|
-
import { TaskPage, useTaskPage } from "../ui/task-page"
|
|
11
|
-
import { TodoWarningsTrigger } from "../ui/todo-warnings"
|
|
12
|
-
|
|
13
|
-
/** Main Tasks page: filters, counts, and one compact row per task. */
|
|
14
|
-
export function TaskListPage({
|
|
15
|
-
folder,
|
|
16
|
-
folderId,
|
|
17
|
-
href,
|
|
18
|
-
request,
|
|
19
|
-
}: FolderPluginViewProps) {
|
|
20
|
-
const page = useTaskPage({
|
|
21
|
-
folder,
|
|
22
|
-
folderId,
|
|
23
|
-
noun: "tasks",
|
|
24
|
-
request,
|
|
25
|
-
subpath: "",
|
|
26
|
-
})
|
|
27
|
-
// Shared folder filter over every task: a workspace root merges nested
|
|
28
|
-
// task files, so folder chips narrow to directories (status filtering of
|
|
29
|
-
// the visible set happens inside the todo list).
|
|
30
|
-
const filter = useFolderFilter(page.tasks, {
|
|
31
|
-
getFolder: (item) => item.jsonTodo.source.id,
|
|
32
|
-
rootFolder: folder,
|
|
33
|
-
})
|
|
34
|
-
const filteredDataset = useMemo(() => {
|
|
35
|
-
if (!page.dataset) return undefined
|
|
36
|
-
return {
|
|
37
|
-
...page.dataset,
|
|
38
|
-
tasks: filter.visibleItems.map((item) => item.jsonTodo),
|
|
39
|
-
}
|
|
40
|
-
}, [page.dataset, filter.visibleItems])
|
|
41
|
-
// Workspace-qualified task address: the owning folder plus the task id,
|
|
42
|
-
// so identical file-local ids (`todo:1` in nested files) stay distinct in
|
|
43
|
-
// detail links and toggle writes. Falls back to the bare id when the
|
|
44
|
-
// source folder is unknown.
|
|
45
|
-
const taskAddress = (item: UiTodoItem) =>
|
|
46
|
-
qualifiedTaskId(item.jsonTodo.source?.id, item.jsonTodo) ??
|
|
47
|
-
[item.id, item.key].find((value) => value)
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<TaskPage
|
|
51
|
-
body={
|
|
52
|
-
filteredDataset ? (
|
|
53
|
-
<CompactTodoList
|
|
54
|
-
dataset={filteredDataset}
|
|
55
|
-
trailingFilters={
|
|
56
|
-
<TodoWarningsTrigger
|
|
57
|
-
warnings={page.data?.warnings}
|
|
58
|
-
folder={folder}
|
|
59
|
-
/>
|
|
60
|
-
}
|
|
61
|
-
filters
|
|
62
|
-
leadingFilters={
|
|
63
|
-
filter.choices.length > 1 ? (
|
|
64
|
-
<FolderFilterBar
|
|
65
|
-
ariaLabel="Filter tasks by folder"
|
|
66
|
-
choices={filter.choices}
|
|
67
|
-
onSelect={filter.setSelected}
|
|
68
|
-
selected={filter.effectiveSelected}
|
|
69
|
-
total={filter.total}
|
|
70
|
-
/>
|
|
71
|
-
) : undefined
|
|
72
|
-
}
|
|
73
|
-
itemHref={(item) => {
|
|
74
|
-
const target = taskAddress(item)
|
|
75
|
-
return target ? href(target) : undefined
|
|
76
|
-
}}
|
|
77
|
-
onToggle={(item) => {
|
|
78
|
-
const target = taskAddress(item)
|
|
79
|
-
if (target) page.toggle(target)
|
|
80
|
-
}}
|
|
81
|
-
/>
|
|
82
|
-
) : undefined
|
|
83
|
-
}
|
|
84
|
-
page={page}
|
|
85
|
-
unavailableMessage="No local task file in this folder."
|
|
86
|
-
/>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile, stat } from "node:fs/promises"
|
|
2
|
-
import { basename, dirname, relative, resolve, sep } from "node:path"
|
|
3
|
-
|
|
4
|
-
import type {
|
|
5
|
-
FolderPluginContext,
|
|
6
|
-
FolderPluginDetection,
|
|
7
|
-
FolderPluginPayload,
|
|
8
|
-
} from "@raggle-ai/folder-plugin/server"
|
|
9
|
-
import { matchSubpath } from "@raggle-ai/folder-plugin/subpath"
|
|
10
|
-
|
|
11
|
-
import type { TodoFile, TodoIssue } from "./data/parse.ts"
|
|
12
|
-
import {
|
|
13
|
-
parseTodoSource,
|
|
14
|
-
starterTodoSource,
|
|
15
|
-
todoFileWarnings,
|
|
16
|
-
todoIssueSummary,
|
|
17
|
-
} from "./data/parse.ts"
|
|
18
|
-
import { parseQualifiedTaskId, refMatchesFolder } from "./data/task-id.ts"
|
|
19
|
-
import { tasksManifest } from "./manifest.ts"
|
|
20
|
-
|
|
21
|
-
type TaskSource = {
|
|
22
|
-
document: TodoFile
|
|
23
|
-
/** Absolute directory that owns the `todo.json` file. */
|
|
24
|
-
folder: string
|
|
25
|
-
/** Path relative to the viewed root, e.g. "todo.json" or "clients/client-one/todo.json". */
|
|
26
|
-
path: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type TaskSourceLoad = {
|
|
30
|
-
issues: Array<{ file: string; problems: TodoIssue[] }>
|
|
31
|
-
/** Advisory warnings per file; they never invalidate a source. */
|
|
32
|
-
warnings: Array<{ file: string; problems: TodoIssue[] }>
|
|
33
|
-
sources: TaskSource[]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Recursively discover `todo.json` files under a folder. Hidden directories
|
|
38
|
-
* and `node_modules` are skipped and directory symlinks are not followed, so
|
|
39
|
-
* discovered files stay contained inside the folder. Invalid files are
|
|
40
|
-
* reported, valid files become task sources (mirroring the calendar
|
|
41
|
-
* plugin's recursive discovery). `viewRoot` is the folder the viewer opened;
|
|
42
|
-
* `scanRoot` defaults to it but may be a linked folder, in which case source
|
|
43
|
-
* paths stay relative to the viewed root.
|
|
44
|
-
*/
|
|
45
|
-
async function loadTaskSources(
|
|
46
|
-
viewRoot: string,
|
|
47
|
-
scanRoot: string = viewRoot,
|
|
48
|
-
): Promise<TaskSourceLoad> {
|
|
49
|
-
const visit = async (current: string): Promise<string[]> => {
|
|
50
|
-
const entries = await readdir(current, { withFileTypes: true }).catch(
|
|
51
|
-
() => [],
|
|
52
|
-
)
|
|
53
|
-
const paths: string[] = []
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
if (entry.isFile() && entry.name === "todo.json") {
|
|
56
|
-
paths.push(resolve(current, entry.name))
|
|
57
|
-
continue
|
|
58
|
-
}
|
|
59
|
-
if (
|
|
60
|
-
!entry.isDirectory() ||
|
|
61
|
-
entry.name.startsWith(".") ||
|
|
62
|
-
entry.name === "node_modules"
|
|
63
|
-
)
|
|
64
|
-
continue
|
|
65
|
-
paths.push(...(await visit(resolve(current, entry.name))))
|
|
66
|
-
}
|
|
67
|
-
return paths
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const paths = (await visit(scanRoot)).sort((left, right) =>
|
|
71
|
-
relative(scanRoot, left).localeCompare(relative(scanRoot, right)),
|
|
72
|
-
)
|
|
73
|
-
const issues: TaskSourceLoad["issues"] = []
|
|
74
|
-
const warnings: TaskSourceLoad["warnings"] = []
|
|
75
|
-
const sources: TaskSource[] = []
|
|
76
|
-
await Promise.all(
|
|
77
|
-
paths.map(async (path) => {
|
|
78
|
-
const sourceText = await readFile(path, "utf8")
|
|
79
|
-
const file = relative(viewRoot, path).split(sep).join("/")
|
|
80
|
-
const parsed = parseTodoSource(sourceText)
|
|
81
|
-
if (!parsed.ok) {
|
|
82
|
-
issues.push({ file, problems: parsed.issues })
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
warnings.push({
|
|
86
|
-
file,
|
|
87
|
-
problems: [
|
|
88
|
-
...todoFileWarnings(parsed.document),
|
|
89
|
-
...(await missingNoteIssues(parsed.document, dirname(path))),
|
|
90
|
-
],
|
|
91
|
-
})
|
|
92
|
-
sources.push({
|
|
93
|
-
document: parsed.document,
|
|
94
|
-
folder: dirname(path),
|
|
95
|
-
path: file,
|
|
96
|
-
})
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
issues.sort((left, right) => left.file.localeCompare(right.file))
|
|
100
|
-
return { issues, sources, warnings }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Load `todo.json` files from the host-resolved linked folders (the same
|
|
105
|
-
* relation graph that powers linked notes). Paths stay relative to the
|
|
106
|
-
* viewed root so linked sources never collide with the root `todo.json`
|
|
107
|
-
* semantics; the multi-source dataset already renders them as their own
|
|
108
|
-
* folder chips.
|
|
109
|
-
*/
|
|
110
|
-
async function loadLinkedTaskSources(
|
|
111
|
-
viewRoot: string,
|
|
112
|
-
linkedFolders: string[],
|
|
113
|
-
): Promise<TaskSourceLoad> {
|
|
114
|
-
const loads = await Promise.all(
|
|
115
|
-
linkedFolders.map((folder) => loadTaskSources(viewRoot, folder)),
|
|
116
|
-
)
|
|
117
|
-
return {
|
|
118
|
-
issues: loads.flatMap((load) => load.issues),
|
|
119
|
-
sources: loads.flatMap((load) => load.sources),
|
|
120
|
-
warnings: loads.flatMap((load) => load.warnings),
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Root-file issues keep their short paths; nested files get a file prefix. */
|
|
125
|
-
function flattenIssues(load: TaskSourceLoad) {
|
|
126
|
-
return load.issues.flatMap(({ file, problems }) =>
|
|
127
|
-
problems.map((problem) => ({
|
|
128
|
-
path: file === "todo.json" ? problem.path : `${file}: ${problem.path}`,
|
|
129
|
-
message: problem.message,
|
|
130
|
-
})),
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Root-file warnings keep their short paths; nested files get a file prefix. */
|
|
135
|
-
function flattenWarnings(load: TaskSourceLoad) {
|
|
136
|
-
return load.warnings.flatMap(({ file, problems }) =>
|
|
137
|
-
problems.map((problem) => ({
|
|
138
|
-
path: file === "todo.json" ? problem.path : `${file}: ${problem.path}`,
|
|
139
|
-
message: problem.message,
|
|
140
|
-
severity: problem.severity,
|
|
141
|
-
})),
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Server-side advisory: a folder-relative `notes` entry should resolve to a
|
|
147
|
-
* real file next to its `todo.json`. A dead reference defeats the point of
|
|
148
|
-
* note linking, so it warns (never errors); https entries are skipped.
|
|
149
|
-
*/
|
|
150
|
-
async function missingNoteIssues(
|
|
151
|
-
document: TodoFile,
|
|
152
|
-
folder: string,
|
|
153
|
-
): Promise<TodoIssue[]> {
|
|
154
|
-
const missing: TodoIssue[] = []
|
|
155
|
-
for (const [index, task] of document.tasks.entries()) {
|
|
156
|
-
const notes = task.notes
|
|
157
|
-
if (!Array.isArray(notes)) continue
|
|
158
|
-
for (const [noteIndex, note] of notes.entries()) {
|
|
159
|
-
if (typeof note !== "string" || /^https?:\/\//i.test(note)) continue
|
|
160
|
-
const target = (note.split("#", 1)[0] ?? "").trim()
|
|
161
|
-
if (!target) continue
|
|
162
|
-
const exists = await stat(resolve(folder, target))
|
|
163
|
-
.then((entry) => entry.isFile())
|
|
164
|
-
.catch(() => false)
|
|
165
|
-
if (!exists) {
|
|
166
|
-
missing.push({
|
|
167
|
-
message: `references a missing note file: ${target}`,
|
|
168
|
-
path: `tasks[${index}].notes[${noteIndex}]`,
|
|
169
|
-
severity: "warning",
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return missing
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function taskCompleted(task: Record<string, unknown>) {
|
|
178
|
-
return task.done === true || task.status === "done"
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function tasksOf(source: TaskSource) {
|
|
182
|
-
return Array.isArray(source.document.tasks) ? source.document.tasks : []
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function sourceTitle(folder: string) {
|
|
186
|
-
return `${basename(folder)} tasks`
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Merged dataset for the workspace list: every valid `todo.json` under the
|
|
191
|
-
* viewed folder becomes one source, root folder first, and every task is
|
|
192
|
-
* tagged with its owning folder so the client can filter by folder chips.
|
|
193
|
-
*/
|
|
194
|
-
function mergedDataset(folder: string, sources: TaskSource[]) {
|
|
195
|
-
const root = sources.find((source) => source.path === "todo.json")
|
|
196
|
-
const title =
|
|
197
|
-
root && typeof root.document.title === "string"
|
|
198
|
-
? root.document.title
|
|
199
|
-
: sourceTitle(folder)
|
|
200
|
-
|
|
201
|
-
const byFolder = new Map<string, TaskSource[]>()
|
|
202
|
-
for (const source of sources) {
|
|
203
|
-
byFolder.set(source.folder, [
|
|
204
|
-
...(byFolder.get(source.folder) ?? []),
|
|
205
|
-
source,
|
|
206
|
-
])
|
|
207
|
-
}
|
|
208
|
-
const ordered = new Map<string, TaskSource[]>()
|
|
209
|
-
ordered.set(folder, byFolder.get(folder) ?? [])
|
|
210
|
-
for (const sourceFolder of [...byFolder.keys()].sort()) {
|
|
211
|
-
const docs = byFolder.get(sourceFolder)
|
|
212
|
-
if (docs && sourceFolder !== folder) ordered.set(sourceFolder, docs)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const datasetSources: Array<{
|
|
216
|
-
id: string
|
|
217
|
-
open: number
|
|
218
|
-
title: string
|
|
219
|
-
total: number
|
|
220
|
-
}> = []
|
|
221
|
-
const tasks: Array<Record<string, unknown>> = []
|
|
222
|
-
for (const [sourceFolder, docs] of ordered) {
|
|
223
|
-
const fileTasks = docs.flatMap(tasksOf)
|
|
224
|
-
const completed = fileTasks.filter(taskCompleted).length
|
|
225
|
-
datasetSources.push({
|
|
226
|
-
id: sourceFolder,
|
|
227
|
-
open: fileTasks.length - completed,
|
|
228
|
-
title: sourceTitle(sourceFolder),
|
|
229
|
-
total: fileTasks.length,
|
|
230
|
-
})
|
|
231
|
-
for (const task of fileTasks) {
|
|
232
|
-
tasks.push({
|
|
233
|
-
...task,
|
|
234
|
-
source: { id: sourceFolder, title: sourceTitle(sourceFolder) },
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const completed = tasks.filter(taskCompleted).length
|
|
240
|
-
return {
|
|
241
|
-
...root?.document,
|
|
242
|
-
$schema:
|
|
243
|
-
typeof root?.document.$schema === "string"
|
|
244
|
-
? root.document.$schema
|
|
245
|
-
: "https://raggle.co/schemas/tasks.json",
|
|
246
|
-
id: typeof root?.document.id === "string" ? root.document.id : "todo",
|
|
247
|
-
project: { name: title },
|
|
248
|
-
sources: datasetSources,
|
|
249
|
-
summary: {
|
|
250
|
-
completed,
|
|
251
|
-
open: tasks.length - completed,
|
|
252
|
-
sources: datasetSources.length,
|
|
253
|
-
},
|
|
254
|
-
tasks,
|
|
255
|
-
title,
|
|
256
|
-
version:
|
|
257
|
-
typeof root?.document.version === "number" ? root.document.version : 1,
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function singleTaskDataset(
|
|
262
|
-
folder: string,
|
|
263
|
-
source: TaskSource,
|
|
264
|
-
task: Record<string, unknown>,
|
|
265
|
-
) {
|
|
266
|
-
const completed = taskCompleted(task)
|
|
267
|
-
return {
|
|
268
|
-
...source.document,
|
|
269
|
-
$schema:
|
|
270
|
-
typeof source.document.$schema === "string"
|
|
271
|
-
? source.document.$schema
|
|
272
|
-
: "https://raggle.co/schemas/tasks.json",
|
|
273
|
-
version:
|
|
274
|
-
typeof source.document.version === "number" ? source.document.version : 1,
|
|
275
|
-
project: { name: sourceTitle(folder) },
|
|
276
|
-
sources: [
|
|
277
|
-
{
|
|
278
|
-
id: source.folder,
|
|
279
|
-
open: completed ? 0 : 1,
|
|
280
|
-
title: sourceTitle(source.folder),
|
|
281
|
-
total: 1,
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
summary: {
|
|
285
|
-
completed: completed ? 1 : 0,
|
|
286
|
-
open: completed ? 0 : 1,
|
|
287
|
-
sources: 1,
|
|
288
|
-
},
|
|
289
|
-
tasks: [
|
|
290
|
-
{
|
|
291
|
-
...task,
|
|
292
|
-
source: { id: source.folder, title: sourceTitle(source.folder) },
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function createTasksPlugin() {
|
|
299
|
-
return {
|
|
300
|
-
...tasksManifest,
|
|
301
|
-
async detect(context: FolderPluginContext): Promise<FolderPluginDetection> {
|
|
302
|
-
const [loads, linked] = await Promise.all([
|
|
303
|
-
loadTaskSources(context.folder),
|
|
304
|
-
loadLinkedTaskSources(context.folder, context.linkedFolders ?? []),
|
|
305
|
-
])
|
|
306
|
-
const merged: TaskSourceLoad = {
|
|
307
|
-
issues: [...loads.issues, ...linked.issues],
|
|
308
|
-
sources: [...loads.sources, ...linked.sources],
|
|
309
|
-
// Warnings are advisory: detection stays "available" with its count.
|
|
310
|
-
warnings: [],
|
|
311
|
-
}
|
|
312
|
-
const noContent =
|
|
313
|
-
merged.sources.length === 0 && merged.issues.length === 0
|
|
314
|
-
if (noContent) return { status: "unavailable" }
|
|
315
|
-
if (merged.issues.length > 0) {
|
|
316
|
-
// Explicit error state: a todo.json exists but is not valid (locally
|
|
317
|
-
// or in a linked folder). The host flags the navigation item.
|
|
318
|
-
return {
|
|
319
|
-
status: "error",
|
|
320
|
-
error: todoIssueSummary(flattenIssues(merged)),
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
count: merged.sources.reduce(
|
|
325
|
-
(sum, source) => sum + tasksOf(source).length,
|
|
326
|
-
0,
|
|
327
|
-
),
|
|
328
|
-
status: "available",
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
async handle(
|
|
332
|
-
context: FolderPluginContext,
|
|
333
|
-
subpath: string,
|
|
334
|
-
): Promise<FolderPluginPayload<unknown>> {
|
|
335
|
-
const [load, linked] = await Promise.all([
|
|
336
|
-
loadTaskSources(context.folder),
|
|
337
|
-
loadLinkedTaskSources(context.folder, context.linkedFolders ?? []),
|
|
338
|
-
])
|
|
339
|
-
const sources = [...load.sources, ...linked.sources]
|
|
340
|
-
const rootIssue = load.issues.find((issue) => issue.file === "todo.json")
|
|
341
|
-
if (rootIssue) {
|
|
342
|
-
// In-band error payload: precise, per-field issues the client renders
|
|
343
|
-
// directly. Throwing would map to a misleading 4xx/5xx instead.
|
|
344
|
-
return {
|
|
345
|
-
kind: "available",
|
|
346
|
-
data: {
|
|
347
|
-
folder: context.folder,
|
|
348
|
-
issues: rootIssue.problems,
|
|
349
|
-
status: "error",
|
|
350
|
-
},
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const warnings = flattenWarnings({
|
|
355
|
-
issues: [],
|
|
356
|
-
sources: [],
|
|
357
|
-
warnings: [...load.warnings, ...linked.warnings],
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
if (subpath === "" || sources.length === 0) {
|
|
361
|
-
// Workspace list: the root folder always owns a source (even with no
|
|
362
|
-
// file) so a missing/empty folder still yields an empty, valid
|
|
363
|
-
// dataset — for the list and for any detail subpath alike.
|
|
364
|
-
return {
|
|
365
|
-
kind: "available",
|
|
366
|
-
data: {
|
|
367
|
-
dataset: mergedDataset(context.folder, sources),
|
|
368
|
-
folder: context.folder,
|
|
369
|
-
status: "available",
|
|
370
|
-
warnings,
|
|
371
|
-
},
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Individual task view: match the subpath against the task id or key
|
|
376
|
-
// across every discovered source (local and linked) and return a
|
|
377
|
-
// single-task dataset.
|
|
378
|
-
const id = subpath.trim()
|
|
379
|
-
// Qualified addresses (`<folderPathId(folder)>@<id|key>`) name their
|
|
380
|
-
// owning folder, so identical file-local ids `todo:1` resolve to the
|
|
381
|
-
// right file. A ref that matches no source is an id that contains "@":
|
|
382
|
-
// it drops through to the legacy bare-id search below. A ref that
|
|
383
|
-
// matches a real source but no task is a broken address and must not
|
|
384
|
-
// silently show a different source's task.
|
|
385
|
-
const qualified = parseQualifiedTaskId(id)
|
|
386
|
-
if (qualified) {
|
|
387
|
-
const source = sources.find((item) =>
|
|
388
|
-
refMatchesFolder(qualified.ref, item.folder),
|
|
389
|
-
)
|
|
390
|
-
if (source) {
|
|
391
|
-
const task = tasksOf(source).find(
|
|
392
|
-
(item) => item.id === qualified.id || item.key === qualified.id,
|
|
393
|
-
)
|
|
394
|
-
return {
|
|
395
|
-
kind: "available",
|
|
396
|
-
data: task
|
|
397
|
-
? {
|
|
398
|
-
dataset: singleTaskDataset(context.folder, source, task),
|
|
399
|
-
folder: context.folder,
|
|
400
|
-
status: "available",
|
|
401
|
-
warnings,
|
|
402
|
-
}
|
|
403
|
-
: {
|
|
404
|
-
folder: context.folder,
|
|
405
|
-
message: `Task not found: ${id}`,
|
|
406
|
-
status: "error",
|
|
407
|
-
},
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
// Legacy bare id/key: first source that contains it wins, via the
|
|
412
|
-
// shared subpath matcher (also used by the minimal reference plugin).
|
|
413
|
-
const bare = matchSubpath(
|
|
414
|
-
sources,
|
|
415
|
-
id,
|
|
416
|
-
tasksOf,
|
|
417
|
-
(item, target) => item.id === target || item.key === target,
|
|
418
|
-
)
|
|
419
|
-
if (bare) {
|
|
420
|
-
return {
|
|
421
|
-
kind: "available",
|
|
422
|
-
data: {
|
|
423
|
-
dataset: singleTaskDataset(context.folder, bare.source, bare.item),
|
|
424
|
-
folder: context.folder,
|
|
425
|
-
status: "available",
|
|
426
|
-
warnings,
|
|
427
|
-
},
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return {
|
|
431
|
-
kind: "available",
|
|
432
|
-
data: {
|
|
433
|
-
folder: context.folder,
|
|
434
|
-
message: `Task not found: ${id}`,
|
|
435
|
-
status: "error",
|
|
436
|
-
},
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
init(context: FolderPluginContext) {
|
|
440
|
-
return {
|
|
441
|
-
files: [
|
|
442
|
-
{ path: "todo.json", content: starterTodoSource(context.folder) },
|
|
443
|
-
],
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
}
|
|
447
|
-
}
|