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.
- 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 +29 -3
- package/dist/cli/src/plugins/external.js +78 -24
- 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 +29 -3
- package/dist/plugins/external.js +78 -24
- 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,392 +0,0 @@
|
|
|
1
|
-
import type { UseQueryResult } from "@tanstack/react-query"
|
|
2
|
-
import type { ReactNode } from "react"
|
|
3
|
-
import { useMemo, useRef } from "react"
|
|
4
|
-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
|
5
|
-
|
|
6
|
-
import type { FolderPluginViewProps } from "@raggle-ai/folder-plugin/client"
|
|
7
|
-
import type { ContentIssue } from "@raggle-ai/ui/components/content-issues"
|
|
8
|
-
import { Button } from "@raggle-ai/ui/components/button"
|
|
9
|
-
import { ContentIssues } from "@raggle-ai/ui/components/content-issues"
|
|
10
|
-
import { toast } from "@raggle-ai/ui/components/sonner"
|
|
11
|
-
import { EntityDetailPage } from "@raggle-ai/ui/pages/entity-detail-page"
|
|
12
|
-
import { EntityListPage } from "@raggle-ai/ui/pages/entity-list-page"
|
|
13
|
-
|
|
14
|
-
import type { TodoDataset, UiTodoItem } from "../data/todo-model"
|
|
15
|
-
import { decodeTodoDataset, todoItems } from "../data/todo-model"
|
|
16
|
-
import { ErrorDetails } from "./error-details"
|
|
17
|
-
|
|
18
|
-
/** Shape of the plugin payload the two task pages read. */
|
|
19
|
-
export type TasksPluginData = {
|
|
20
|
-
dataset?: unknown
|
|
21
|
-
linkedStatus?: "pending" | "ready" | "error"
|
|
22
|
-
folder: string
|
|
23
|
-
issues?: ContentIssue[]
|
|
24
|
-
message?: string
|
|
25
|
-
status?: "available" | "error" | "unavailable"
|
|
26
|
-
warnings?: ContentIssue[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Agent-friendly error context: folder plus the failure text. */
|
|
30
|
-
const errorContext = (folder: string, error: string) =>
|
|
31
|
-
[
|
|
32
|
-
"The Kennel Tasks view for this folder failed. Use the context below to",
|
|
33
|
-
"diagnose the cause, fix the data, then re-check the Tasks tab.",
|
|
34
|
-
"",
|
|
35
|
-
`Folder: ${folder}`,
|
|
36
|
-
"",
|
|
37
|
-
"What failed:",
|
|
38
|
-
"The viewer decodes the merged tasks dataset against a strict schema",
|
|
39
|
-
"(https://raggle.co/schemas/tasks.json). todo.json passed file-level",
|
|
40
|
-
"validation, but at least one field violates the schema, so the decode",
|
|
41
|
-
"threw and the view renders nothing until that field is fixed.",
|
|
42
|
-
"",
|
|
43
|
-
"How to read the failure tree:",
|
|
44
|
-
'- Bracketed keys are a JSON path into todo.json: ["tasks"] -> [7] ->',
|
|
45
|
-
' ["resolution"] means tasks[7].resolution in the source file. The',
|
|
46
|
-
" number is the task's position in the tasks array (tasks from linked",
|
|
47
|
-
" folders come after the local file's). Leaves list every accepted",
|
|
48
|
-
' value, and "Expected X, actual Y" shows what the field holds now.',
|
|
49
|
-
"",
|
|
50
|
-
"Common causes:",
|
|
51
|
-
"- An enum field holds free text. resolution accepts only completed,",
|
|
52
|
-
" duplicate, wont_do, not_reproducible, moved, or null; status and",
|
|
53
|
-
" type are enums too. Move the sentence into a comments entry",
|
|
54
|
-
" (id + body + createdAt) or into description, then set the field to",
|
|
55
|
-
' an allowed value (finished work uses resolution: "completed").',
|
|
56
|
-
"- A required field is missing. Each comments entry needs id, body,",
|
|
57
|
-
" and createdAt (ISO date-time). Each task needs name.",
|
|
58
|
-
"- Shape mismatches: null where a string is required (or the reverse),",
|
|
59
|
-
" dates not in YYYY-MM-DD, timestamps not ISO.",
|
|
60
|
-
"",
|
|
61
|
-
"How to fix and verify:",
|
|
62
|
-
"1. Edit todo.json in the folder above. Fix the exact field named in",
|
|
63
|
-
" the failure tree; do not delete the task.",
|
|
64
|
-
"2. Confirm the fix by re-opening the Tasks tab, or decode the same",
|
|
65
|
-
" payload the viewer uses:",
|
|
66
|
-
` curl -fsS "<kennel-url>/api/plugins/tasks?path=${encodeURIComponent(folder)}"`,
|
|
67
|
-
" then run decodeTodoDataset from @raggle-ai/tasks/todo-model on the",
|
|
68
|
-
" returned .dataset value. Success = no throw.",
|
|
69
|
-
"3. A healthy Tasks tab shows no red error panel and open/completed",
|
|
70
|
-
" counts that match todo.json.",
|
|
71
|
-
"",
|
|
72
|
-
"Failure:",
|
|
73
|
-
error,
|
|
74
|
-
].join("\n")
|
|
75
|
-
|
|
76
|
-
/** Message text of an Error-like failure, or undefined when it has none. */
|
|
77
|
-
const failureText = (error: unknown) =>
|
|
78
|
-
error instanceof Error && error.message ? error.message : undefined
|
|
79
|
-
|
|
80
|
-
function TaskIssues({ issues }: { issues: ContentIssue[] }) {
|
|
81
|
-
return <ContentIssues file="todo.json" issues={issues} />
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function LinkedTaskStatus({
|
|
85
|
-
status,
|
|
86
|
-
retry,
|
|
87
|
-
}: {
|
|
88
|
-
status?: string
|
|
89
|
-
retry: () => void
|
|
90
|
-
}) {
|
|
91
|
-
if (status === "pending")
|
|
92
|
-
return (
|
|
93
|
-
<p role="status">Loading linked tasks… Local tasks remain available.</p>
|
|
94
|
-
)
|
|
95
|
-
if (status === "error")
|
|
96
|
-
return (
|
|
97
|
-
<p role="alert">
|
|
98
|
-
Linked tasks could not load.{" "}
|
|
99
|
-
<button onClick={retry}>Retry linked tasks</button>
|
|
100
|
-
</p>
|
|
101
|
-
)
|
|
102
|
-
return null
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function decodeDataset(data: TasksPluginData | undefined) {
|
|
106
|
-
let dataset: TodoDataset | undefined
|
|
107
|
-
let decodeError: string | undefined
|
|
108
|
-
if (data?.status === "available" && data?.dataset) {
|
|
109
|
-
try {
|
|
110
|
-
dataset = decodeTodoDataset(data.dataset)
|
|
111
|
-
} catch (error) {
|
|
112
|
-
// The dataset is generated server-side, so a decode failure means the
|
|
113
|
-
// source had a shape the validator could not see (e.g. an invalid
|
|
114
|
-
// summary). Surface the schema message instead of hiding it.
|
|
115
|
-
decodeError =
|
|
116
|
-
error instanceof Error && error.message
|
|
117
|
-
? error.message
|
|
118
|
-
: "Could not parse the task file."
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return { dataset, decodeError }
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Complete/reopen a task; persists to its `todo.json` on the server. */
|
|
125
|
-
async function toggleTaskDone(folder: string, id: string): Promise<boolean> {
|
|
126
|
-
const response = await fetch("/api/tasks/toggle", {
|
|
127
|
-
method: "POST",
|
|
128
|
-
headers: { "content-type": "application/json" },
|
|
129
|
-
body: JSON.stringify({ path: folder, id }),
|
|
130
|
-
})
|
|
131
|
-
if (!response.ok) {
|
|
132
|
-
let message = "Could not update task."
|
|
133
|
-
try {
|
|
134
|
-
const payload = (await response.json()) as {
|
|
135
|
-
error?: { message?: string }
|
|
136
|
-
}
|
|
137
|
-
if (payload.error?.message) message = payload.error.message
|
|
138
|
-
} catch {
|
|
139
|
-
// Non-JSON error body; keep the default message.
|
|
140
|
-
}
|
|
141
|
-
throw new Error(message)
|
|
142
|
-
}
|
|
143
|
-
const payload = (await response.json()) as { done?: boolean }
|
|
144
|
-
return payload.done ?? false
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function useTaskToggle(folder: string, folderId: string) {
|
|
148
|
-
const queryClient = useQueryClient()
|
|
149
|
-
return useMutation({
|
|
150
|
-
mutationFn: (id: string) => toggleTaskDone(folder, id),
|
|
151
|
-
onError: (error) => {
|
|
152
|
-
toast.error(
|
|
153
|
-
error instanceof Error ? error.message : "Could not update task.",
|
|
154
|
-
)
|
|
155
|
-
},
|
|
156
|
-
onSuccess: () => {
|
|
157
|
-
void queryClient.invalidateQueries({
|
|
158
|
-
queryKey: ["folder", folderId, "tasks"],
|
|
159
|
-
})
|
|
160
|
-
},
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** Everything the task list page and the task detail page share. */
|
|
165
|
-
export type TaskPageData = {
|
|
166
|
-
/** The noun in "Loading task(s)", "Could not load task(s)", "Retry task(s)". */
|
|
167
|
-
noun: string
|
|
168
|
-
folder: string
|
|
169
|
-
query: UseQueryResult<TasksPluginData> & {
|
|
170
|
-
/** Re-fetch with the linked-folder retry marker set. */
|
|
171
|
-
retryLinked: () => void
|
|
172
|
-
}
|
|
173
|
-
data?: TasksPluginData
|
|
174
|
-
dataset?: TodoDataset
|
|
175
|
-
decodeError?: string
|
|
176
|
-
tasks: UiTodoItem[]
|
|
177
|
-
/** First task of the dataset (the detail page's single task). */
|
|
178
|
-
item: UiTodoItem | null
|
|
179
|
-
/** Complete/reopen a task by its workspace-qualified address. */
|
|
180
|
-
toggle: (id: string) => void
|
|
181
|
-
/** True while a toggle write is in flight. */
|
|
182
|
-
busy: boolean
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* One fetch + decode + toggle pipeline for both task pages. `noun` picks the
|
|
187
|
-
* wording of the loading, error, and retry lines ("task" or "tasks");
|
|
188
|
-
* `subpath` is the task address for the detail page, "" for the list.
|
|
189
|
-
*/
|
|
190
|
-
export function useTaskPage({
|
|
191
|
-
folder,
|
|
192
|
-
folderId,
|
|
193
|
-
request,
|
|
194
|
-
subpath,
|
|
195
|
-
noun,
|
|
196
|
-
}: {
|
|
197
|
-
folder: string
|
|
198
|
-
folderId: string
|
|
199
|
-
request: FolderPluginViewProps["request"]
|
|
200
|
-
subpath: string
|
|
201
|
-
noun: string
|
|
202
|
-
}): TaskPageData {
|
|
203
|
-
const retry = useRef(false)
|
|
204
|
-
const query = useQuery({
|
|
205
|
-
enabled: Boolean(folder),
|
|
206
|
-
gcTime: 5 * 60_000,
|
|
207
|
-
queryFn: ({ signal }) => {
|
|
208
|
-
const headers = retry.current
|
|
209
|
-
? { "x-kennel-retry-linked": "1" }
|
|
210
|
-
: undefined
|
|
211
|
-
retry.current = false
|
|
212
|
-
return request(subpath, { signal, headers }) as Promise<TasksPluginData>
|
|
213
|
-
},
|
|
214
|
-
queryKey: ["folder", folderId, "tasks", subpath],
|
|
215
|
-
staleTime: 15_000,
|
|
216
|
-
refetchInterval: (query) =>
|
|
217
|
-
query.state.data?.linkedStatus === "pending" ? 1_000 : false,
|
|
218
|
-
})
|
|
219
|
-
const data = query.data as TasksPluginData | undefined
|
|
220
|
-
// Stable dataset identity so child lists and the filter memoize cleanly
|
|
221
|
-
// across chip-interaction re-renders.
|
|
222
|
-
const { dataset, decodeError } = useMemo(() => decodeDataset(data), [data])
|
|
223
|
-
const tasks = useMemo(() => todoItems(dataset ?? null), [dataset])
|
|
224
|
-
const toggle = useTaskToggle(folder, folderId)
|
|
225
|
-
return {
|
|
226
|
-
noun,
|
|
227
|
-
folder,
|
|
228
|
-
query: {
|
|
229
|
-
...query,
|
|
230
|
-
retryLinked: () => {
|
|
231
|
-
retry.current = true
|
|
232
|
-
void query.refetch()
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
data,
|
|
236
|
-
dataset,
|
|
237
|
-
decodeError,
|
|
238
|
-
tasks,
|
|
239
|
-
item: tasks[0] ?? null,
|
|
240
|
-
toggle: (id) => toggle.mutate(id),
|
|
241
|
-
busy: toggle.isPending,
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function retryButton(page: TaskPageData, label: string) {
|
|
246
|
-
return (
|
|
247
|
-
<Button
|
|
248
|
-
className="h-7 px-2 text-[13px] text-[#9c3a3a] hover:text-[#6f2020]"
|
|
249
|
-
onClick={() => void page.query.refetch()}
|
|
250
|
-
size="sm"
|
|
251
|
-
type="button"
|
|
252
|
-
variant="ghost"
|
|
253
|
-
>
|
|
254
|
-
{label}
|
|
255
|
-
</Button>
|
|
256
|
-
)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** The task page's failure block, if any: fetch error, validation issues,
|
|
260
|
-
* or a decode failure, in that order. */
|
|
261
|
-
function taskErrorNode(page: TaskPageData, fetchError: string): ReactNode {
|
|
262
|
-
const { data, decodeError, query } = page
|
|
263
|
-
if (query.error) {
|
|
264
|
-
return (
|
|
265
|
-
<ErrorDetails
|
|
266
|
-
action={retryButton(page, `Retry ${page.noun}`)}
|
|
267
|
-
context={() => errorContext(page.folder, fetchError)}
|
|
268
|
-
error={fetchError}
|
|
269
|
-
title={`Could not load ${page.noun}.`}
|
|
270
|
-
/>
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
if (data?.status === "error") {
|
|
274
|
-
if (data.issues?.length) return <TaskIssues issues={data.issues} />
|
|
275
|
-
const message = data.message ?? "todo.json could not be validated."
|
|
276
|
-
return (
|
|
277
|
-
<ErrorDetails
|
|
278
|
-
context={() => errorContext(page.folder, message)}
|
|
279
|
-
error={message}
|
|
280
|
-
title="todo.json could not be validated."
|
|
281
|
-
/>
|
|
282
|
-
)
|
|
283
|
-
}
|
|
284
|
-
if (decodeError) {
|
|
285
|
-
return (
|
|
286
|
-
<ErrorDetails
|
|
287
|
-
context={() => errorContext(page.folder, decodeError ?? "")}
|
|
288
|
-
error={decodeError}
|
|
289
|
-
title="Task data is not valid."
|
|
290
|
-
/>
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
return undefined
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** The task page's no-content line, if any: a missing task address on the
|
|
297
|
-
* detail page, or a folder without a task file on the list page. */
|
|
298
|
-
function taskEmptyNode(
|
|
299
|
-
page: TaskPageData,
|
|
300
|
-
unavailableMessage?: string,
|
|
301
|
-
missingMessage?: string,
|
|
302
|
-
): string | undefined {
|
|
303
|
-
const { data, decodeError, item, query } = page
|
|
304
|
-
if (!data || query.isPending || query.error) return undefined
|
|
305
|
-
const available = data.status === "available"
|
|
306
|
-
const failed = data.status === "error"
|
|
307
|
-
if (
|
|
308
|
-
available &&
|
|
309
|
-
!failed &&
|
|
310
|
-
!item &&
|
|
311
|
-
data.linkedStatus !== "pending" &&
|
|
312
|
-
!decodeError &&
|
|
313
|
-
missingMessage
|
|
314
|
-
) {
|
|
315
|
-
return missingMessage
|
|
316
|
-
}
|
|
317
|
-
if (!available && !failed) {
|
|
318
|
-
return unavailableMessage ?? `No ${page.noun} in this folder.`
|
|
319
|
-
}
|
|
320
|
-
return undefined
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Page scaffold for both task pages: the shared fetch/error chrome
|
|
325
|
-
* (linked status, loading line, fetch error, validation issues, decode
|
|
326
|
-
* errors) rendered through the shared entity page shells, with the page's
|
|
327
|
-
* own header, extra row, and body inside. `unavailableMessage` and
|
|
328
|
-
* `missingMessage` are optional per-page lines for the no-content states.
|
|
329
|
-
* The detail page passes `list` to get the shared "Back to {name}" link.
|
|
330
|
-
*/
|
|
331
|
-
export function TaskPage({
|
|
332
|
-
page,
|
|
333
|
-
list,
|
|
334
|
-
header,
|
|
335
|
-
extra,
|
|
336
|
-
unavailableMessage,
|
|
337
|
-
missingMessage,
|
|
338
|
-
body,
|
|
339
|
-
}: {
|
|
340
|
-
page: TaskPageData
|
|
341
|
-
/** Detail page only: the list this task came from; renders the shared
|
|
342
|
-
* "Back to {name}" link before the status chrome. */
|
|
343
|
-
list?: { href: string; name: string }
|
|
344
|
-
/** Rendered after the linked-task status bar (e.g. a warnings row). */
|
|
345
|
-
header?: ReactNode
|
|
346
|
-
/** Rendered after the linked-task status bar (e.g. a warnings row). */
|
|
347
|
-
extra?: ReactNode
|
|
348
|
-
/** Line for "folder has no task file" (the list page). */
|
|
349
|
-
unavailableMessage?: string
|
|
350
|
-
/** Line for an address that names no task (the detail page). */
|
|
351
|
-
missingMessage?: string
|
|
352
|
-
/** Page body; rendered only when a dataset decoded cleanly. */
|
|
353
|
-
body?: ReactNode
|
|
354
|
-
}) {
|
|
355
|
-
const { data, dataset, decodeError, query } = page
|
|
356
|
-
const fetchError = failureText(query.error) ?? `Could not load ${page.noun}.`
|
|
357
|
-
const available = data?.status === "available"
|
|
358
|
-
const headerNode = (
|
|
359
|
-
<>
|
|
360
|
-
<LinkedTaskStatus retry={query.retryLinked} status={data?.linkedStatus} />
|
|
361
|
-
{header}
|
|
362
|
-
</>
|
|
363
|
-
)
|
|
364
|
-
const errorNode = taskErrorNode(page, fetchError)
|
|
365
|
-
const emptyNode = taskEmptyNode(page, unavailableMessage, missingMessage)
|
|
366
|
-
const bodyNode = available && dataset && !decodeError ? body : null
|
|
367
|
-
return list ? (
|
|
368
|
-
<EntityDetailPage
|
|
369
|
-
error={errorNode}
|
|
370
|
-
extra={extra}
|
|
371
|
-
header={headerNode}
|
|
372
|
-
listHref={list.href}
|
|
373
|
-
listName={list.name}
|
|
374
|
-
loading={query.isPending}
|
|
375
|
-
missing={emptyNode}
|
|
376
|
-
noun={page.noun}
|
|
377
|
-
>
|
|
378
|
-
{bodyNode}
|
|
379
|
-
</EntityDetailPage>
|
|
380
|
-
) : (
|
|
381
|
-
<EntityListPage
|
|
382
|
-
empty={emptyNode}
|
|
383
|
-
error={errorNode}
|
|
384
|
-
extra={extra}
|
|
385
|
-
header={headerNode}
|
|
386
|
-
loading={query.isPending}
|
|
387
|
-
noun={page.noun}
|
|
388
|
-
>
|
|
389
|
-
{bodyNode}
|
|
390
|
-
</EntityListPage>
|
|
391
|
-
)
|
|
392
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react"
|
|
2
|
-
|
|
3
|
-
import { FilterChipPanel } from "@raggle-ai/ui/components/filter-chip-panel"
|
|
4
|
-
|
|
5
|
-
import type { TodoDataset, TodoFilterSelection } from "../data/todo-model.js"
|
|
6
|
-
import { datasetAgents } from "../data/todo-model.js"
|
|
7
|
-
|
|
8
|
-
export type TodoAgentFilterProps = {
|
|
9
|
-
readonly dataset: TodoDataset | null
|
|
10
|
-
/** Selected assignee ids; empty shows tasks for any agent. */
|
|
11
|
-
readonly value: TodoFilterSelection
|
|
12
|
-
readonly onChange: (value: TodoFilterSelection) => void
|
|
13
|
-
readonly className?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Agent filter: expand/collapse chip panel over the assignees in use. */
|
|
17
|
-
export function TodoAgentFilter({
|
|
18
|
-
dataset,
|
|
19
|
-
value,
|
|
20
|
-
onChange,
|
|
21
|
-
className = "",
|
|
22
|
-
}: TodoAgentFilterProps) {
|
|
23
|
-
const agents = useMemo(() => datasetAgents(dataset), [dataset])
|
|
24
|
-
if (!agents.length) return null
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<FilterChipPanel
|
|
28
|
-
ariaLabel="Filter tasks by agent"
|
|
29
|
-
className={className}
|
|
30
|
-
label="Agent"
|
|
31
|
-
onChange={onChange}
|
|
32
|
-
options={agents.map((agent) => ({ value: agent.id, label: agent.name }))}
|
|
33
|
-
selected={value}
|
|
34
|
-
/>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
TodoDataset,
|
|
5
|
-
TodoFilter,
|
|
6
|
-
TodoFilterSelection,
|
|
7
|
-
} from "../data/todo-model.js"
|
|
8
|
-
import { TodoAgentFilter } from "./todo-agent-filter.js"
|
|
9
|
-
import { TodoStatusFilter } from "./todo-status-filter.js"
|
|
10
|
-
import { TodoTagFilter } from "./todo-tag-filter.js"
|
|
11
|
-
|
|
12
|
-
export type TodoFilterBarProps = {
|
|
13
|
-
readonly dataset: TodoDataset | null
|
|
14
|
-
readonly filter: TodoFilter
|
|
15
|
-
readonly onFilterChange: (filter: TodoFilter) => void
|
|
16
|
-
/** Selected tags; empty shows tasks with any tags. */
|
|
17
|
-
readonly tag: TodoFilterSelection
|
|
18
|
-
readonly onTagChange: (tag: TodoFilterSelection) => void
|
|
19
|
-
/** Selected assignee ids; empty shows tasks for any agent. */
|
|
20
|
-
readonly assignee: TodoFilterSelection
|
|
21
|
-
readonly onAssigneeChange: (assignee: TodoFilterSelection) => void
|
|
22
|
-
/** Slot rendered first in the row, e.g. the folder filter bar. */
|
|
23
|
-
readonly leading?: ReactNode
|
|
24
|
-
/** Slot rendered at the right end of the row, e.g. the warnings button. */
|
|
25
|
-
readonly trailing?: ReactNode
|
|
26
|
-
readonly className?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** One horizontal filter line: Status dropdown plus expandable Agent and
|
|
30
|
-
* Tag chip panels, all filterable together. */
|
|
31
|
-
export function TodoFilterBar({
|
|
32
|
-
dataset,
|
|
33
|
-
filter,
|
|
34
|
-
onFilterChange,
|
|
35
|
-
tag,
|
|
36
|
-
onTagChange,
|
|
37
|
-
assignee,
|
|
38
|
-
onAssigneeChange,
|
|
39
|
-
leading,
|
|
40
|
-
trailing,
|
|
41
|
-
className = "",
|
|
42
|
-
}: TodoFilterBarProps) {
|
|
43
|
-
return (
|
|
44
|
-
<div
|
|
45
|
-
className={["flex flex-wrap items-center gap-x-3 gap-y-2", className]
|
|
46
|
-
.filter(Boolean)
|
|
47
|
-
.join(" ")}
|
|
48
|
-
>
|
|
49
|
-
{leading}
|
|
50
|
-
<TodoStatusFilter
|
|
51
|
-
dataset={dataset}
|
|
52
|
-
onChange={onFilterChange}
|
|
53
|
-
value={filter}
|
|
54
|
-
/>
|
|
55
|
-
<TodoAgentFilter
|
|
56
|
-
dataset={dataset}
|
|
57
|
-
onChange={onAssigneeChange}
|
|
58
|
-
value={assignee}
|
|
59
|
-
/>
|
|
60
|
-
<TodoTagFilter dataset={dataset} onChange={onTagChange} value={tag} />
|
|
61
|
-
{trailing ? (
|
|
62
|
-
<div className="ml-auto flex shrink-0">{trailing}</div>
|
|
63
|
-
) : null}
|
|
64
|
-
</div>
|
|
65
|
-
)
|
|
66
|
-
}
|