@dxos/plugin-kanban 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8

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 (256) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +398 -0
  3. package/README.md +1 -1
  4. package/dist/lib/neutral/KanbanArticle-T4CPKAZH.mjs +132 -0
  5. package/dist/lib/neutral/KanbanArticle-T4CPKAZH.mjs.map +7 -0
  6. package/dist/lib/neutral/KanbanPlugin.mjs +44 -0
  7. package/dist/lib/neutral/KanbanPlugin.mjs.map +7 -0
  8. package/dist/lib/neutral/KanbanPlugin.node.mjs +27 -0
  9. package/dist/lib/neutral/KanbanPlugin.node.mjs.map +7 -0
  10. package/dist/lib/neutral/KanbanPlugin.workerd.mjs +21 -0
  11. package/dist/lib/neutral/KanbanPlugin.workerd.mjs.map +7 -0
  12. package/dist/lib/neutral/KanbanSettings-5WOS4CUE.mjs +83 -0
  13. package/dist/lib/neutral/KanbanSettings-5WOS4CUE.mjs.map +7 -0
  14. package/dist/lib/neutral/blueprint-definition-6DV3Q5MC.mjs +15 -0
  15. package/dist/lib/neutral/blueprint-definition-6DV3Q5MC.mjs.map +7 -0
  16. package/dist/lib/neutral/blueprints/index.mjs +8 -0
  17. package/dist/lib/neutral/capabilities/index.mjs +17 -0
  18. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-6ZHHQWO5.mjs +39 -0
  20. package/dist/lib/neutral/chunk-6ZHHQWO5.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-DAKIZO46.mjs +246 -0
  22. package/dist/lib/neutral/chunk-DAKIZO46.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-M5ISZWZU.mjs +8 -0
  24. package/dist/lib/neutral/chunk-M5ISZWZU.mjs.map +7 -0
  25. package/dist/lib/{browser/blueprints/index.mjs → neutral/chunk-ZTQW5KQS.mjs} +4 -5
  26. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
  27. package/dist/lib/neutral/components/index.mjs +243 -0
  28. package/dist/lib/neutral/components/index.mjs.map +7 -0
  29. package/dist/lib/neutral/containers/index.mjs +11 -0
  30. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  31. package/dist/lib/neutral/create-object-DKBSI46K.mjs +40 -0
  32. package/dist/lib/neutral/create-object-DKBSI46K.mjs.map +7 -0
  33. package/dist/lib/{browser/delete-card-VPNVIWOA.mjs → neutral/delete-card-VNAV3CZV.mjs} +6 -14
  34. package/dist/lib/neutral/delete-card-VNAV3CZV.mjs.map +7 -0
  35. package/dist/lib/neutral/delete-card-field-XHOLGS6L.mjs +39 -0
  36. package/dist/lib/neutral/delete-card-field-XHOLGS6L.mjs.map +7 -0
  37. package/dist/lib/neutral/hooks/index.mjs +431 -0
  38. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  39. package/dist/lib/neutral/index.mjs +34 -0
  40. package/dist/lib/neutral/index.mjs.map +7 -0
  41. package/dist/lib/neutral/meta.json +1 -0
  42. package/dist/lib/neutral/meta.mjs +8 -0
  43. package/dist/lib/neutral/meta.mjs.map +7 -0
  44. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +13 -0
  45. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +7 -0
  46. package/dist/lib/neutral/operations/index.mjs +8 -0
  47. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  48. package/dist/lib/neutral/plugin.mjs +16 -0
  49. package/dist/lib/neutral/plugin.mjs.map +7 -0
  50. package/dist/lib/neutral/react-surface-VZEVEJL5.mjs +91 -0
  51. package/dist/lib/neutral/react-surface-VZEVEJL5.mjs.map +7 -0
  52. package/dist/lib/{browser/restore-card-4GG2RYKR.mjs → neutral/restore-card-EKVEPATL.mjs} +6 -14
  53. package/dist/lib/neutral/restore-card-EKVEPATL.mjs.map +7 -0
  54. package/dist/lib/neutral/restore-card-field-TQCTGGNO.mjs +37 -0
  55. package/dist/lib/neutral/restore-card-field-TQCTGGNO.mjs.map +7 -0
  56. package/dist/lib/neutral/testing/index.mjs +62 -0
  57. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  58. package/dist/lib/neutral/translations.mjs +44 -0
  59. package/dist/lib/neutral/translations.mjs.map +7 -0
  60. package/dist/lib/neutral/types/index.mjs +22 -0
  61. package/dist/lib/neutral/types/index.mjs.map +7 -0
  62. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs +42 -0
  63. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs.map +7 -0
  64. package/dist/types/src/KanbanPlugin.d.ts +1 -0
  65. package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
  66. package/dist/types/src/KanbanPlugin.node.d.ts +4 -0
  67. package/dist/types/src/KanbanPlugin.node.d.ts.map +1 -0
  68. package/dist/types/src/KanbanPlugin.test.d.ts +2 -0
  69. package/dist/types/src/KanbanPlugin.test.d.ts.map +1 -0
  70. package/dist/types/src/KanbanPlugin.workerd.d.ts +4 -0
  71. package/dist/types/src/KanbanPlugin.workerd.d.ts.map +1 -0
  72. package/dist/types/src/blueprints/kanban-blueprint.d.ts +2 -2
  73. package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -1
  74. package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/blueprint-definition.d.ts +1 -1
  76. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  78. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  79. package/dist/types/src/capabilities/index.d.ts +9 -2
  80. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  81. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  82. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
  83. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  84. package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -1
  85. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +9 -29
  86. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -1
  87. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +24 -24
  88. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -1
  89. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +2 -3
  90. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -1
  91. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +2 -3
  92. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -1
  93. package/dist/types/src/components/KanbanBoard/context.d.ts +38 -0
  94. package/dist/types/src/components/KanbanBoard/context.d.ts.map +1 -0
  95. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts +6 -0
  96. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts.map +1 -0
  97. package/dist/types/src/containers/{KanbanContainer/KanbanContainer.stories.d.ts → KanbanArticle/KanbanArticle.stories.d.ts} +26 -26
  98. package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts.map +1 -0
  99. package/dist/types/src/containers/KanbanArticle/index.d.ts +2 -0
  100. package/dist/types/src/containers/KanbanArticle/index.d.ts.map +1 -0
  101. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts +13 -0
  102. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts.map +1 -0
  103. package/dist/types/src/containers/KanbanSettings/index.d.ts +2 -0
  104. package/dist/types/src/containers/KanbanSettings/index.d.ts.map +1 -0
  105. package/dist/types/src/containers/index.d.ts +2 -2
  106. package/dist/types/src/containers/index.d.ts.map +1 -1
  107. package/dist/types/src/hooks/index.d.ts +1 -0
  108. package/dist/types/src/hooks/index.d.ts.map +1 -1
  109. package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -1
  110. package/dist/types/src/hooks/useItemsProjection.d.ts +10 -0
  111. package/dist/types/src/hooks/useItemsProjection.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts +2 -0
  113. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts.map +1 -0
  114. package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -1
  115. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +1 -1
  116. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -1
  117. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +1 -1
  118. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -1
  119. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  120. package/dist/types/src/index.d.ts +3 -1
  121. package/dist/types/src/index.d.ts.map +1 -1
  122. package/dist/types/src/meta.d.ts +1 -1
  123. package/dist/types/src/meta.d.ts.map +1 -1
  124. package/dist/types/src/operations/delete-card-field.d.ts +3 -3
  125. package/dist/types/src/operations/delete-card-field.d.ts.map +1 -1
  126. package/dist/types/src/operations/delete-card.d.ts +3 -3
  127. package/dist/types/src/operations/delete-card.d.ts.map +1 -1
  128. package/dist/types/src/operations/index.d.ts +1 -2
  129. package/dist/types/src/operations/index.d.ts.map +1 -1
  130. package/dist/types/src/operations/restore-card-field.d.ts +3 -3
  131. package/dist/types/src/operations/restore-card-field.d.ts.map +1 -1
  132. package/dist/types/src/operations/restore-card.d.ts +3 -3
  133. package/dist/types/src/operations/restore-card.d.ts.map +1 -1
  134. package/dist/types/src/playwright/board-manager.d.ts.map +1 -1
  135. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  136. package/dist/types/src/plugin.d.ts +4 -0
  137. package/dist/types/src/plugin.d.ts.map +1 -0
  138. package/dist/types/src/translations.d.ts +24 -24
  139. package/dist/types/src/translations.d.ts.map +1 -1
  140. package/dist/types/src/types/Kanban.d.ts +66 -9
  141. package/dist/types/src/types/Kanban.d.ts.map +1 -1
  142. package/dist/types/src/{operations/definitions.d.ts → types/KanbanOperation.d.ts} +4 -4
  143. package/dist/types/src/types/KanbanOperation.d.ts.map +1 -0
  144. package/dist/types/src/types/constants.d.ts +3 -3
  145. package/dist/types/src/types/constants.d.ts.map +1 -1
  146. package/dist/types/src/types/index.d.ts +2 -1
  147. package/dist/types/src/types/index.d.ts.map +1 -1
  148. package/dist/types/src/types/schema.d.ts +15 -1
  149. package/dist/types/src/types/schema.d.ts.map +1 -1
  150. package/dist/types/src/types/types.d.ts +2 -2
  151. package/dist/types/src/util/arrangement.d.ts +8 -4
  152. package/dist/types/src/util/arrangement.d.ts.map +1 -1
  153. package/dist/types/tsconfig.tsbuildinfo +1 -1
  154. package/package.json +110 -76
  155. package/src/KanbanPlugin.node.ts +21 -0
  156. package/src/KanbanPlugin.test.ts +31 -0
  157. package/src/KanbanPlugin.tsx +12 -39
  158. package/src/KanbanPlugin.workerd.ts +18 -0
  159. package/src/blueprints/kanban-blueprint.ts +2 -3
  160. package/src/capabilities/artifact-definition.ts +10 -9
  161. package/src/capabilities/blueprint-definition.ts +6 -4
  162. package/src/capabilities/create-object.ts +40 -0
  163. package/src/capabilities/index.ts +9 -2
  164. package/src/capabilities/operation-handler.ts +1 -1
  165. package/src/capabilities/react-surface.tsx +31 -14
  166. package/src/capabilities/undo-mappings.ts +1 -1
  167. package/src/components/KanbanBoard/KanbanBoard.stories.tsx +9 -6
  168. package/src/components/KanbanBoard/KanbanBoard.tsx +22 -51
  169. package/src/components/KanbanBoard/KanbanCard.tsx +75 -60
  170. package/src/components/KanbanBoard/KanbanColumn.tsx +16 -13
  171. package/src/components/KanbanBoard/context.ts +54 -0
  172. package/src/containers/{KanbanContainer/KanbanContainer.stories.tsx → KanbanArticle/KanbanArticle.stories.tsx} +48 -42
  173. package/src/containers/KanbanArticle/KanbanArticle.tsx +179 -0
  174. package/src/containers/KanbanArticle/index.ts +5 -0
  175. package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
  176. package/src/containers/KanbanSettings/index.ts +5 -0
  177. package/src/containers/index.ts +2 -2
  178. package/src/hooks/index.ts +1 -0
  179. package/src/hooks/useEchoChangeCallback.ts +2 -2
  180. package/src/hooks/useItemsProjection.ts +44 -0
  181. package/src/hooks/{useKanbanBoardModel.test.ts → useKanbanBoardModel.browser.test.ts} +14 -19
  182. package/src/hooks/useKanbanBoardModel.ts +19 -7
  183. package/src/hooks/useProjectionModel.ts +4 -4
  184. package/src/index.ts +3 -2
  185. package/src/meta.ts +22 -5
  186. package/src/operations/delete-card-field.ts +11 -16
  187. package/src/operations/delete-card.ts +3 -3
  188. package/src/operations/index.ts +1 -3
  189. package/src/operations/restore-card-field.ts +11 -16
  190. package/src/operations/restore-card.ts +3 -3
  191. package/src/playwright/smoke.spec.ts +3 -3
  192. package/src/plugin.ts +11 -0
  193. package/src/testing/KanbanCardTileSimple.tsx +6 -6
  194. package/src/types/Kanban.ts +86 -23
  195. package/src/{operations/definitions.ts → types/KanbanOperation.ts} +25 -9
  196. package/src/types/index.ts +3 -1
  197. package/src/types/schema.ts +20 -2
  198. package/src/types/types.ts +2 -2
  199. package/src/util/arrangement.test.ts +22 -13
  200. package/src/util/arrangement.ts +25 -15
  201. package/src/vite-env.d.ts +10 -0
  202. package/dist/lib/browser/blueprints/index.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-A3PBV3S5.mjs +0 -105
  204. package/dist/lib/browser/chunk-A3PBV3S5.mjs.map +0 -7
  205. package/dist/lib/browser/delete-card-VPNVIWOA.mjs.map +0 -7
  206. package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs +0 -50
  207. package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs.map +0 -7
  208. package/dist/lib/browser/index.mjs +0 -121
  209. package/dist/lib/browser/index.mjs.map +0 -7
  210. package/dist/lib/browser/meta.json +0 -1
  211. package/dist/lib/browser/operations/index.mjs +0 -13
  212. package/dist/lib/browser/operations/index.mjs.map +0 -7
  213. package/dist/lib/browser/restore-card-4GG2RYKR.mjs.map +0 -7
  214. package/dist/lib/browser/restore-card-field-3T26ACYX.mjs +0 -48
  215. package/dist/lib/browser/restore-card-field-3T26ACYX.mjs.map +0 -7
  216. package/dist/lib/browser/types/index.mjs +0 -100
  217. package/dist/lib/browser/types/index.mjs.map +0 -7
  218. package/dist/lib/node-esm/blueprints/index.mjs +0 -28
  219. package/dist/lib/node-esm/blueprints/index.mjs.map +0 -7
  220. package/dist/lib/node-esm/chunk-6LELYA2G.mjs +0 -106
  221. package/dist/lib/node-esm/chunk-6LELYA2G.mjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  223. package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs +0 -33
  224. package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs.map +0 -7
  225. package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs +0 -51
  226. package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs.map +0 -7
  227. package/dist/lib/node-esm/index.mjs +0 -122
  228. package/dist/lib/node-esm/index.mjs.map +0 -7
  229. package/dist/lib/node-esm/meta.json +0 -1
  230. package/dist/lib/node-esm/operations/index.mjs +0 -14
  231. package/dist/lib/node-esm/operations/index.mjs.map +0 -7
  232. package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs +0 -30
  233. package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs.map +0 -7
  234. package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs +0 -49
  235. package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs.map +0 -7
  236. package/dist/lib/node-esm/types/index.mjs +0 -101
  237. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  238. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +0 -6
  239. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +0 -1
  240. package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +0 -1
  241. package/dist/types/src/containers/KanbanContainer/index.d.ts +0 -2
  242. package/dist/types/src/containers/KanbanContainer/index.d.ts.map +0 -1
  243. package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts +0 -6
  244. package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts.map +0 -1
  245. package/dist/types/src/containers/KanbanViewEditor/index.d.ts +0 -2
  246. package/dist/types/src/containers/KanbanViewEditor/index.d.ts.map +0 -1
  247. package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts +0 -2
  248. package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts.map +0 -1
  249. package/dist/types/src/operations/definitions.d.ts.map +0 -1
  250. package/src/containers/KanbanContainer/KanbanContainer.tsx +0 -96
  251. package/src/containers/KanbanContainer/index.ts +0 -5
  252. package/src/containers/KanbanViewEditor/KanbanViewEditor.tsx +0 -63
  253. package/src/containers/KanbanViewEditor/index.ts +0 -5
  254. /package/dist/lib/{browser/chunk-J5LGTIGS.mjs.map → neutral/blueprints/index.mjs.map} +0 -0
  255. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  256. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
