@dxos/plugin-kanban 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731

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 (285) 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-V3UCZQB6.mjs +133 -0
  5. package/dist/lib/neutral/KanbanArticle-V3UCZQB6.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/{browser/KanbanViewEditor-IH5CJ6BW.mjs → neutral/KanbanSettings-Q2RJW4U4.mjs} +38 -21
  13. package/dist/lib/neutral/KanbanSettings-Q2RJW4U4.mjs.map +7 -0
  14. package/dist/lib/{browser/blueprint-definition-HFEKGFJK.mjs → neutral/blueprint-definition-FHVIEKTQ.mjs} +4 -6
  15. package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs.map +7 -0
  16. package/dist/lib/{browser → neutral}/blueprints/index.mjs +1 -1
  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-PNGVV67W.mjs +246 -0
  22. package/dist/lib/neutral/chunk-PNGVV67W.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-UWUD7L3O.mjs +8 -0
  24. package/dist/lib/neutral/chunk-UWUD7L3O.mjs.map +7 -0
  25. package/dist/lib/{browser/chunk-TLYZSC7O.mjs → neutral/chunk-ZTQW5KQS.mjs} +3 -6
  26. package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
  27. package/dist/lib/{browser/KanbanContainer-BCXSJ6KS.mjs → neutral/components/index.mjs} +81 -142
  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/neutral/delete-card-4QF3TKXO.mjs +24 -0
  34. package/dist/lib/neutral/delete-card-4QF3TKXO.mjs.map +7 -0
  35. package/dist/lib/neutral/delete-card-field-5M4ZHKLO.mjs +39 -0
  36. package/dist/lib/neutral/delete-card-field-5M4ZHKLO.mjs.map +7 -0
  37. package/dist/lib/{node-esm/chunk-CSL3HF2X.mjs → neutral/hooks/index.mjs} +128 -82
  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/meta.json +1 -0
  41. package/dist/lib/neutral/meta.mjs +8 -0
  42. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +13 -0
  43. package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +7 -0
  44. package/dist/lib/neutral/operations/index.mjs +8 -0
  45. package/dist/lib/neutral/plugin.mjs +16 -0
  46. package/dist/lib/neutral/plugin.mjs.map +7 -0
  47. package/dist/lib/{browser/react-surface-FOMOGFVW.mjs → neutral/react-surface-MROAQQQ6.mjs} +30 -38
  48. package/dist/lib/neutral/react-surface-MROAQQQ6.mjs.map +7 -0
  49. package/dist/lib/neutral/restore-card-5NI7HMIJ.mjs +21 -0
  50. package/dist/lib/neutral/restore-card-5NI7HMIJ.mjs.map +7 -0
  51. package/dist/lib/neutral/restore-card-field-QM2R67J5.mjs +37 -0
  52. package/dist/lib/neutral/restore-card-field-QM2R67J5.mjs.map +7 -0
  53. package/dist/lib/neutral/testing/index.mjs +62 -0
  54. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  55. package/dist/lib/neutral/translations.mjs +44 -0
  56. package/dist/lib/neutral/translations.mjs.map +7 -0
  57. package/dist/lib/{browser → neutral}/types/index.mjs +7 -5
  58. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs +42 -0
  59. package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs.map +7 -0
  60. package/dist/types/src/KanbanPlugin.d.ts +1 -0
  61. package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
  62. package/dist/types/src/KanbanPlugin.node.d.ts +4 -0
  63. package/dist/types/src/KanbanPlugin.node.d.ts.map +1 -0
  64. package/dist/types/src/KanbanPlugin.test.d.ts +2 -0
  65. package/dist/types/src/KanbanPlugin.test.d.ts.map +1 -0
  66. package/dist/types/src/KanbanPlugin.workerd.d.ts +4 -0
  67. package/dist/types/src/KanbanPlugin.workerd.d.ts.map +1 -0
  68. package/dist/types/src/blueprints/kanban-blueprint.d.ts +2 -2
  69. package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -1
  70. package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -0
  71. package/dist/types/src/capabilities/blueprint-definition.d.ts +6 -0
  72. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
  73. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  74. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  75. package/dist/types/src/capabilities/index.d.ts +12 -3
  76. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  78. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  79. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  80. package/dist/types/src/capabilities/undo-mappings.d.ts +5 -0
  81. package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -0
  82. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +17 -38
  83. package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -1
  84. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +48 -46
  85. package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -1
  86. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +2 -3
  87. package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -1
  88. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +2 -3
  89. package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -1
  90. package/dist/types/src/components/KanbanBoard/context.d.ts +38 -0
  91. package/dist/types/src/components/KanbanBoard/context.d.ts.map +1 -0
  92. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts +6 -0
  93. package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts.map +1 -0
  94. package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts +79 -0
  95. package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts.map +1 -0
  96. package/dist/types/src/containers/KanbanArticle/index.d.ts +2 -0
  97. package/dist/types/src/containers/KanbanArticle/index.d.ts.map +1 -0
  98. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts +13 -0
  99. package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts.map +1 -0
  100. package/dist/types/src/containers/KanbanSettings/index.d.ts +2 -0
  101. package/dist/types/src/containers/KanbanSettings/index.d.ts.map +1 -0
  102. package/dist/types/src/containers/index.d.ts +2 -2
  103. package/dist/types/src/containers/index.d.ts.map +1 -1
  104. package/dist/types/src/hooks/index.d.ts +1 -0
  105. package/dist/types/src/hooks/index.d.ts.map +1 -1
  106. package/dist/types/src/hooks/useEchoChangeCallback.d.ts +1 -1
  107. package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -1
  108. package/dist/types/src/hooks/useItemsProjection.d.ts +10 -0
  109. package/dist/types/src/hooks/useItemsProjection.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts +2 -0
  111. package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useKanbanBoardModel.d.ts +1 -1
  113. package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -1
  114. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +2 -2
  115. package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -1
  116. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +2 -2
  117. package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -1
  118. package/dist/types/src/hooks/useProjectionModel.d.ts +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 +5 -0
  125. package/dist/types/src/operations/delete-card-field.d.ts.map +1 -0
  126. package/dist/types/src/operations/delete-card.d.ts +5 -0
  127. package/dist/types/src/operations/delete-card.d.ts.map +1 -0
  128. package/dist/types/src/operations/index.d.ts +3 -0
  129. package/dist/types/src/operations/index.d.ts.map +1 -0
  130. package/dist/types/src/operations/restore-card-field.d.ts +5 -0
  131. package/dist/types/src/operations/restore-card-field.d.ts.map +1 -0
  132. package/dist/types/src/operations/restore-card.d.ts +5 -0
  133. package/dist/types/src/operations/restore-card.d.ts.map +1 -0
  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/testing/KanbanCardTileSimple.d.ts +4 -2
  139. package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -1
  140. package/dist/types/src/translations.d.ts +48 -46
  141. package/dist/types/src/translations.d.ts.map +1 -1
  142. package/dist/types/src/types/Kanban.d.ts +66 -9
  143. package/dist/types/src/types/Kanban.d.ts.map +1 -1
  144. package/dist/types/src/types/KanbanOperation.d.ts +52 -0
  145. package/dist/types/src/types/KanbanOperation.d.ts.map +1 -0
  146. package/dist/types/src/types/constants.d.ts +3 -3
  147. package/dist/types/src/types/constants.d.ts.map +1 -1
  148. package/dist/types/src/types/index.d.ts +2 -1
  149. package/dist/types/src/types/index.d.ts.map +1 -1
  150. package/dist/types/src/types/schema.d.ts +15 -104
  151. package/dist/types/src/types/schema.d.ts.map +1 -1
  152. package/dist/types/src/types/types.d.ts +2 -2
  153. package/dist/types/src/util/arrangement.d.ts +8 -4
  154. package/dist/types/src/util/arrangement.d.ts.map +1 -1
  155. package/dist/types/tsconfig.tsbuildinfo +1 -1
  156. package/package.json +115 -63
  157. package/src/KanbanPlugin.node.ts +21 -0
  158. package/src/KanbanPlugin.test.ts +31 -0
  159. package/src/KanbanPlugin.tsx +15 -29
  160. package/src/KanbanPlugin.workerd.ts +18 -0
  161. package/src/blueprints/kanban-blueprint.ts +4 -8
  162. package/src/capabilities/{artifact-definition/artifact-definition.ts → artifact-definition.ts} +12 -11
  163. package/src/capabilities/{blueprint-definition/blueprint-definition.ts → blueprint-definition.ts} +3 -1
  164. package/src/capabilities/create-object.ts +40 -0
  165. package/src/capabilities/index.ts +12 -3
  166. package/src/capabilities/operation-handler.ts +14 -0
  167. package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +35 -19
  168. package/src/capabilities/undo-mappings.ts +34 -0
  169. package/src/components/KanbanBoard/KanbanBoard.stories.tsx +22 -19
  170. package/src/components/KanbanBoard/KanbanBoard.tsx +43 -63
  171. package/src/components/KanbanBoard/KanbanCard.tsx +77 -63
  172. package/src/components/KanbanBoard/KanbanColumn.tsx +19 -16
  173. package/src/components/KanbanBoard/context.ts +54 -0
  174. package/src/containers/{KanbanContainer/KanbanContainer.stories.tsx → KanbanArticle/KanbanArticle.stories.tsx} +67 -53
  175. package/src/containers/KanbanArticle/KanbanArticle.tsx +180 -0
  176. package/src/containers/KanbanArticle/index.ts +5 -0
  177. package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
  178. package/src/containers/KanbanSettings/index.ts +5 -0
  179. package/src/containers/index.ts +2 -2
  180. package/src/hooks/index.ts +1 -0
  181. package/src/hooks/useEchoChangeCallback.ts +3 -3
  182. package/src/hooks/useItemsProjection.ts +44 -0
  183. package/src/hooks/{useKanbanBoardModel.test.ts → useKanbanBoardModel.browser.test.ts} +13 -19
  184. package/src/hooks/useKanbanBoardModel.ts +20 -6
  185. package/src/hooks/useKanbanColumnEventHandler.ts +1 -1
  186. package/src/hooks/useKanbanItemEventHandler.ts +1 -1
  187. package/src/hooks/useProjectionModel.ts +5 -5
  188. package/src/index.ts +3 -2
  189. package/src/meta.ts +22 -5
  190. package/src/operations/delete-card-field.ts +42 -0
  191. package/src/operations/delete-card.ts +23 -0
  192. package/src/operations/index.ts +10 -0
  193. package/src/operations/restore-card-field.ts +36 -0
  194. package/src/operations/restore-card.ts +21 -0
  195. package/src/playwright/smoke.spec.ts +3 -3
  196. package/src/plugin.ts +11 -0
  197. package/src/testing/KanbanCardTileSimple.tsx +29 -23
  198. package/src/translations.ts +26 -25
  199. package/src/types/Kanban.ts +85 -18
  200. package/src/types/KanbanOperation.ts +79 -0
  201. package/src/types/index.ts +3 -1
  202. package/src/types/schema.ts +20 -78
  203. package/src/types/types.ts +2 -2
  204. package/src/util/arrangement.test.ts +23 -14
  205. package/src/util/arrangement.ts +25 -15
  206. package/src/vite-env.d.ts +10 -0
  207. package/dist/lib/browser/KanbanContainer-BCXSJ6KS.mjs.map +0 -7
  208. package/dist/lib/browser/KanbanViewEditor-IH5CJ6BW.mjs.map +0 -7
  209. package/dist/lib/browser/blueprint-definition-HFEKGFJK.mjs.map +0 -7
  210. package/dist/lib/browser/chunk-QSWCFMEB.mjs +0 -385
  211. package/dist/lib/browser/chunk-QSWCFMEB.mjs.map +0 -7
  212. package/dist/lib/browser/chunk-RNFIFE2P.mjs +0 -213
  213. package/dist/lib/browser/chunk-RNFIFE2P.mjs.map +0 -7
  214. package/dist/lib/browser/chunk-TLYZSC7O.mjs.map +0 -7
  215. package/dist/lib/browser/index.mjs +0 -101
  216. package/dist/lib/browser/index.mjs.map +0 -7
  217. package/dist/lib/browser/meta.json +0 -1
  218. package/dist/lib/browser/operation-resolver-BRA2OHUE.mjs +0 -162
  219. package/dist/lib/browser/operation-resolver-BRA2OHUE.mjs.map +0 -7
  220. package/dist/lib/browser/react-surface-FOMOGFVW.mjs.map +0 -7
  221. package/dist/lib/node-esm/KanbanContainer-EHRTLE7M.mjs +0 -305
  222. package/dist/lib/node-esm/KanbanContainer-EHRTLE7M.mjs.map +0 -7
  223. package/dist/lib/node-esm/KanbanViewEditor-WDACFC35.mjs +0 -67
  224. package/dist/lib/node-esm/KanbanViewEditor-WDACFC35.mjs.map +0 -7
  225. package/dist/lib/node-esm/blueprint-definition-NARBX32U.mjs +0 -18
  226. package/dist/lib/node-esm/blueprint-definition-NARBX32U.mjs.map +0 -7
  227. package/dist/lib/node-esm/blueprints/index.mjs +0 -9
  228. package/dist/lib/node-esm/chunk-4AWDHQVY.mjs +0 -214
  229. package/dist/lib/node-esm/chunk-4AWDHQVY.mjs.map +0 -7
  230. package/dist/lib/node-esm/chunk-CSL3HF2X.mjs.map +0 -7
  231. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  232. package/dist/lib/node-esm/chunk-X3UJUQIV.mjs +0 -31
  233. package/dist/lib/node-esm/chunk-X3UJUQIV.mjs.map +0 -7
  234. package/dist/lib/node-esm/index.mjs +0 -102
  235. package/dist/lib/node-esm/index.mjs.map +0 -7
  236. package/dist/lib/node-esm/meta.json +0 -1
  237. package/dist/lib/node-esm/operation-resolver-UEQ64LCN.mjs +0 -163
  238. package/dist/lib/node-esm/operation-resolver-UEQ64LCN.mjs.map +0 -7
  239. package/dist/lib/node-esm/react-surface-AITKFRBE.mjs +0 -100
  240. package/dist/lib/node-esm/react-surface-AITKFRBE.mjs.map +0 -7
  241. package/dist/lib/node-esm/types/index.mjs +0 -21
  242. package/dist/types/src/capabilities/artifact-definition/artifact-definition.d.ts.map +0 -1
  243. package/dist/types/src/capabilities/artifact-definition/index.d.ts +0 -3
  244. package/dist/types/src/capabilities/artifact-definition/index.d.ts.map +0 -1
  245. package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts +0 -6
  246. package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts.map +0 -1
  247. package/dist/types/src/capabilities/blueprint-definition/index.d.ts +0 -3
  248. package/dist/types/src/capabilities/blueprint-definition/index.d.ts.map +0 -1
  249. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  250. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  251. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  252. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  253. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  254. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  255. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  256. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +0 -6
  257. package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +0 -1
  258. package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts +0 -77
  259. package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +0 -1
  260. package/dist/types/src/containers/KanbanContainer/index.d.ts +0 -3
  261. package/dist/types/src/containers/KanbanContainer/index.d.ts.map +0 -1
  262. package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts +0 -6
  263. package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts.map +0 -1
  264. package/dist/types/src/containers/KanbanViewEditor/index.d.ts +0 -3
  265. package/dist/types/src/containers/KanbanViewEditor/index.d.ts.map +0 -1
  266. package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts +0 -2
  267. package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts.map +0 -1
  268. package/src/capabilities/artifact-definition/index.ts +0 -7
  269. package/src/capabilities/blueprint-definition/index.ts +0 -7
  270. package/src/capabilities/operation-resolver/index.ts +0 -7
  271. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -133
  272. package/src/capabilities/react-surface/index.ts +0 -7
  273. package/src/containers/KanbanContainer/KanbanContainer.tsx +0 -87
  274. package/src/containers/KanbanContainer/index.ts +0 -7
  275. package/src/containers/KanbanViewEditor/KanbanViewEditor.tsx +0 -63
  276. package/src/containers/KanbanViewEditor/index.ts +0 -7
  277. /package/dist/lib/{browser → neutral}/blueprints/index.mjs.map +0 -0
  278. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  279. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
  280. /package/dist/lib/{browser/types → neutral}/index.mjs.map +0 -0
  281. /package/dist/lib/{node-esm/blueprints/index.mjs.map → neutral/meta.mjs.map} +0 -0
  282. /package/dist/lib/{node-esm/types → neutral/operations}/index.mjs.map +0 -0
  283. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/types/index.mjs.map} +0 -0
  284. /package/dist/types/src/capabilities/{artifact-definition/artifact-definition.d.ts → artifact-definition.d.ts} +0 -0
  285. /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
@@ -10,36 +10,40 @@ import { expect, waitFor, within } from 'storybook/test';
10
10
 
11
11
  import { withPluginManager } from '@dxos/app-framework/testing';
12
12
  import { Surface } from '@dxos/app-framework/ui';
13
- import { Obj, type QueryAST, Type } from '@dxos/echo';
14
- import { View } from '@dxos/echo';
13
+ import { AppSurface } from '@dxos/app-toolkit/ui';
14
+ import { Filter, Obj, type QueryAST, Type, View } from '@dxos/echo';
15
15
  import { type Mutable } from '@dxos/echo/internal';
16
16
  import { invariant } from '@dxos/invariant';
17
- import { ClientPlugin } from '@dxos/plugin-client';
18
- import { PreviewPlugin } from '@dxos/plugin-preview';
19
- import { SpacePlugin } from '@dxos/plugin-space';
17
+ // `/plugin` entrypoints used here for the same reason as `corePlugins()` —
18
+ // see `@dxos/plugin-testing/src/core.ts` for the rationale.
19
+ import { ClientPlugin } from '@dxos/plugin-client/testing';
20
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
21
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
22
+ import { SpacePlugin } from '@dxos/plugin-space/testing';
20
23
  import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
21
- import { faker } from '@dxos/random';
22
- import { Filter, type Space, useQuery, useSchema, useSpaces } from '@dxos/react-client/echo';
23
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
24
+ import { random } from '@dxos/random';
25
+ import { type Space, useQuery, useType, useSpaces } from '@dxos/react-client/echo';
24
26
  import { ViewEditor } from '@dxos/react-ui-form';