@@ -0,0 +1,179 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Atom, RegistryContext } from '@effect-atom/atom-react';
6
+ import React, { useCallback, useContext, useMemo } from 'react';
7
+
8
+ import { useCapabilities, useOperationInvoker } from '@dxos/app-framework/ui';
9
+ import { AppCapabilities } from '@dxos/app-toolkit';
10
+ import { useSchemaFilter, type AppSurface } from '@dxos/app-toolkit/ui';
11
+ import { Filter, Obj, Query, type Ref, Type } from '@dxos/echo';
12
+ import { useObject, useType } from '@dxos/react-client/echo';
13
+ import { Panel, Toolbar } from '@dxos/react-ui';
14
+ import { getTagFromQuery, getTypeURIFromQuery } from '@dxos/schema';
15
+
16
+ import { KanbanBoard } from '#components';
17
+ import { useEchoChangeCallback, useItemsProjection, useProjectionModel } from '#hooks';
18
+ import { KanbanOperation } from '#types';
19
+ import { Kanban } from '#types';
20
+
21
+ export type KanbanArticleProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
22
+
23
+ export const KanbanArticle = (props: KanbanArticleProps) => {
24
+ // Branch on `kanban.spec.kind`: view-variant runs a typename query through
25
+ // `useProjectionModel`; items-variant dereferences `kanban.spec.items` and
26
+ // uses a stub projection from `useItemsProjection`.
27
+ return Kanban.isKanbanItems(props.subject) ? (
28
+ <ItemsKanbanArticle {...props} subject={props.subject} />
29
+ ) : (
30
+ <ViewKanbanArticle {...props} />
31
+ );
32
+ };
33
+
34
+ const ViewKanbanArticle = ({ role, subject: object }: KanbanArticleProps) => {
35
+ const registry = useContext(RegistryContext);
36
+ const schemas = useCapabilities(AppCapabilities.Schema);
37
+ const db = Obj.getDatabase(object);
38
+ const { invokePromise } = useOperationInvoker();
39
+ const [view] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
40
+ const typeUri = view?.query ? getTypeURIFromQuery(view.query.ast) : undefined;
41
+ const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
42
+
43
+ const schemaFromDb = useType(db, typeUri);
44
+ const cardSchema = useMemo(
45
+ () => schemaFromDb ?? schemas.flat().find((schema) => Type.getURI(schema) === typeUri),
46
+ [schemaFromDb, schemas, typeUri],
47
+ );
48
+
49
+ const baseFilter = useSchemaFilter(cardSchema);
50
+ const items = useMemo(() => {
51
+ if (!db) {
52
+ return null;
53
+ }
54
+ const query = tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter);
55
+ return db.query(query).atom;
56
+ }, [db, baseFilter, tag]);
57
+
58
+ const projection = useProjectionModel(cardSchema, object, registry);
59
+ const change = useEchoChangeCallback(object);
60
+
61
+ const pivotFieldId = view?.projection?.pivotFieldId;
62
+ const columnFieldPath =
63
+ projection && pivotFieldId ? projection.tryGetFieldProjection(pivotFieldId)?.props.property : undefined;
64
+
65
+ const handleCardAdd = useCallback(
66
+ (columnValue: string | undefined) => {
67
+ if (db && cardSchema && columnFieldPath) {
68
+ const card = Obj.make(Type.assertObject(cardSchema), {
69
+ [columnFieldPath]: columnValue,
70
+ });
71
+ db.add(card);
72
+ return card.id;
73
+ }
74
+ },
75
+ [db, cardSchema, columnFieldPath],
76
+ );
77
+
78
+ const handleCardRemove = useCallback(
79
+ (card: { id: string }) => {
80
+ void invokePromise(KanbanOperation.DeleteCard, { card });
81
+ },
82
+ [invokePromise],
83
+ );
84
+
85
+ if (!object || !db || !items || !projection || !change) {
86
+ return null;
87
+ }
88
+
89
+ return (
90
+ <Panel.Root role={role}>
91
+ <Panel.Toolbar asChild>
92
+ <Toolbar.Root />
93
+ </Panel.Toolbar>
94
+ <KanbanBoard.Root
95
+ kanban={object}
96
+ projection={projection}
97
+ items={items}
98
+ change={change}
99
+ onCardAdd={handleCardAdd}
100
+ onCardRemove={handleCardRemove}
101
+ >
102
+ <Panel.Content asChild>
103
+ <KanbanBoard.Content />
104
+ </Panel.Content>
105
+ </KanbanBoard.Root>
106
+ </Panel.Root>
107
+ );
108
+ };
109
+
110
+ type ItemsKanbanArticleProps = Omit<KanbanArticleProps, 'subject'> & { subject: Kanban.KanbanItems };
111
+
112
+ const ItemsKanbanArticle = ({ role, subject: object }: ItemsKanbanArticleProps) => {
113
+ const db = Obj.getDatabase(object);
114
+ const projection = useItemsProjection(object);
115
+ const change = useEchoChangeCallback(object);
116
+
117
+ // TODO(wittjosiah): pass refs (not loaded objects) through to the kanban
118
+ // board and let `KanbanCard` subscribe to its own ref via `useObject`.
119
+ // Today this atom subscribes to *every* item — any one changing causes the
120
+ // container (and the model's per-column atoms) to recompute. With cards
121
+ // subscribing themselves, the container only needs the refs and the
122
+ // per-card render is independent. Requires:
123
+ // - `KanbanCard` to accept `Ref<Obj.Unknown>` as `data` and call
124
+ // `useObject(ref)` internally.
125
+ // - The model to handle a ref-bearing item shape (id from
126
+ // `ref.dxn.asEchoDXN()?.echoUri`) and use arrangement-only ordering
127
+ // for items-variant (no pivot-value fallback, since refs don't expose
128
+ // the pivot field without loading).
129
+ // - `Mosaic.isItem` to accept the ref wrapper alongside `Obj.isObject`.
130
+ const itemsAtom = useMemo(
131
+ () =>
132
+ Atom.make((get) => {
133
+ const out: Obj.Unknown[] = [];
134
+ for (const ref of object.spec.items as ReadonlyArray<Ref.Ref<Obj.Unknown>>) {
135
+ const target = get(Obj.atom(ref));
136
+ if (target == null) {
137
+ continue;
138
+ }
139
+ // Drop soft-deleted cards (e.g. Trello-closed cards). The ref
140
+ // stays in `spec.items` so arrangement is preserved, but the card
141
+ // shouldn't render.
142
+ if (Obj.isDeleted(target)) {
143
+ continue;
144
+ }
145
+ out.push(target as unknown as Obj.Unknown);
146
+ }
147
+ return out;
148
+ }),
149
+ [object.spec.items],
150
+ );
151
+
152
+ const handleCardRemove = useCallback(() => undefined, []);
153
+
154
+ if (!object || !db || !change) {
155
+ return null;
156
+ }
157
+
158
+ // TODO(wittjosiah): wire `onCardAdd` to the create-object flow so
159
+ // users can add items directly from the kanban (currently the column's
160
+ // "+" button is hidden because `onCardAdd` is undefined).
161
+ return (
162
+ <Panel.Root role={role}>
163
+ <Panel.Toolbar asChild>
164
+ <Toolbar.Root />
165
+ </Panel.Toolbar>
166
+ <KanbanBoard.Root
167
+ kanban={object}
168
+ projection={projection}
169
+ items={itemsAtom}
170
+ change={change}
171
+ onCardRemove={handleCardRemove}
172
+ >
173
+ <Panel.Content asChild>
174
+ <KanbanBoard.Content />
175
+ </Panel.Content>
176
+ </KanbanBoard.Root>
177
+ </Panel.Root>
178
+ );
179
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export { KanbanArticle as default } from './KanbanArticle';
@@ -0,0 +1,94 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import React, { useCallback, useContext, useMemo } from 'react';
7
+
8
+ import { type AppSurface } from '@dxos/app-toolkit/ui';
9
+ import { Obj } from '@dxos/echo';
10
+ import { Format } from '@dxos/echo/Format';
11
+ import { useObject, useType } from '@dxos/react-client/echo';
12
+ import { Form, type FormFieldMap, SelectField } from '@dxos/react-ui-form';
13
+ import { getTypeURIFromQuery } from '@dxos/schema';
14
+
15
+ import { useProjectionModel } from '#hooks';
16
+ import { type Kanban, KanbanSettingsSchema, KanbanViewSettingsSchema, UNCATEGORIZED_VALUE } from '#types';
17
+
18
+ export type KanbanSettingsProps = AppSurface.ObjectPropertiesProps<Kanban.Kanban>;
19
+
20
+ /**
21
+ * Settings panel for a Kanban. Renders fields common to every kanban
22
+ * (currently the "Hide uncategorized column" toggle); for view-variant
23
+ * kanbans an additional "Column field" picker drives the View's pivot
24
+ * field. Items-variant kanbans use a hardcoded `spec.pivotField`, so that
25
+ * field is omitted there.
26
+ */
27
+ export const KanbanSettings = ({ subject: object }: KanbanSettingsProps) => {
28
+ const registry = useContext(RegistryContext);
29
+ const db = Obj.getDatabase(object);
30
+ const isView = object.spec.kind === 'view';
31
+ const [view, updateView] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
32
+ const [, updateKanban] = useObject(object);
33
+ const currentTypeUri = view?.query ? getTypeURIFromQuery(view.query.ast) : undefined;
34
+ const schema = useType(db, currentTypeUri);
35
+ const projection = useProjectionModel(schema, object, registry);
36
+
37
+ const fieldProjections = projection?.getFieldProjections() ?? [];
38
+ const selectFields = useMemo(
39
+ () =>
40
+ fieldProjections
41
+ .filter((field) => field.props.format === Format.TypeFormat.SingleSelect)
42
+ .map(({ field }) => ({ value: field.id, label: field.path })),
43
+ [fieldProjections],
44
+ );
45
+
46
+ const hideUncategorized = object.arrangement.columns[UNCATEGORIZED_VALUE]?.hidden ?? false;
47
+
48
+ const handleValuesChanged = useCallback(
49
+ (values: Partial<{ columnFieldId: string; hideUncategorized: boolean }>) => {
50
+ if (isView && values.columnFieldId != null) {
51
+ updateView((view) => {
52
+ view.projection.pivotFieldId = values.columnFieldId!;
53
+ });
54
+ }
55
+ if (values.hideUncategorized !== undefined) {
56
+ updateKanban((kanban) => {
57
+ const existing = kanban.arrangement.columns[UNCATEGORIZED_VALUE];
58
+ if (existing) {
59
+ existing.hidden = values.hideUncategorized;
60
+ } else {
61
+ kanban.arrangement.columns[UNCATEGORIZED_VALUE] = {
62
+ ids: [],
63
+ hidden: values.hideUncategorized,
64
+ };
65
+ }
66
+ });
67
+ }
68
+ },
69
+ [isView, updateView, updateKanban],
70
+ );
71
+
72
+ const initialValues = useMemo(
73
+ () => ({
74
+ ...(isView ? { columnFieldId: view?.projection.pivotFieldId } : {}),
75
+ hideUncategorized,
76
+ }),
77
+ [isView, view?.projection.pivotFieldId, hideUncategorized],
78
+ );
79
+
80
+ const fieldMap: FormFieldMap = useMemo(
81
+ () => ({ columnFieldId: (props) => <SelectField {...props} options={selectFields} /> }),
82
+ [selectFields],
83
+ );
84
+
85
+ // Schema is picked by `kanban.spec.kind` — they have different shapes,
86
+ // so cast for `Form.Root`'s single-schema prop.
87
+ const settingsSchema = (isView ? KanbanViewSettingsSchema : KanbanSettingsSchema) as any;
88
+
89
+ return (
90
+ <Form.Root schema={settingsSchema} values={initialValues} fieldMap={fieldMap} onValuesChanged={handleValuesChanged}>
91
+ <Form.FieldSet />
92
+ </Form.Root>
93
+ );
94
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export { KanbanSettings as default } from './KanbanSettings';
@@ -4,5 +4,5 @@
4
4
 
5
5
  import { type ComponentType, lazy } from 'react';
6
6
 
7
- export const KanbanContainer: ComponentType<any> = lazy(() => import('./KanbanContainer'));
8
- export const KanbanViewEditor: ComponentType<any> = lazy(() => import('./KanbanViewEditor'));
7
+ export const KanbanArticle: ComponentType<any> = lazy(() => import('./KanbanArticle'));
8
+ export const KanbanSettings: ComponentType<any> = lazy(() => import('./KanbanSettings'));
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './useEchoChangeCallback';
6
+ export * from './useItemsProjection';
6
7
  export * from './useKanbanBoardModel';
7
8
  export * from './useKanbanColumnEventHandler';
8
9
  export * from './useKanbanItemEventHandler';
@@ -13,9 +13,9 @@ import { type Kanban, type KanbanChangeCallback } from '#types';
13
13
  * Use this when the kanban and items are stored in the ECHO database.
14
14
  */
15
15
  export const createEchoChangeCallback = <T extends Obj.Unknown>(kanban: Kanban.Kanban): KanbanChangeCallback<T> => ({
16
- kanban: (mutate) => Obj.change(kanban, (kanban) => mutate(kanban)),
16
+ kanban: (mutate) => Obj.update(kanban, (kanban) => mutate(kanban)),
17
17
  setItemField: (item, field, value) => {
18
- Obj.change(item, (item: any) => {
18
+ Obj.update(item, (item: any) => {
19
19
  item[field] = value;
20
20
  });
21
21
  },
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import { useMemo } from 'react';
7
+
8
+ import type { ProjectionModel } from '@dxos/schema';
9
+
10
+ import { type Kanban } from '#types';
11
+
12
+ /**
13
+ * Minimal `ProjectionModel` for `spec.kind === 'items'` (no View). Supplies `pivotField`
14
+ * and column options from `arrangement.columns` keys—written by sync so columns exist
15
+ * before refs hydrate. Stubs `getFieldProjections` / `getHiddenProperties` for shared
16
+ * board/card UI; hides the pivot on the card body (column shows it); Expando cards render title only.
17
+ */
18
+ export const useItemsProjection = (kanban: Kanban.KanbanItems): ProjectionModel => {
19
+ return useMemo(() => {
20
+ const pivotField = kanban.spec.pivotField;
21
+
22
+ const optionIds = Object.keys(kanban.arrangement?.columns ?? {});
23
+ const options = optionIds.map((id) => ({ id, title: id, color: 'neutral' as const }));
24
+
25
+ const fieldProjection: any = {
26
+ field: { id: pivotField, path: pivotField },
27
+ props: { property: pivotField, options },
28
+ };
29
+
30
+ const fields = Atom.make(() => [fieldProjection.field]);
31
+
32
+ const stub: Pick<ProjectionModel, 'tryGetFieldProjection' | 'getFieldProjections' | 'getHiddenProperties'> & {
33
+ fields: typeof fields;
34
+ } = {
35
+ fields,
36
+ tryGetFieldProjection: (id: string) => (id === pivotField ? fieldProjection : undefined),
37
+ getFieldProjections: () => [],
38
+ getHiddenProperties: () => [pivotField],
39
+ };
40
+
41
+ // TODO(wittjosiah): Refactor ProjectionModel to be an interface that we can fulfill.
42
+ return stub as unknown as ProjectionModel;
43
+ }, [kanban.arrangement?.columns, kanban.spec.pivotField]);
44
+ };
@@ -7,10 +7,10 @@ import { act, renderHook } from '@testing-library/react';
7
7
  import * as Schema from 'effect/Schema';
8
8
  import { beforeEach, describe, test } from 'vitest';
9
9
 
10
- import { Filter, JsonSchema, Obj, Query, Type } from '@dxos/echo';
11
- import { type View } from '@dxos/echo';
12
- import { Format, FormatAnnotation, PropertyMetaAnnotationId } from '@dxos/echo/internal';
13
- import { ObjectId } from '@dxos/keys';
10
+ import { DXN, Filter, JsonSchema, Obj, Query, Type, type View } from '@dxos/echo';
11
+ import { Format, FormatAnnotation } from '@dxos/echo/Format';
12
+ import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
13
+ import { EntityId } from '@dxos/keys';
14
14
  import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
15
15
 
16
16
  import { Kanban } from '#types';
@@ -36,14 +36,9 @@ const KanbanTaskSchema = Schema.Struct({
36
36
  }),
37
37
  Schema.optional,
38
38
  ),
39
- }).pipe(
40
- Type.object({
41
- typename: 'com.example.type.kanban-task',
42
- version: '0.1.0',
43
- }),
44
- );
39
+ }).pipe(Type.makeObject(DXN.make('com.example.type.kanbanTask', '0.1.0')));
45
40
 
46
- type KanbanTask = Schema.Schema.Type<typeof KanbanTaskSchema>;
41
+ type KanbanTask = Type.InstanceType<typeof KanbanTaskSchema>;
47
42
 
48
43
  describe('useKanbanBoardModel', () => {
49
44
  let registry: Registry.Registry;
@@ -146,7 +141,7 @@ describe('useKanbanBoardModel', () => {
146
141
  expect(orderBefore).toEqual(['__uncategorized__', 'a', 'b']);
147
142
 
148
143
  act(() => {
149
- Obj.change(kanban, (kanban) => {
144
+ Obj.update(kanban, (kanban) => {
150
145
  kanban.arrangement.order = ['b', 'a'];
151
146
  });
152
147
  });
@@ -160,15 +155,15 @@ describe('useKanbanBoardModel', () => {
160
155
 
161
156
  test('getItems returns items in column ordered by arrangement ids', ({ expect }) => {
162
157
  const item1 = Obj.make(KanbanTaskSchema, {
163
- id: ObjectId.random(),
158
+ id: EntityId.random(),
164
159
  status: 'a',
165
160
  });
166
161
  const item2 = Obj.make(KanbanTaskSchema, {
167
- id: ObjectId.random(),
162
+ id: EntityId.random(),
168
163
  status: 'a',
169
164
  });
170
165
  const item3 = Obj.make(KanbanTaskSchema, {
171
- id: ObjectId.random(),
166
+ id: EntityId.random(),
172
167
  status: 'a',
173
168
  });
174
169
  const items: KanbanTask[] = [item1, item2, item3];
@@ -187,7 +182,7 @@ describe('useKanbanBoardModel', () => {
187
182
  });
188
183
 
189
184
  act(() => {
190
- Obj.change(kanban, (kanban) => {
185
+ Obj.update(kanban, (kanban) => {
191
186
  kanban.arrangement.columns['a'] = {
192
187
  ids: [item3.id, item1.id, item2.id],
193
188
  };
@@ -201,11 +196,11 @@ describe('useKanbanBoardModel', () => {
201
196
 
202
197
  test('subscribing to one column items atom does not fire when another column changes', ({ expect }) => {
203
198
  const itemA = Obj.make(KanbanTaskSchema, {
204
- id: ObjectId.random(),
199
+ id: EntityId.random(),
205
200
  status: 'a',
206
201
  });
207
202
  const itemB = Obj.make(KanbanTaskSchema, {
208
- id: ObjectId.random(),
203
+ id: EntityId.random(),
209
204
  status: 'b',
210
205
  });
211
206
  const items: KanbanTask[] = [itemA, itemB];
@@ -225,7 +220,7 @@ describe('useKanbanBoardModel', () => {
225
220
  });
226
221
 
227
222
  act(() => {
228
- Obj.change(kanban, (kanban) => {
223
+ Obj.update(kanban, (kanban) => {
229
224
  kanban.arrangement.columns['b'] = { ids: [itemB.id] };
230
225
  });
231
226
  });
@@ -6,7 +6,6 @@ import { Atom, type Registry } from '@effect-atom/atom-react';
6
6
  import { useMemo } from 'react';
7
7
 
8
8
  import { Obj } from '@dxos/echo';
9
- import { AtomObj } from '@dxos/echo-atom';
10
9
  import type { BoardModel } from '@dxos/react-ui-mosaic';
11
10
  import type { ProjectionModel } from '@dxos/schema';
12
11
 
@@ -36,16 +35,29 @@ export function useKanbanBoardModel<T extends BaseKanbanItem = BaseKanbanItem>(
36
35
  registry: Registry.Registry,
37
36
  ): BoardModel<ColumnStructure, T> {
38
37
  // Source atoms: reactive reads from the kanban object; items come from the passed-in atom (e.g. AtomQuery or in-memory).
39
- const arrangementAtom = useMemo(() => AtomObj.makeProperty(kanban, 'arrangement'), [kanban]);
38
+ const arrangementAtom = useMemo(() => Obj.atomProperty(kanban, 'arrangement'), [kanban]);
40
39
  const viewSnapshotAtom = useMemo(
41
- () => (kanban?.view ? AtomObj.make(kanban.view) : Atom.make<undefined>(() => undefined)),
42
- [kanban?.view],
40
+ () =>
41
+ kanban?.spec?.kind === 'view' && kanban.spec.view
42
+ ? Obj.atom(kanban.spec.view)
43
+ : Atom.make<undefined>(() => undefined),
44
+ [kanban?.spec],
43
45
  );
44
46
 
45
- /** Only changes when view.projection.pivotFieldId changes; keeps columns from firing on other view updates. */
47
+ /**
48
+ * Only changes when the discriminator-relevant pivot input changes.
49
+ * View-variant: derived from `view.projection.pivotFieldId`.
50
+ * Items-variant: the kanban's `spec.pivotField` (the property name itself acts as the field id).
51
+ */
46
52
  const pivotFieldIdAtom = useMemo(
47
- () => Atom.make((get) => get(viewSnapshotAtom)?.projection?.pivotFieldId as string | undefined),
48
- [viewSnapshotAtom],
53
+ () =>
54
+ Atom.make((get) => {
55
+ if (kanban?.spec.kind === 'items') {
56
+ return kanban.spec.pivotField;
57
+ }
58
+ return get(viewSnapshotAtom)?.projection?.pivotFieldId as string | undefined;
59
+ }),
60
+ [kanban?.spec, viewSnapshotAtom],
49
61
  );
50
62
 
51
63
  // Effective per-column ids: from kanban.arrangement.columns; empty when arrangement has no columns.
@@ -30,17 +30,17 @@ export const useProjectionModel = <S extends Type.AnyEntity>(
30
30
 
31
31
  useAsyncEffect(
32
32
  async (controller) => {
33
- if (!schema || !kanban) {
33
+ if (!schema || !kanban || kanban.spec.kind !== 'view') {
34
34
  return;
35
35
  }
36
36
  try {
37
- const view = await kanban.view.load();
37
+ const view = await kanban.spec.view.load();
38
38
  if (controller.signal.aborted) {
39
39
  return;
40
40
  }
41
41
 
42
- const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
43
- const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
42
+ const jsonSchema = Type.getDatabase(schema) != null ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
43
+ const change = createEchoChangeCallback(view, Type.getDatabase(schema) != null ? schema : undefined);
44
44
 
45
45
  const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
46
46
  projection.normalizeView();
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './blueprints';
5
6
  export * from './meta';
6
-
7
- export * from './KanbanPlugin';
7
+ export * from './operations';
8
+ export * from './types';
package/src/meta.ts CHANGED
@@ -2,18 +2,35 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Plugin } from '@dxos/app-framework';
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { DXN } from '@dxos/echo';
6
7
  import { trim } from '@dxos/util';
7
8
 
8
- export const meta: Plugin.Meta = {
9
- id: 'org.dxos.plugin.kanban',
9
+ export const meta = Plugin.makeMeta({
10
+ key: DXN.make('org.dxos.plugin.kanban'),
10
11
  name: 'Kanban',
12
+ author: 'DXOS',
11
13
  description: trim`
12
14
  Visual project management using customizable kanban boards to track workflow progress.
13
- Organize table data into columns, drag and drop items between stages, and trigger automations based on status changes.
15
+ Organize any ECHO type into drag-and-drop columns defined by a single-select field, move cards
16
+ between stages, and reorder both columns and cards — all changes are persisted to ECHO and
17
+ replicated to collaborators in real time.
18
+
19
+ Kanban boards come in two variants: view-variant boards source their cards through a linked
20
+ View query (any ECHO type can be pivoted on a single-select field), while items-variant boards
21
+ own their cards explicitly and are used by external sync integrations such as Linear or GitHub.
22
+ Both variants share the same arrangement storage and drag-and-drop mechanics.
23
+
24
+ Board settings let users choose which single-select field drives the column layout and
25
+ optionally hide the uncategorized column for items that have no pivot value.
26
+ Field-level undo support allows deleted card fields to be restored without data loss.
27
+
28
+ The plugin contributes a Kanban blueprint that exposes board creation and card management
29
+ to AI agents, enabling automated project tracking workflows.
14
30
  `,
15
31
  icon: 'ph--kanban--regular',
16
32
  iconHue: 'green',
17
33
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-kanban',
34
+ spec: 'PLUGIN.mdl',
18
35
  screenshots: ['https://dxos.network/plugin-details-kanban-dark.png'],
19
- };
36
+ });
@@ -3,34 +3,29 @@
3
3
  import * as Effect from 'effect/Effect';
4
4
 
5
5
  import { Capabilities, Capability } from '@dxos/app-framework';
6
- import { JsonSchema, Obj } from '@dxos/echo';
7
- import { type EchoSchema } from '@dxos/echo/internal';
6
+ import { Operation } from '@dxos/compute';
7
+ import { Obj, Type } from '@dxos/echo';
8
8
  import { invariant } from '@dxos/invariant';
9
- import { Operation } from '@dxos/operation';
10
- import { ProjectionModel, createEchoChangeCallback, getTypenameFromQuery } from '@dxos/schema';
9
+ import { ProjectionModel, createEchoChangeCallback, getTypeURIFromQuery } from '@dxos/schema';
11
10
 
12
- import { DeleteCardField } from './definitions';
11
+ import { KanbanOperation } from '../types';
13
12
 
14
- const handler: Operation.WithHandler<typeof DeleteCardField> = DeleteCardField.pipe(
13
+ const handler: Operation.WithHandler<typeof KanbanOperation.DeleteCardField> = KanbanOperation.DeleteCardField.pipe(
15
14
  Operation.withHandler(
16
15
  Effect.fnUntraced(function* ({ view, fieldId }) {
17
16
  const registry = yield* Capability.get(Capabilities.AtomRegistry);
18
17
  const db = Obj.getDatabase(view);
19
18
  invariant(db, 'Database not found');
20
- const schema = yield* Effect.promise(() =>
21
- db.schemaRegistry
22
- .query({
23
- typename: getTypenameFromQuery(view.query.ast)!,
24
- location: ['database', 'runtime'],
25
- })
26
- .first(),
27
- );
19
+ const types = yield* Effect.sync(() => db.graph.registry.list().filter(Type.isType));
20
+ const schema = types.find((t) => Type.getURI(t) === getTypeURIFromQuery(view.query.ast));
21
+ invariant(schema, 'Schema not found');
28
22
 
23
+ invariant(Type.isType(schema), 'expected stored Type.Type for card schema');
29
24
  const projection = new ProjectionModel({
30
25
  registry,
31
26
  view,
32
- baseSchema: JsonSchema.toJsonSchema(schema),
33
- change: createEchoChangeCallback(view, schema as EchoSchema),
27
+ baseSchema: schema.jsonSchema,
28
+ change: createEchoChangeCallback(view, schema),
34
29
  });
35
30
 
36
31
  const result = projection.deleteFieldProjection(fieldId);
@@ -2,13 +2,13 @@
2
2
 
3
3
  import * as Effect from 'effect/Effect';
4
4
 
5
+ import { Operation } from '@dxos/compute';
5
6
  import { Obj } from '@dxos/echo';
6
7
  import { invariant } from '@dxos/invariant';
7
- import { Operation } from '@dxos/operation';
8
8
 
9
- import { DeleteCard } from './definitions';
9
+ import { KanbanOperation } from '../types';
10
10
 
11
- const handler: Operation.WithHandler<typeof DeleteCard> = DeleteCard.pipe(
11
+ const handler: Operation.WithHandler<typeof KanbanOperation.DeleteCard> = KanbanOperation.DeleteCard.pipe(
12
12
  Operation.withHandler(({ card }) =>
13
13
  Effect.sync(() => {
14
14
  const db = Obj.getDatabase(card);
@@ -1,8 +1,6 @@
1
1
  // Copyright 2025 DXOS.org
2
2
 
3
- import { OperationHandlerSet } from '@dxos/operation';
4
-
5
- export * as KanbanOperation from './definitions';
3
+ import { OperationHandlerSet } from '@dxos/compute';
6
4
 
7
5
  export const KanbanOperationHandlerSet = OperationHandlerSet.lazy(
8
6
  () => import('./delete-card'),