25
- import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
27
+ import { Syntax } from '@dxos/react-ui-syntax-highlighter';
28
+ import { withLayout } from '@dxos/react-ui/testing';
26
29
  import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
27
30
  // TODO(wittjosiah): Replace with echo/testing.
28
31
  import { Organization, Person } from '@dxos/types';
29
32
 
30
- import { useProjectionModel } from '../../hooks';
33
+ import { useProjectionModel } from '#hooks';
34
+ import { translations } from '#translations';
35
+ import { Kanban } from '#types';
36
+
31
37
  import { KanbanPlugin } from '../../KanbanPlugin';
32
- import { translations } from '../../translations';
33
- import { Kanban } from '../../types';
34
38
 
35
- faker.seed(0);
39
+ random.seed(0);
36
40
 
37
41
  const createOrg = (status?: Organization.Organization['status']) => ({
38
- name: faker.commerce.productName(),
39
- description: faker.lorem.paragraph(),
40
- image: faker.image.url(),
41
- website: faker.internet.url(),
42
- status: (status ?? faker.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
42
+ name: random.commerce.productName(),
43
+ description: random.lorem.paragraph(),
44
+ image: random.image.url(),
45
+ website: random.internet.url(),
46
+ status: (status ?? random.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
43
47
  });
44
48
 
45
49
  //
@@ -53,7 +57,7 @@ type ClientSetupOptions = {
53
57
 
54
58
  /**
55
59
  * Creates the standard plugin manager decorator with client configuration.
56
- * Includes KanbanPlugin so the Surface resolves to KanbanContainer.
60
+ * Includes KanbanPlugin so the Surface resolves to KanbanArticle.
57
61
  */
58
62
  const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
59
63
  withPluginManager({
@@ -63,7 +67,7 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
63
67
  types: [...types, View.View, Kanban.Kanban],
64
68
  onClientInitialized: ({ client }) =>
65
69
  Effect.gen(function* () {
66
- yield* Effect.promise(() => client.halo.createIdentity());
70
+ yield* initializeIdentity(client);
67
71
  const space = yield* Effect.promise(() => client.spaces.create());
68
72
  yield* Effect.promise(() => space.waitUntilReady());
69
73
  yield* Effect.promise(() => onSpaceCreated?.(space) ?? Promise.resolve());
@@ -77,59 +81,67 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
77
81
  });
78
82
 
79
83
  /**
80
- * Renders the first Kanban in the space via Surface (resolves to KanbanContainer),
81
- * with a sidebar containing ViewEditor and JsonFilter.
84
+ * Renders the first Kanban in the space via Surface (resolves to KanbanArticle),
85
+ * with a sidebar containing ViewEditor and Json filter.
82
86
  */
83
87
  const DefaultComponent = () => {
84
88
  const registry = useContext(RegistryContext);
85
89
  const spaces = useSpaces();
86
90
  const space = spaces[spaces.length - 1];
87
91
  const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
88
- const typename = kanban?.view.target?.query ? getTypenameFromQuery(kanban.view.target.query.ast) : undefined;
89
- const schema = useSchema(space?.db, typename);
90
- const projection = useProjectionModel(schema, kanban, registry);
92
+ const viewRef = kanban && kanban.spec.kind === 'view' ? kanban.spec.view : undefined;
93
+ const view = viewRef?.target;
94
+ const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
95
+ const type = useType(space?.db, typename);
96
+ const projection = useProjectionModel(type, kanban, registry);
91
97
 
92
- const data = useMemo(() => (kanban ? { subject: kanban } : {}), [kanban]);
98
+ const data = useMemo(() => (kanban ? { subject: kanban, attendableId: 'story' } : undefined), [kanban]);
93
99
 
94
100
  const handleUpdateQuery = useCallback(
95
101
  (newQuery: QueryAST.Query) => {
96
- invariant(schema);
97
- invariant(kanban?.view.target);
98
- if (Type.isMutable(schema)) {
99
- schema.updateTypename(getTypenameFromQuery(newQuery));
100
- }
101
- Obj.change(kanban.view.target, (view) => {
102
+ invariant(type);
103
+ invariant(view);
104
+ // NOTE: persisted Type.Type typename is immutable; only the view's
105
+ // query is updated here.
106
+ Obj.update(view, (view) => {
102
107
  view.query.ast = newQuery as Mutable<QueryAST.Query>;
103
108
  });
104
109
  },
105
- [kanban, schema],
110
+ [view, type],
106
111
  );
107
112
 
108
113
  const handleDeleteField = useCallback(
109
114
  (fieldId: string) => {
110
- if (schema && Type.isMutable(schema) && projection) {
115
+ if (type && Type.getDatabase(type) != null && projection) {
111
116
  projection.deleteFieldProjection(fieldId);
112
117
  }
113
118
  },
114
- [schema, projection],
119
+ [type, projection],
115
120
  );
116
121
 
117
- if (!schema || !kanban?.view.target) {
122
+ if (!type || !view) {
118
123
  return null;
119
124
  }
120
125
 
121
126
  return (
122
127
  <div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
123
- <Surface.Surface role='article' data={data} limit={1} />
128
+ <Surface.Surface type={AppSurface.Article} data={data} limit={1} />
124
129
  <div className='flex flex-col h-full overflow-hidden border-l border-separator'>
125
130
  <ViewEditor
126
- registry={space?.db.schemaRegistry}
127
- schema={schema}
128
- view={kanban.view.target}
131
+ registry={space?.db.graph.registry}
132
+ type={type}
133
+ view={view}
129
134
  onQueryChanged={handleUpdateQuery}
130
- onDelete={schema && Type.isMutable(schema) ? handleDeleteField : undefined}
135
+ onDelete={type && Type.getDatabase(type) != null ? handleDeleteField : undefined}
131
136
  />
132
- <JsonFilter data={{ view: kanban.view.target, schema }} classNames='text-xs' />
137
+ <Syntax.Root data={{ view, schema: Type.getSchema(type) }}>
138
+ <Syntax.Content>
139
+ <Syntax.Filter />
140
+ <Syntax.Viewport>
141
+ <Syntax.Code classNames='text-xs' />
142
+ </Syntax.Viewport>
143
+ </Syntax.Content>
144
+ </Syntax.Root>
133
145
  </div>
134
146
  </div>
135
147
  );
@@ -143,7 +155,7 @@ const meta = {
143
155
  title: 'plugins/plugin-kanban/containers/Kanban',
144
156
  component: DefaultComponent,
145
157
  render: () => <DefaultComponent />,
146
- decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
158
+ decorators: [withLayout({ layout: 'fullscreen' })],
147
159
  parameters: {
148
160
  layout: 'fullscreen',
149
161
  translations,
@@ -165,7 +177,7 @@ export const Default: Story = {
165
177
  onSpaceCreated: async (space) => {
166
178
  const { view } = await ViewModel.makeFromDatabase({
167
179
  db: space.db,
168
- typename: Organization.Organization.typename,
180
+ typename: Type.getTypename(Organization.Organization),
169
181
  pivotFieldName: 'status',
170
182
  });
171
183
  const kanban = Kanban.make({ view });
@@ -182,9 +194,9 @@ export const Default: Story = {
182
194
 
183
195
  // Wait for the kanban columns to render by finding the status tags.
184
196
  // Organization.StatusOptions: prospect, qualified, active, commit, reject.
185
- const activeTag = await canvas.findByText('Active', undefined, { timeout: 30_000 });
186
- const prospectTag = await canvas.findByText('Prospect', undefined, { timeout: 10_000 });
187
- const commitTag = await canvas.findByText('Commit', undefined, { timeout: 10_000 });
197
+ const activeTag = await canvas.findByText('Active', undefined, { timeout: 12_000 });
198
+ const prospectTag = await canvas.findByText('Prospect', undefined, { timeout: 12_000 });
199
+ const commitTag = await canvas.findByText('Commit', undefined, { timeout: 12_000 });
188
200
 
189
201
  // Verify all expected columns are rendered.
190
202
  await expect(activeTag).toBeTruthy();
@@ -209,7 +221,7 @@ export const Default: Story = {
209
221
  await expect(activeCards.length).toBeGreaterThan(0);
210
222
  await expect(prospectCards.length).toBeGreaterThan(0);
211
223
 
212
- // Verify cards have drag handles (Card.Toolbar includes drag handle).
224
+ // Verify cards have drag handles (Card.Header includes drag handle).
213
225
  const firstActiveCard = activeCards[0];
214
226
  const buttons = firstActiveCard.querySelectorAll('button');
215
227
  await expect(buttons.length).toBeGreaterThan(0);
@@ -226,7 +238,7 @@ export const Default: Story = {
226
238
  };
227
239
 
228
240
  /**
229
- * Story variant that uses a mutable database schema (EchoSchema).
241
+ * Story variant that uses a database-stored Type.Type entity (mutable schema).
230
242
  * This allows testing schema mutations like adding/removing fields.
231
243
  */
232
244
  // TODO(wittjosiah): Card previews (e.g., OrganizationCard) are type-specific and hard-coded.
@@ -237,12 +249,14 @@ export const MutableSchema: Story = {
237
249
  decorators: [
238
250
  withKanbanPlugins({
239
251
  onSpaceCreated: async (space) => {
240
- // Register schema in the database to make it mutable (EchoSchema).
241
- const [schema] = await space.db.schemaRegistry.register([Organization.Organization]);
252
+ // Persist the schema in the database to make it mutable (stored Type.Type).
253
+ const type = await space.db.addType(Organization.Organization);
242
254
 
243
255
  const { view } = await ViewModel.makeFromDatabase({
244
256
  db: space.db,
245
- typename: schema.typename,
257
+ // `db.addType` returns a persisted `Type.Type` entity; its typename lives in the
258
+ // type metadata, so read it via `Type.getTypename` rather than a `.typename` prop.
259
+ typename: Type.getTypename(type),
246
260
  pivotFieldName: 'status',
247
261
  });
248
262
  const kanban = Kanban.make({ view });
@@ -256,7 +270,7 @@ export const MutableSchema: Story = {
256
270
  ...Array.from({ length: 1 }, () => createOrg('commit')),
257
271
  ...Array.from({ length: 1 }, () => createOrg('reject')),
258
272
  ];
259
- requiredOrgs.forEach((org) => space.db.add(Obj.make(schema, org)));
273
+ requiredOrgs.forEach((org) => space.db.add(Obj.make(type, org)));
260
274
  },
261
275
  }),
262
276
  ],
@@ -0,0 +1,180 @@
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 { AtomObj, AtomQuery } from '@dxos/echo-atom';
13
+ import { useObject, useType } from '@dxos/react-client/echo';
14
+ import { Panel, Toolbar } from '@dxos/react-ui';
15
+ import { getTagFromQuery, getTypenameFromQuery } from '@dxos/schema';
16
+
17
+ import { KanbanBoard } from '#components';
18
+ import { useEchoChangeCallback, useItemsProjection, useProjectionModel } from '#hooks';
19
+ import { KanbanOperation } from '#types';
20
+ import { Kanban } from '#types';
21
+
22
+ export type KanbanArticleProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
23
+
24
+ export const KanbanArticle = (props: KanbanArticleProps) => {
25
+ // Branch on `kanban.spec.kind`: view-variant runs a typename query through
26
+ // `useProjectionModel`; items-variant dereferences `kanban.spec.items` and
27
+ // uses a stub projection from `useItemsProjection`.
28
+ return Kanban.isKanbanItems(props.subject) ? (
29
+ <ItemsKanbanArticle {...props} subject={props.subject} />
30
+ ) : (
31
+ <ViewKanbanArticle {...props} />
32
+ );
33
+ };
34
+
35
+ const ViewKanbanArticle = ({ role, subject: object }: KanbanArticleProps) => {
36
+ const registry = useContext(RegistryContext);
37
+ const schemas = useCapabilities(AppCapabilities.Schema);
38
+ const db = Obj.getDatabase(object);
39
+ const { invokePromise } = useOperationInvoker();
40
+ const [view] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
41
+ const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
42
+ const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
43
+
44
+ const schemaFromDb = useType(db, typename);
45
+ const cardSchema = useMemo(
46
+ () => schemaFromDb ?? schemas.flat().find((schema) => Type.getTypename(schema) === typename),
47
+ [schemaFromDb, schemas, typename],
48
+ );
49
+
50
+ const baseFilter = useSchemaFilter(cardSchema);
51
+ const items = useMemo(() => {
52
+ if (!db) {
53
+ return null;
54
+ }
55
+ const query = tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter);
56
+ return AtomQuery.make(db, query);
57
+ }, [db, baseFilter, tag]);
58
+
59
+ const projection = useProjectionModel(cardSchema, object, registry);
60
+ const change = useEchoChangeCallback(object);
61
+
62
+ const pivotFieldId = view?.projection?.pivotFieldId;
63
+ const columnFieldPath =
64
+ projection && pivotFieldId ? projection.tryGetFieldProjection(pivotFieldId)?.props.property : undefined;
65
+
66
+ const handleCardAdd = useCallback(
67
+ (columnValue: string | undefined) => {
68
+ if (db && cardSchema && columnFieldPath) {
69
+ const card = Obj.make(Type.assertObject(cardSchema), {
70
+ [columnFieldPath]: columnValue,
71
+ });
72
+ db.add(card);
73
+ return card.id;
74
+ }
75
+ },
76
+ [db, cardSchema, columnFieldPath],
77
+ );
78
+
79
+ const handleCardRemove = useCallback(
80
+ (card: { id: string }) => {
81
+ void invokePromise(KanbanOperation.DeleteCard, { card });
82
+ },
83
+ [invokePromise],
84
+ );
85
+
86
+ if (!object || !db || !items || !projection || !change) {
87
+ return null;
88
+ }
89
+
90
+ return (
91
+ <Panel.Root role={role}>
92
+ <Panel.Toolbar asChild>
93
+ <Toolbar.Root />
94
+ </Panel.Toolbar>
95
+ <KanbanBoard.Root
96
+ kanban={object}
97
+ projection={projection}
98
+ items={items}
99
+ change={change}
100
+ onCardAdd={handleCardAdd}
101
+ onCardRemove={handleCardRemove}
102
+ >
103
+ <Panel.Content asChild>
104
+ <KanbanBoard.Content />
105
+ </Panel.Content>
106
+ </KanbanBoard.Root>
107
+ </Panel.Root>
108
+ );
109
+ };
110
+
111
+ type ItemsKanbanArticleProps = Omit<KanbanArticleProps, 'subject'> & { subject: Kanban.KanbanItems };
112
+
113
+ const ItemsKanbanArticle = ({ role, subject: object }: ItemsKanbanArticleProps) => {
114
+ const db = Obj.getDatabase(object);
115
+ const projection = useItemsProjection(object);
116
+ const change = useEchoChangeCallback(object);
117
+
118
+ // TODO(wittjosiah): pass refs (not loaded objects) through to the kanban
119
+ // board and let `KanbanCard` subscribe to its own ref via `useObject`.
120
+ // Today this atom subscribes to *every* item — any one changing causes the
121
+ // container (and the model's per-column atoms) to recompute. With cards
122
+ // subscribing themselves, the container only needs the refs and the
123
+ // per-card render is independent. Requires:
124
+ // - `KanbanCard` to accept `Ref<Obj.Unknown>` as `data` and call
125
+ // `useObject(ref)` internally.
126
+ // - The model to handle a ref-bearing item shape (id from
127
+ // `ref.dxn.asEchoDXN()?.echoUri`) and use arrangement-only ordering
128
+ // for items-variant (no pivot-value fallback, since refs don't expose
129
+ // the pivot field without loading).
130
+ // - `Mosaic.isItem` to accept the ref wrapper alongside `Obj.isObject`.
131
+ const itemsAtom = useMemo(
132
+ () =>
133
+ Atom.make((get) => {
134
+ const out: Obj.Unknown[] = [];
135
+ for (const ref of object.spec.items as ReadonlyArray<Ref.Ref<Obj.Unknown>>) {
136
+ const target = get(AtomObj.make(ref));
137
+ if (target == null) {
138
+ continue;
139
+ }
140
+ // Drop soft-deleted cards (e.g. Trello-closed cards). The ref
141
+ // stays in `spec.items` so arrangement is preserved, but the card
142
+ // shouldn't render.
143
+ if (Obj.isDeleted(target)) {
144
+ continue;
145
+ }
146
+ out.push(target as unknown as Obj.Unknown);
147
+ }
148
+ return out;
149
+ }),
150
+ [object.spec.items],
151
+ );
152
+
153
+ const handleCardRemove = useCallback(() => undefined, []);
154
+
155
+ if (!object || !db || !change) {
156
+ return null;
157
+ }
158
+
159
+ // TODO(wittjosiah): wire `onCardAdd` to the create-object flow so
160
+ // users can add items directly from the kanban (currently the column's
161
+ // "+" button is hidden because `onCardAdd` is undefined).
162
+ return (
163
+ <Panel.Root role={role}>
164
+ <Panel.Toolbar asChild>
165
+ <Toolbar.Root />
166
+ </Panel.Toolbar>
167
+ <KanbanBoard.Root
168
+ kanban={object}
169
+ projection={projection}
170
+ items={itemsAtom}
171
+ change={change}
172
+ onCardRemove={handleCardRemove}
173
+ >
174
+ <Panel.Content asChild>
175
+ <KanbanBoard.Content />
176
+ </Panel.Content>
177
+ </KanbanBoard.Root>
178
+ </Panel.Root>
179
+ );
180
+ };
@@ -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/internal';
11
+ import { useObject, useType } from '@dxos/react-client/echo';
12
+ import { Form, type FormFieldMap, SelectField } from '@dxos/react-ui-form';
13
+ import { getTypenameFromQuery } 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 currentTypename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
34
+ const schema = useType(db, currentTypename);
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';
@@ -6,16 +6,16 @@ import { useMemo } from 'react';
6
6
 
7
7
  import { Obj } from '@dxos/echo';
8
8
 
9
- import { type Kanban, type KanbanChangeCallback } from '../types';
9
+ import { type Kanban, type KanbanChangeCallback } from '#types';
10
10
 
11
11
  /**
12
12
  * Creates a change callback for ECHO-backed kanban and items (plain function, no hooks).
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
+ };