@elaraai/east-ui 1.0.80 → 1.0.82

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 (353) hide show
  1. package/dist/src/charts/chart/index.d.ts +4 -0
  2. package/dist/src/charts/chart/index.d.ts.map +1 -1
  3. package/dist/src/charts/spec/index.d.ts +8 -0
  4. package/dist/src/charts/spec/index.d.ts.map +1 -1
  5. package/dist/src/collections/deck/index.d.ts +8 -0
  6. package/dist/src/collections/deck/index.d.ts.map +1 -1
  7. package/dist/src/collections/index.d.ts +2 -2
  8. package/dist/src/collections/index.d.ts.map +1 -1
  9. package/dist/src/collections/index.js.map +1 -1
  10. package/dist/src/collections/library/index.d.ts +4 -0
  11. package/dist/src/collections/library/index.d.ts.map +1 -1
  12. package/dist/src/collections/library/types.d.ts +4 -0
  13. package/dist/src/collections/library/types.d.ts.map +1 -1
  14. package/dist/src/collections/matrix/index.d.ts +56 -19
  15. package/dist/src/collections/matrix/index.d.ts.map +1 -1
  16. package/dist/src/collections/matrix/index.js +38 -24
  17. package/dist/src/collections/matrix/index.js.map +1 -1
  18. package/dist/src/collections/plan/assemble.d.ts +655 -2113
  19. package/dist/src/collections/plan/assemble.d.ts.map +1 -1
  20. package/dist/src/collections/plan/assemble.js +159 -136
  21. package/dist/src/collections/plan/assemble.js.map +1 -1
  22. package/dist/src/collections/plan/builders.d.ts +193 -40
  23. package/dist/src/collections/plan/builders.d.ts.map +1 -1
  24. package/dist/src/collections/plan/builders.js +162 -32
  25. package/dist/src/collections/plan/builders.js.map +1 -1
  26. package/dist/src/collections/plan/factories.d.ts +290 -116
  27. package/dist/src/collections/plan/factories.d.ts.map +1 -1
  28. package/dist/src/collections/plan/factories.js +281 -156
  29. package/dist/src/collections/plan/factories.js.map +1 -1
  30. package/dist/src/collections/plan/index.d.ts +111 -58
  31. package/dist/src/collections/plan/index.d.ts.map +1 -1
  32. package/dist/src/collections/plan/index.js +62 -31
  33. package/dist/src/collections/plan/index.js.map +1 -1
  34. package/dist/src/collections/plan/ir.d.ts +759 -1769
  35. package/dist/src/collections/plan/ir.d.ts.map +1 -1
  36. package/dist/src/collections/plan/ir.js +51 -27
  37. package/dist/src/collections/plan/ir.js.map +1 -1
  38. package/dist/src/collections/plan/pick.d.ts +89 -71
  39. package/dist/src/collections/plan/pick.d.ts.map +1 -1
  40. package/dist/src/collections/plan/pick.js +101 -86
  41. package/dist/src/collections/plan/pick.js.map +1 -1
  42. package/dist/src/collections/plan/root.d.ts +164 -77
  43. package/dist/src/collections/plan/root.d.ts.map +1 -1
  44. package/dist/src/collections/plan/root.js +350 -42
  45. package/dist/src/collections/plan/root.js.map +1 -1
  46. package/dist/src/collections/plan/series.d.ts +12524 -10527
  47. package/dist/src/collections/plan/series.d.ts.map +1 -1
  48. package/dist/src/collections/plan/series.js +1596 -298
  49. package/dist/src/collections/plan/series.js.map +1 -1
  50. package/dist/src/collections/plan/types.d.ts +6115 -2802
  51. package/dist/src/collections/plan/types.d.ts.map +1 -1
  52. package/dist/src/collections/plan/types.js +575 -170
  53. package/dist/src/collections/plan/types.js.map +1 -1
  54. package/dist/src/collections/sheet/bridge.d.ts +154 -43
  55. package/dist/src/collections/sheet/bridge.d.ts.map +1 -1
  56. package/dist/src/collections/sheet/bridge.js +383 -240
  57. package/dist/src/collections/sheet/bridge.js.map +1 -1
  58. package/dist/src/collections/sheet/columns.d.ts +66 -16
  59. package/dist/src/collections/sheet/columns.d.ts.map +1 -1
  60. package/dist/src/collections/sheet/columns.js +25 -25
  61. package/dist/src/collections/sheet/columns.js.map +1 -1
  62. package/dist/src/collections/sheet/context-bridge.d.ts +48 -0
  63. package/dist/src/collections/sheet/context-bridge.d.ts.map +1 -0
  64. package/dist/src/collections/sheet/context-bridge.js +216 -0
  65. package/dist/src/collections/sheet/context-bridge.js.map +1 -0
  66. package/dist/src/collections/sheet/draft-bridge.d.ts +37 -0
  67. package/dist/src/collections/sheet/draft-bridge.d.ts.map +1 -0
  68. package/dist/src/collections/sheet/draft-bridge.js +153 -0
  69. package/dist/src/collections/sheet/draft-bridge.js.map +1 -0
  70. package/dist/src/collections/sheet/drafts.d.ts +14 -0
  71. package/dist/src/collections/sheet/drafts.d.ts.map +1 -0
  72. package/dist/src/collections/sheet/drafts.js +14 -0
  73. package/dist/src/collections/sheet/drafts.js.map +1 -0
  74. package/dist/src/collections/sheet/editing-bridge.d.ts +36 -0
  75. package/dist/src/collections/sheet/editing-bridge.d.ts.map +1 -0
  76. package/dist/src/collections/sheet/editing-bridge.js +196 -0
  77. package/dist/src/collections/sheet/editing-bridge.js.map +1 -0
  78. package/dist/src/collections/sheet/editing-types.d.ts +268 -0
  79. package/dist/src/collections/sheet/editing-types.d.ts.map +1 -0
  80. package/dist/src/collections/sheet/editing-types.js +34 -0
  81. package/dist/src/collections/sheet/editing-types.js.map +1 -0
  82. package/dist/src/collections/sheet/edits.d.ts +37 -0
  83. package/dist/src/collections/sheet/edits.d.ts.map +1 -0
  84. package/dist/src/collections/sheet/edits.js +31 -0
  85. package/dist/src/collections/sheet/edits.js.map +1 -0
  86. package/dist/src/collections/sheet/group.d.ts +165 -0
  87. package/dist/src/collections/sheet/group.d.ts.map +1 -0
  88. package/dist/src/collections/sheet/group.js +52 -0
  89. package/dist/src/collections/sheet/group.js.map +1 -0
  90. package/dist/src/collections/sheet/index.d.ts +171 -27
  91. package/dist/src/collections/sheet/index.d.ts.map +1 -1
  92. package/dist/src/collections/sheet/index.js +99 -17
  93. package/dist/src/collections/sheet/index.js.map +1 -1
  94. package/dist/src/collections/sheet/link.d.ts +22 -16
  95. package/dist/src/collections/sheet/link.d.ts.map +1 -1
  96. package/dist/src/collections/sheet/link.js +18 -12
  97. package/dist/src/collections/sheet/link.js.map +1 -1
  98. package/dist/src/collections/sheet/registers.d.ts +152 -5
  99. package/dist/src/collections/sheet/registers.d.ts.map +1 -1
  100. package/dist/src/collections/sheet/registers.js +154 -5
  101. package/dist/src/collections/sheet/registers.js.map +1 -1
  102. package/dist/src/collections/sheet/root.d.ts +175 -26
  103. package/dist/src/collections/sheet/root.d.ts.map +1 -1
  104. package/dist/src/collections/sheet/root.js +150 -26
  105. package/dist/src/collections/sheet/root.js.map +1 -1
  106. package/dist/src/collections/sheet/seed-bridge.d.ts +14 -0
  107. package/dist/src/collections/sheet/seed-bridge.d.ts.map +1 -0
  108. package/dist/src/collections/sheet/seed-bridge.js +51 -0
  109. package/dist/src/collections/sheet/seed-bridge.js.map +1 -0
  110. package/dist/src/collections/sheet/sub-rows.d.ts +249 -0
  111. package/dist/src/collections/sheet/sub-rows.d.ts.map +1 -0
  112. package/dist/src/collections/sheet/sub-rows.js +222 -0
  113. package/dist/src/collections/sheet/sub-rows.js.map +1 -0
  114. package/dist/src/collections/sheet/transactions.d.ts +76 -0
  115. package/dist/src/collections/sheet/transactions.d.ts.map +1 -0
  116. package/dist/src/collections/sheet/transactions.js +40 -0
  117. package/dist/src/collections/sheet/transactions.js.map +1 -0
  118. package/dist/src/collections/sheet/types.d.ts +12034 -3651
  119. package/dist/src/collections/sheet/types.d.ts.map +1 -1
  120. package/dist/src/collections/sheet/types.js +365 -48
  121. package/dist/src/collections/sheet/types.js.map +1 -1
  122. package/dist/src/collections/table/index.d.ts +98 -123
  123. package/dist/src/collections/table/index.d.ts.map +1 -1
  124. package/dist/src/collections/table/index.js +97 -111
  125. package/dist/src/collections/table/index.js.map +1 -1
  126. package/dist/src/collections/table/types.d.ts +87 -31
  127. package/dist/src/collections/table/types.d.ts.map +1 -1
  128. package/dist/src/collections/table/types.js +51 -26
  129. package/dist/src/collections/table/types.js.map +1 -1
  130. package/dist/src/collections/value-tree/flatten.d.ts.map +1 -1
  131. package/dist/src/collections/value-tree/flatten.js +4 -1
  132. package/dist/src/collections/value-tree/flatten.js.map +1 -1
  133. package/dist/src/component.d.ts +5075 -4079
  134. package/dist/src/component.d.ts.map +1 -1
  135. package/dist/src/component.js +46 -46
  136. package/dist/src/component.js.map +1 -1
  137. package/dist/src/contracts/approval.d.ts +10 -8
  138. package/dist/src/contracts/approval.d.ts.map +1 -1
  139. package/dist/src/contracts/approval.js +10 -8
  140. package/dist/src/contracts/approval.js.map +1 -1
  141. package/dist/src/contracts/drag.d.ts +11 -9
  142. package/dist/src/contracts/drag.d.ts.map +1 -1
  143. package/dist/src/contracts/drag.js +28 -21
  144. package/dist/src/contracts/drag.js.map +1 -1
  145. package/dist/src/contracts/editing.d.ts +1283 -0
  146. package/dist/src/contracts/editing.d.ts.map +1 -0
  147. package/dist/src/contracts/editing.js +711 -0
  148. package/dist/src/contracts/editing.js.map +1 -0
  149. package/dist/src/contracts/index.d.ts +2 -1
  150. package/dist/src/contracts/index.d.ts.map +1 -1
  151. package/dist/src/contracts/index.js +2 -1
  152. package/dist/src/contracts/index.js.map +1 -1
  153. package/dist/src/contracts/pick.d.ts +6 -7
  154. package/dist/src/contracts/pick.d.ts.map +1 -1
  155. package/dist/src/contracts/pick.js +6 -7
  156. package/dist/src/contracts/pick.js.map +1 -1
  157. package/dist/src/contracts/review.d.ts +3 -1
  158. package/dist/src/contracts/review.d.ts.map +1 -1
  159. package/dist/src/contracts/review.js +3 -1
  160. package/dist/src/contracts/review.js.map +1 -1
  161. package/dist/src/contracts/source.d.ts +270 -80
  162. package/dist/src/contracts/source.d.ts.map +1 -1
  163. package/dist/src/contracts/source.js +357 -58
  164. package/dist/src/contracts/source.js.map +1 -1
  165. package/dist/src/display/chip-rail/index.d.ts.map +1 -1
  166. package/dist/src/display/chip-rail/index.js +10 -10
  167. package/dist/src/display/chip-rail/index.js.map +1 -1
  168. package/dist/src/extension.d.ts +2 -1
  169. package/dist/src/extension.d.ts.map +1 -1
  170. package/dist/src/extension.js +2 -1
  171. package/dist/src/extension.js.map +1 -1
  172. package/dist/src/format/index.d.ts +1 -154
  173. package/dist/src/format/index.d.ts.map +1 -1
  174. package/dist/src/format/types.d.ts +35 -23
  175. package/dist/src/format/types.d.ts.map +1 -1
  176. package/dist/src/format/types.js +16 -14
  177. package/dist/src/format/types.js.map +1 -1
  178. package/dist/src/forms/field/index.js +4 -4
  179. package/dist/src/forms/field/index.js.map +1 -1
  180. package/dist/src/forms/file-upload/index.js +16 -16
  181. package/dist/src/forms/file-upload/index.js.map +1 -1
  182. package/dist/src/index.d.ts +4 -2
  183. package/dist/src/index.d.ts.map +1 -1
  184. package/dist/src/index.js +12 -5
  185. package/dist/src/index.js.map +1 -1
  186. package/dist/src/internal.d.ts +8 -3
  187. package/dist/src/internal.d.ts.map +1 -1
  188. package/dist/src/internal.js +11 -3
  189. package/dist/src/internal.js.map +1 -1
  190. package/dist/src/layout/box/index.d.ts.map +1 -1
  191. package/dist/src/layout/box/index.js +47 -47
  192. package/dist/src/layout/box/index.js.map +1 -1
  193. package/dist/src/layout/flex/index.d.ts.map +1 -1
  194. package/dist/src/layout/flex/index.js +47 -47
  195. package/dist/src/layout/flex/index.js.map +1 -1
  196. package/dist/src/layout/grid/index.js +11 -11
  197. package/dist/src/layout/grid/index.js.map +1 -1
  198. package/dist/src/layout/scroll-area/index.d.ts.map +1 -1
  199. package/dist/src/layout/scroll-area/index.js +8 -8
  200. package/dist/src/layout/scroll-area/index.js.map +1 -1
  201. package/dist/src/layout/separator/index.d.ts.map +1 -1
  202. package/dist/src/layout/separator/index.js +6 -6
  203. package/dist/src/layout/separator/index.js.map +1 -1
  204. package/dist/src/layout/stack/index.d.ts.map +1 -1
  205. package/dist/src/layout/stack/index.js +46 -46
  206. package/dist/src/layout/stack/index.js.map +1 -1
  207. package/dist/src/layout/sticky/index.d.ts.map +1 -1
  208. package/dist/src/layout/sticky/index.js +8 -8
  209. package/dist/src/layout/sticky/index.js.map +1 -1
  210. package/dist/src/overlays/action-bar/index.d.ts.map +1 -1
  211. package/dist/src/overlays/action-bar/index.js +8 -8
  212. package/dist/src/overlays/action-bar/index.js.map +1 -1
  213. package/dist/src/platform/index.d.ts +1 -1
  214. package/dist/src/platform/index.d.ts.map +1 -1
  215. package/dist/src/platform/index.js +1 -1
  216. package/dist/src/platform/index.js.map +1 -1
  217. package/dist/src/platform/slice/impl.d.ts +62 -51
  218. package/dist/src/platform/slice/impl.d.ts.map +1 -1
  219. package/dist/src/platform/slice/impl.js +155 -72
  220. package/dist/src/platform/slice/impl.js.map +1 -1
  221. package/dist/src/platform/slice/index.d.ts +3744 -2226
  222. package/dist/src/platform/slice/index.d.ts.map +1 -1
  223. package/dist/src/platform/slice/index.js +43 -1
  224. package/dist/src/platform/slice/index.js.map +1 -1
  225. package/dist/src/runtime/collections/deck.d.ts +4 -0
  226. package/dist/src/runtime/collections/deck.d.ts.map +1 -1
  227. package/dist/src/runtime/collections/matrix.d.ts +10 -9
  228. package/dist/src/runtime/collections/matrix.d.ts.map +1 -1
  229. package/dist/src/runtime/collections/matrix.js +11 -6
  230. package/dist/src/runtime/collections/matrix.js.map +1 -1
  231. package/dist/src/runtime/collections/plan.d.ts +148 -65
  232. package/dist/src/runtime/collections/plan.d.ts.map +1 -1
  233. package/dist/src/runtime/collections/plan.js +155 -67
  234. package/dist/src/runtime/collections/plan.js.map +1 -1
  235. package/dist/src/runtime/collections/sheet.d.ts +56 -23
  236. package/dist/src/runtime/collections/sheet.d.ts.map +1 -1
  237. package/dist/src/runtime/collections/sheet.js +30 -22
  238. package/dist/src/runtime/collections/sheet.js.map +1 -1
  239. package/dist/src/runtime/collections/table.d.ts +18 -11
  240. package/dist/src/runtime/collections/table.d.ts.map +1 -1
  241. package/dist/src/runtime/collections/table.js +7 -4
  242. package/dist/src/runtime/collections/table.js.map +1 -1
  243. package/dist/src/shared/reify.d.ts +4 -46
  244. package/dist/src/shared/reify.d.ts.map +1 -1
  245. package/dist/src/shared/reify.js +4 -61
  246. package/dist/src/shared/reify.js.map +1 -1
  247. package/dist/src/shared/tree.d.ts +88 -0
  248. package/dist/src/shared/tree.d.ts.map +1 -0
  249. package/dist/src/shared/tree.js +134 -0
  250. package/dist/src/shared/tree.js.map +1 -0
  251. package/dist/src/slice/breakdown/index.d.ts +4 -0
  252. package/dist/src/slice/breakdown/index.d.ts.map +1 -1
  253. package/dist/src/slice/breakdown/types.d.ts +4 -0
  254. package/dist/src/slice/breakdown/types.d.ts.map +1 -1
  255. package/dist/src/slice/cohort/index.d.ts +24 -4
  256. package/dist/src/slice/cohort/index.d.ts.map +1 -1
  257. package/dist/src/slice/cohort/index.js +8 -4
  258. package/dist/src/slice/cohort/index.js.map +1 -1
  259. package/dist/src/slice/cohort/types.d.ts +11 -0
  260. package/dist/src/slice/cohort/types.d.ts.map +1 -1
  261. package/dist/src/slice/cohort/types.js +7 -0
  262. package/dist/src/slice/cohort/types.js.map +1 -1
  263. package/dist/src/slice/filter/index.d.ts +4 -0
  264. package/dist/src/slice/filter/index.d.ts.map +1 -1
  265. package/dist/src/slice/filter/types.d.ts +4 -0
  266. package/dist/src/slice/filter/types.d.ts.map +1 -1
  267. package/dist/src/slice/index.d.ts +736 -0
  268. package/dist/src/slice/index.d.ts.map +1 -1
  269. package/dist/src/slice/legend/index.d.ts +4 -0
  270. package/dist/src/slice/legend/index.d.ts.map +1 -1
  271. package/dist/src/slice/legend/types.d.ts +4 -0
  272. package/dist/src/slice/legend/types.d.ts.map +1 -1
  273. package/dist/src/slice/rail/index.d.ts +8 -4
  274. package/dist/src/slice/rail/index.d.ts.map +1 -1
  275. package/dist/src/slice/rail/index.js.map +1 -1
  276. package/dist/src/slice/range/index.d.ts +4 -0
  277. package/dist/src/slice/range/index.d.ts.map +1 -1
  278. package/dist/src/slice/range/types.d.ts +4 -0
  279. package/dist/src/slice/range/types.d.ts.map +1 -1
  280. package/dist/src/slice/search/index.d.ts +4 -0
  281. package/dist/src/slice/search/index.d.ts.map +1 -1
  282. package/dist/src/slice/search/types.d.ts +4 -0
  283. package/dist/src/slice/search/types.d.ts.map +1 -1
  284. package/dist/src/slice/summary/index.d.ts +4 -0
  285. package/dist/src/slice/summary/index.d.ts.map +1 -1
  286. package/dist/src/slice/summary/types.d.ts +4 -0
  287. package/dist/src/slice/summary/types.d.ts.map +1 -1
  288. package/dist/src/typography/code/index.js +1 -1
  289. package/dist/src/typography/code/index.js.map +1 -1
  290. package/dist/src/typography/code-block/index.js +7 -7
  291. package/dist/src/typography/code-block/index.js.map +1 -1
  292. package/dist/src/typography/heading/index.js +2 -2
  293. package/dist/src/typography/heading/index.js.map +1 -1
  294. package/dist/src/typography/list/index.js +1 -1
  295. package/dist/src/typography/list/index.js.map +1 -1
  296. package/dist/src/typography/mark/index.js +1 -1
  297. package/dist/src/typography/mark/index.js.map +1 -1
  298. package/dist/src/typography/note/index.js +1 -1
  299. package/dist/src/typography/note/index.js.map +1 -1
  300. package/dist/src/typography/numeric/index.d.ts +3 -308
  301. package/dist/src/typography/numeric/index.d.ts.map +1 -1
  302. package/dist/src/typography/numeric/index.js +4 -4
  303. package/dist/src/typography/numeric/index.js.map +1 -1
  304. package/dist/src/typography/numeric/types.d.ts +1 -154
  305. package/dist/src/typography/numeric/types.d.ts.map +1 -1
  306. package/dist/src/typography/text/index.js +1 -1
  307. package/dist/src/typography/text/index.js.map +1 -1
  308. package/dist/test/collections/matrix.examples.d.ts +7 -3
  309. package/dist/test/collections/matrix.examples.d.ts.map +1 -1
  310. package/dist/test/collections/matrix.examples.js +55 -24
  311. package/dist/test/collections/matrix.examples.js.map +1 -1
  312. package/dist/test/collections/paged-source.examples.d.ts +4 -0
  313. package/dist/test/collections/paged-source.examples.d.ts.map +1 -1
  314. package/dist/test/collections/paged-source.examples.js +98 -13
  315. package/dist/test/collections/paged-source.examples.js.map +1 -1
  316. package/dist/test/collections/plan.examples.d.ts +101 -26
  317. package/dist/test/collections/plan.examples.d.ts.map +1 -1
  318. package/dist/test/collections/plan.examples.js +1481 -755
  319. package/dist/test/collections/plan.examples.js.map +1 -1
  320. package/dist/test/collections/sheet-link.examples.d.ts +7 -0
  321. package/dist/test/collections/sheet-link.examples.d.ts.map +1 -0
  322. package/dist/test/collections/sheet-link.examples.js +41 -0
  323. package/dist/test/collections/sheet-link.examples.js.map +1 -0
  324. package/dist/test/collections/sheet-transactions.examples.d.ts +7 -0
  325. package/dist/test/collections/sheet-transactions.examples.d.ts.map +1 -0
  326. package/dist/test/collections/sheet-transactions.examples.js +58 -0
  327. package/dist/test/collections/sheet-transactions.examples.js.map +1 -0
  328. package/dist/test/collections/sheet.examples.d.ts +52 -5
  329. package/dist/test/collections/sheet.examples.d.ts.map +1 -1
  330. package/dist/test/collections/sheet.examples.js +500 -158
  331. package/dist/test/collections/sheet.examples.js.map +1 -1
  332. package/dist/test/collections/table.examples.d.ts +31 -3
  333. package/dist/test/collections/table.examples.d.ts.map +1 -1
  334. package/dist/test/collections/table.examples.js +188 -28
  335. package/dist/test/collections/table.examples.js.map +1 -1
  336. package/dist/test/contracts/editing.examples.d.ts +7 -0
  337. package/dist/test/contracts/editing.examples.d.ts.map +1 -0
  338. package/dist/test/contracts/editing.examples.js +58 -0
  339. package/dist/test/contracts/editing.examples.js.map +1 -0
  340. package/dist/test/layout/splitter.examples.d.ts.map +1 -1
  341. package/dist/test/layout/splitter.examples.js +3 -1
  342. package/dist/test/layout/splitter.examples.js.map +1 -1
  343. package/dist/test/platform/slice.examples.d.ts.map +1 -1
  344. package/dist/test/platform/slice.examples.js +1 -0
  345. package/dist/test/platform/slice.examples.js.map +1 -1
  346. package/dist/test/slice/slice.examples.d.ts.map +1 -1
  347. package/dist/test/slice/slice.examples.js +5 -2
  348. package/dist/test/slice/slice.examples.js.map +1 -1
  349. package/package.json +4 -3
  350. package/dist/src/collections/plan/data-forms.d.ts +0 -332
  351. package/dist/src/collections/plan/data-forms.d.ts.map +0 -1
  352. package/dist/src/collections/plan/data-forms.js +0 -212
  353. package/dist/src/collections/plan/data-forms.js.map +0 -1
@@ -3,410 +3,1708 @@
3
3
  * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
4
  */
5
5
  /**
6
- * Plan series (`Plan Data Interface.md` §3.5/§3.5a) — the data-driven row
7
- * series of a `data` + `series` canvas. Series are REAL EAST VALUES:
8
- * {@link PlanSeriesType} is a type constructor (the `DataBindHandleType`
9
- * pattern) instantiated per row type, and every `Plan.series.*` builder
10
- * reifies its accessors ONCE into the arm's typed `derive` function
11
- * (`Fn(Dict<String, R>) → Dict<String, PlanRow>` — Table's per-column `valueFn` move)
12
- * and returns `variant(kind, { derive })`. Application is eager expression
13
- * composition, typed end to end: the inline arm applies each series' `derive`
14
- * to the data expression (Table's `rows_mapped`); the paged arm (P-c) wraps
15
- * the same `derive`s over a `Data.bindPaged` handle's `page` method.
6
+ * Plan series (#822) — the row recipes of a `data` + `series` canvas.
7
+ *
8
+ * A canvas is its `data` (keyed entries) plus a list of series. Each series
9
+ * says which entries it takes (`match`), what kind of row each becomes, and
10
+ * how to read that row's content from the entry; several series over one
11
+ * source give each entry several rows. The top-level list IS the layout: each
12
+ * series contributes its rows in declared order, as BLOCKS kept apart
13
+ * (`PlanBlockType`, #823) — a data series one block of its entries' rows (an
14
+ * ordered stream, `Array<PlanRow>`), which a paged canvas pages on its own; a
15
+ * section its header's fixed block, then its members' blocks; hand-built rows
16
+ * a fixed block.
17
+ *
18
+ * Hierarchy comes only from the data's own nesting. Every data series takes
19
+ * `children`: a bare accessor returns more of THIS series' entries (to any
20
+ * depth — a `RecursiveType` entry), and `Plan.children(of, [series…])` steps
21
+ * down to a child collection of another type, laid out like a top-level list.
22
+ * `Plan.series.section` puts a titled block over series, `Plan.series.views`
23
+ * gives each entry one adjacent row per member series, and `Plan.series.rows`
24
+ * places hand-built rows.
25
+ *
26
+ * Every row has a typed id — `{ series, path }`, the path the entry keys that
27
+ * lead to it — and every series is a REAL EAST VALUE (the `DataBindHandleType`
28
+ * pattern): its `derive` is a reified East function from the source to its
29
+ * rows, so a series list can be bound, picked (`Plan.pick`) and stored. Each
30
+ * builder also records how to BUILD the series for any collection type, which
31
+ * is what lets a step-down compile its child series for the child collection's
32
+ * type, the root compile them for a source keyed by any type, and the root
33
+ * refuse two series sharing a key.
16
34
  *
17
35
  * @packageDocumentation
18
36
  */
19
- import { East, Expr, ArrayType, BooleanType, DictType, FunctionType, OptionType, StringType, StructType, VariantType, variant, some, none, } from "@elaraai/east";
37
+ import { East, Expr, ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, FloatType, FunctionType, IntegerType, OptionType, RefType, StringType, StructType, VariantType, isTypeEqual, printFor, toEastTypeValue, variant, some, none, } from "@elaraai/east";
20
38
  import { StatusValueType } from "../../feedback/status/types.js";
21
39
  import { IconType } from "../../display/icon/types.js";
22
40
  import { ApprovalStateType } from "../../contracts/approval.js";
23
- import { foldEntriesToDict } from "../../shared/reify.js";
24
41
  import { TableAggregateType } from "../table/types.js";
25
- import { PlanAggregateType, PlanExpandType, PlanRowKindType, PlanLaneType, PlanBucketEventType, PlanCellMarkerType, PlanChipType, PlanEventMarkType, PlanRowsCollectionType, } from "./types.js";
26
- import { resolveTag, resolveIcon, emptyRows } from "./builders.js";
27
- import { groupParentFn, applyRowOverrides, normalizeRows, LAST_WINS } from "./assemble.js";
28
- import { createBuckets, createCards, createEvents, createChart, createGroup, } from "./factories.js";
29
- import { rowKey, reifyLevelKey, groupRows, spanParentKind, spanRowOf, heatParentKind, heatRowOf, tableParentKind, tableRowOf, } from "./data-forms.js";
42
+ import { TickFormatType } from "../../format/types.js";
43
+ import { reifyAccessor } from "../../shared/reify.js";
44
+ import { PlanAggregateType, PlanExpandType, PlanGroupSummaryType, PlanHeatCellsType, PlanLaneType, PlanBucketEventType, PlanCellMarkerType, PlanChipType, PlanEventMarkType, PlanRunType, PlanDecisionMarkType, PlanPortType, PlanRollupType, PlanRowIdType, PlanRowKindType, PlanRowType, PlanRowsCollectionType, PlanBlockType, PlanBlocksType, PlanGestureType, PlanDropType, PlanInstantType, } from "./types.js";
45
+ import { resolveTag, resolveIcon } from "./builders.js";
46
+ import { planRow, planGutter, normalizeRows, REBASE_ROWS } from "./assemble.js";
47
+ import { spanKind, bucketsKind, chartKind, heatKind, tableKind, cardsKind, eventsKind, groupKind, groupSummary, } from "./factories.js";
30
48
  // ============================================================================
31
49
  // The series type — a constructor, instantiated per row type
32
50
  // ============================================================================
33
51
  /**
34
- * The Plan series type CONSTRUCTOR — given the domain row type it returns
35
- * the concrete variant type of one series value (the `DataBindHandleType`
36
- * precedent: the row type lives structurally in each arm's `derive`
37
- * signature, so a series bound to one row type is a compile error against
38
- * data of another).
52
+ * The item a move to another row carries between its two writes (#825) — its
53
+ * bytes, from the source row's write, which takes it out of its list, to the
54
+ * target row's, which puts it into its own; `none` while nothing is in
55
+ * transit. The canvas threads ONE through every row a gesture writes, in
56
+ * order, and an item still carried at the end is a move that landed nowhere.
57
+ *
58
+ * @internal
59
+ */
60
+ export const PlanCarriedType = RefType(OptionType(BlobType));
61
+ /**
62
+ * The Plan series type CONSTRUCTOR — given the entry type (and the entries'
63
+ * key type, `String` by default) it returns the concrete variant type of one
64
+ * series value (the `DataBindHandleType` precedent: the entry type lives
65
+ * structurally in each arm's `derive` signature, so a series built for one
66
+ * entry type is a compile error against data of another).
39
67
  *
40
68
  * @remarks
41
- * Every kind arm carries `derive: Fn(Dict<String, R>) → Dict<String, PlanRow>` — the
42
- * series' whole pipeline (match filter → per-entry construction → groupBy
43
- * parents) reified once by its builder, taking the source's KEYED collection
44
- * and producing the canvas's. A leaf row's key is the source entry's key, so
45
- * the canvas is addressable by the keys the source is searched by (#568). The `rows` arm is literal one-off chrome and carries the
46
- * finished rows directly.
47
- *
48
- * @typeParam R - The East row type of the canvas's data source
49
- * @param r - The row type value
50
- * @returns The concrete `VariantType` of a series over `r`
69
+ * Every arm carries the series' identity and `derive: Fn(Dict<K, R>) →
70
+ * Array<PlanBlock>` — the blocks the series contributes (#823: a data series'
71
+ * one block of its entries' rows, each followed by its subtree; a section's
72
+ * header block, then its members'), reified once by its builder. The arm is
73
+ * the series' kind, which is all the series library reads beyond the identity.
74
+ *
75
+ * It carries its writer too (#880), `write: Fn(R, K, RowId, Gesture,
76
+ * Ref<Option<Blob>>) → Option<R>` — a gesture on one of its rows written into
77
+ * the entry the row came from, through the fields the series declares
78
+ * (`review.verdict`, `edit`), at whatever depth the row sits; `none` when no
79
+ * series in its tree takes the gesture on that row. The `Ref` is the item a
80
+ * move to another row carries (#825): the source row's write takes the item
81
+ * out of its list into it, as bytes, and the target row's write puts it into
82
+ * its own list — the two rows may be different series' and different
83
+ * entries'. A series list is an East value (a pick, a stored list), so the
84
+ * writer travels with the series rather than being rebuilt by the canvas.
85
+ *
86
+ * @param r - The entry type value
87
+ * @param k - The entries' key type (default `StringType`)
88
+ * @returns The concrete `VariantType` of a series over entries of `r` keyed by `k`
51
89
  */
52
- const seriesShape = (r) => {
53
- // Every data-driven arm carries the same identity + the same reified
54
- // pipeline. `key` and `title` are what let a series be listed, ordered and
55
- // persisted (#590 phase 1); `derive` is the pipeline itself.
56
- const identity = {
90
+ const seriesShape = (r, k = StringType) => {
91
+ const arm = StructType({
57
92
  key: StringType,
58
93
  title: StringType,
59
94
  subtitle: OptionType(StringType),
60
95
  icon: OptionType(IconType),
61
- };
62
- const identified = (rt) => ({
63
- ...identity,
64
- derive: FunctionType([DictType(StringType, rt)], PlanRowsCollectionType),
96
+ derive: FunctionType([DictType(k, r)], PlanBlocksType),
97
+ write: FunctionType([r, k, PlanRowIdType, PlanGestureType, PlanCarriedType], OptionType(r)),
65
98
  });
66
99
  return VariantType({
67
- span: StructType(identified(r)),
68
- buckets: StructType(identified(r)),
69
- chart: StructType(identified(r)),
70
- heat: StructType(identified(r)),
71
- table: StructType(identified(r)),
72
- cards: StructType(identified(r)),
73
- events: StructType(identified(r)),
74
- // `group` and `rows` carry identity too (#590 §6.1, resolved). A GROUP
75
- // is the unit a person actually picks — "add Machines" — and hiding one
76
- // takes its whole subtree with it, because the children are built by
77
- // the group's own `derive`. Without this the library could only offer
78
- // top-level series, which on a grouped canvas is almost nothing.
79
- group: StructType(identified(r)),
80
- rows: StructType({ ...identity, rows: PlanRowsCollectionType }),
100
+ span: arm, buckets: arm, chart: arm, heat: arm, table: arm, cards: arm, events: arm,
101
+ group: arm, section: arm, views: arm, rows: arm,
81
102
  });
82
103
  };
104
+ /**
105
+ * The Plan series type constructor — `Plan.Types.Series(RowType)` for a list
106
+ * of series over `Dict<String, RowType>` entries, `Plan.Types.Series(RowType,
107
+ * IntegerType)` over `Dict<Integer, RowType>`.
108
+ */
83
109
  export const PlanSeriesType = seriesShape;
84
- /** Pin one arm value against the instantiated series type. */
85
- function seriesValue(rowType, tag, payload,
86
- /** The series identity (#590) — every arm carries one. */
87
- identity) {
88
- const id = {
89
- key: identity.key,
90
- title: identity.title,
91
- subtitle: identity.subtitle !== undefined ? some(identity.subtitle) : none,
92
- icon: identity.icon !== undefined ? some(resolveIcon(identity.icon)) : none,
110
+ /** Marks a {@link PlanChildren} step-down. */
111
+ const PLAN_CHILDREN = Symbol("PlanChildren");
112
+ /**
113
+ * Step down from an entry to a child collection of another entry type (#822).
114
+ *
115
+ * @typeParam R - The parent entry type (inferred from where the step-down is used)
116
+ * @typeParam KT - The parent entries' key type
117
+ * @param of - The child collection — an `Array` (index order) or a `Dict` (key order) — from the parent entry
118
+ * @param series - The series over the child entries, laid out like a top-level list
119
+ * @returns The step-down, for a series' `children`
120
+ */
121
+ export function createChildren(of, series) {
122
+ return { [PLAN_CHILDREN]: true, of, series };
123
+ }
124
+ function isStepDown(x) {
125
+ return typeof x === "object" && x !== null && x[PLAN_CHILDREN] === true;
126
+ }
127
+ // ============================================================================
128
+ // Build specs — how each series is built for a collection type
129
+ // ============================================================================
130
+ const PathType = ArrayType(StringType);
131
+ const IdOptType = OptionType(PlanRowIdType);
132
+ /** Every series value a builder made, with its build spec. */
133
+ const SPECS = new WeakMap();
134
+ /** The spec of a series value a `Plan.series.*` builder made, if it was one. */
135
+ function specOf(s) {
136
+ return typeof s === "object" && s !== null ? SPECS.get(s) : undefined;
137
+ }
138
+ /** A series value inside a composite series — it must come from a builder, since the parent builds it for its own entries. */
139
+ function nestedSpec(s, where) {
140
+ const spec = specOf(s);
141
+ if (spec === undefined) {
142
+ throw new Error(`${where}: a nested series must be a \`Plan.series.*\` value written in place — the parent builds it ` +
143
+ "for its own entries, so a series bound to a variable elsewhere cannot be nested here");
144
+ }
145
+ return spec;
146
+ }
147
+ /** A readable name for a series, in messages. */
148
+ function nameOf(spec) {
149
+ return `${spec.arm} "${spec.title}"`;
150
+ }
151
+ /** A collection type's entry and key types — a `Dict`'s value and key, an `Array`'s element and `Integer` index. */
152
+ function shapeOf(collection, where) {
153
+ if (collection.type === "Dict")
154
+ return { entry: collection.value, key: collection.key };
155
+ if (collection.type === "Array")
156
+ return { entry: collection.value, key: IntegerType };
157
+ throw new Error(`${where}: entries come from a Dict (keyed) or an Array (by index) — got a ${collection.type}`);
158
+ }
159
+ /** Refuse a collection whose entries are not the series' entry type. */
160
+ function assertEntry(entry, rowType, where) {
161
+ if (!isTypeEqual(entry, rowType)) {
162
+ throw new Error(`${where}: the series is built over one entry type and applied to entries of another — ` +
163
+ `pass the entries' own type as the series' first argument`);
164
+ }
165
+ }
166
+ /** A path segment from a key — a String key as is, any other key as its `.east` text. */
167
+ function segmentOf(keyType) {
168
+ return keyType.type === "String"
169
+ ? (key) => key
170
+ : (key) => East.print(key);
171
+ }
172
+ /**
173
+ * A series' rows as ONE block (#823) — a data series' entries (`fixed: false`),
174
+ * or rows no entry produces (`fixed: true`: hand-built rows), under the parent
175
+ * the block nests in.
176
+ *
177
+ * @param collection - The collection type the rows are built for
178
+ * @param rows - The series' rows emitter for that collection
179
+ * @param fixed - Whether no entry produces the rows
180
+ * @returns The block list's emitter
181
+ */
182
+ function oneBlock(collection, rows, fixed) {
183
+ return East.function([collection, PathType, IdOptType], PlanBlocksType, ($, coll, prefix, parent) => {
184
+ const block = $.let(East.value({ fixed, parent, rows: rows(coll, prefix, parent) }, PlanBlockType), PlanBlockType);
185
+ return East.value([block], PlanBlocksType);
186
+ });
187
+ }
188
+ /** A cache of built functions by East type. */
189
+ class ByType {
190
+ entries = [];
191
+ get(type, build) {
192
+ const hit = this.entries.find((e) => isTypeEqual(e.type, type));
193
+ if (hit !== undefined)
194
+ return hit.value;
195
+ const value = build();
196
+ this.entries.push({ type, value });
197
+ return value;
198
+ }
199
+ }
200
+ /** Build `fn`, naming the series and the key type when its accessors do not build for it. */
201
+ function building(where, keyType, fn) {
202
+ try {
203
+ return fn();
204
+ }
205
+ catch (err) {
206
+ const kt = keyType.type;
207
+ const reason = err instanceof Error ? err.message : String(err);
208
+ throw new Error(`${where}: its accessors do not build for entries keyed by ${kt} (${reason}) — the key an accessor ` +
209
+ `receives is the collection's: the source's key, a Dict child's key, or an Array child's Integer ` +
210
+ "index. Declare the key type the accessors expect with `keyType`, or leave the key unread.");
211
+ }
212
+ }
213
+ /**
214
+ * How `accessor` reaches the child collection it returns, read off the
215
+ * expression it builds (#880): a plain field of the entry (`r => r.children`,
216
+ * `Plan.children(r => r.lines, …)`), or the entry itself (`g => g`). A write
217
+ * follows either back into the entry. Anything else — a filtered or computed
218
+ * collection — is a copy a write would be lost in, and is `undefined`.
219
+ *
220
+ * @param rowType - The entry type
221
+ * @param keyType - The entries' key type
222
+ * @param accessor - The accessor, over the entry (its node, when recursive) and key
223
+ * @returns How it reaches its collection, or `undefined` when a write cannot follow it
224
+ */
225
+ function childRefOf(rowType, keyType, accessor) {
226
+ const recursive = rowType.type === "Recursive";
227
+ let found;
228
+ East.function([rowType, keyType], undefined, (_$, v, k) => {
229
+ const node = recursive ? v.unwrap() : v;
230
+ const out = accessor(node, k);
231
+ const ast = Expr.ast(out);
232
+ const param = Expr.ast(v);
233
+ const nodeAst = Expr.ast(node);
234
+ if (ast === param || ast === nodeAst)
235
+ found = { kind: "self" };
236
+ else if (ast.ast_type === "GetField" && ast.field !== undefined && (ast.struct === nodeAst || ast.struct === param)) {
237
+ found = { kind: "field", field: ast.field };
238
+ }
239
+ return out;
240
+ });
241
+ return found;
242
+ }
243
+ /** Each key type's segment reader, built once. */
244
+ const segmentKeys = new ByType();
245
+ /**
246
+ * A path segment read back as its collection's key (#880) — the inverse of
247
+ * {@link segmentOf}: a String key as it is, any other key its `.east` text
248
+ * parsed (an `Array` child's segment is its Integer index).
249
+ *
250
+ * @param keyType - The collection's key type
251
+ * @returns `(segment) → key`
252
+ */
253
+ function segmentKeyOf(keyType) {
254
+ return segmentKeys.get(keyType, () => (keyType.type === "String"
255
+ ? East.function([StringType], StringType, (_$, s) => s)
256
+ : East.function([StringType], keyType, (_$, s) => s.parse(keyType))));
257
+ }
258
+ /**
259
+ * A child collection's part of a write (#880) — whether it holds a key, its
260
+ * entry at it, and that entry written back in place. A `Dict` by key; an
261
+ * `Array` by index.
262
+ */
263
+ function collectionOps(collection) {
264
+ if (collection.type === "Dict") {
265
+ const dict = (c) => c;
266
+ return {
267
+ has: (c, k) => dict(c).has(k),
268
+ get: (c, k) => dict(c).get(k),
269
+ put: (c, k, v) => dict(c).update(k, v),
270
+ };
271
+ }
272
+ const array = (c) => c;
273
+ const index = (k) => k;
274
+ return {
275
+ has: (c, k) => index(k).greaterEqual(0n).and(() => index(k).less(array(c).size())),
276
+ get: (c, k) => array(c).get(index(k)),
277
+ put: (c, k, v) => array(c).update(index(k), v),
93
278
  };
94
- return East.value(variant(tag, { ...id, ...payload }), PlanSeriesType(rowType));
95
279
  }
96
- /** Filter the source ENTRIES by the reified match predicate (all when omitted). */
97
- function matchedRows(entries, rowType, match) {
98
- if (match === undefined)
99
- return entries;
100
- const pred = East.function([rowType, StringType], BooleanType, (_$, r, k) => match(r, k));
101
- return entries.filter((_$, r, k) => pred(r, k));
280
+ /** The instant fields a kind's elements have (#825): two ends (a run, a chip) or one instant (a tile, a mark). */
281
+ function editShapeOf(arm) {
282
+ return arm === "span" || arm === "cards" ? "span" : "point";
102
283
  }
103
- /** The envelope's accessor-supplied Option fields for one data entry. */
104
- function envelopeOverrides(cfg, r, k) {
284
+ /** The item field types a move can write an instant into (#825). */
285
+ const INSTANT_FIELD_TYPES = [DateTimeType, FloatType, IntegerType, StringType, PlanInstantType];
286
+ /** Print an item type as the text rows of one item type share (#825) — East's own printer over its type value. */
287
+ const printType = printFor(EastTypeType);
288
+ /**
289
+ * Check what a series' `edit` declares for a move (#825), against its item
290
+ * type — `undefined` when it declares no move (only `create`).
291
+ *
292
+ * @param edit - The declaration
293
+ * @param itemType - The type of the items in `edit.items`
294
+ * @param arm - The series' kind
295
+ * @param where - The series, for the message
296
+ * @returns The move's fields
297
+ * @throws {Error} Naming the field and what is wrong with it
298
+ */
299
+ function moveFieldsOf(edit, itemType, arm, where) {
300
+ const shape = editShapeOf(arm);
301
+ if (shape === "span" && edit.at !== undefined) {
302
+ throw new Error(`${where}: \`edit.at\` is a tile's or a mark's instant — a ${arm} element has two ends: name them with \`start\` and \`end\``);
303
+ }
304
+ if (shape === "point" && (edit.start !== undefined || edit.end !== undefined)) {
305
+ throw new Error(`${where}: a ${arm} element has one instant — name it with \`edit.at\`, not \`start\` / \`end\``);
306
+ }
307
+ const start = shape === "span" ? edit.start : edit.at;
308
+ const end = shape === "span" ? edit.end : undefined;
309
+ if (shape === "span" && (start === undefined) !== (end === undefined)) {
310
+ throw new Error(`${where}: a move writes both ends of a ${arm} element — give \`edit.start\` and \`edit.end\` together`);
311
+ }
312
+ if (start === undefined) {
313
+ if (edit.create === undefined) {
314
+ throw new Error(`${where}: \`edit\` declares neither \`create\` (what a dropped card becomes) nor the item fields a move ` +
315
+ `writes (\`key\` and ${shape === "span" ? "`start` / `end`" : "`at`"}) — declare one or both`);
316
+ }
317
+ return undefined;
318
+ }
319
+ const item = itemType;
320
+ if (item.type !== "Struct") {
321
+ throw new Error(`${where}: a move writes fields of the item — the items in "${edit.items}" must be structs`);
322
+ }
323
+ if (edit.key === undefined) {
324
+ throw new Error(`${where}: a move finds its element's item by key — name the item's String key field with \`edit.key\``);
325
+ }
326
+ const keyType = item.fields[edit.key];
327
+ if (keyType === undefined || !isTypeEqual(keyType, StringType)) {
328
+ throw new Error(`${where}: \`edit.key\` names "${edit.key}", which is not a String field of the item`);
329
+ }
330
+ const instant = (name, what) => {
331
+ const type = item.fields[name];
332
+ if (type === undefined || !INSTANT_FIELD_TYPES.some((t) => isTypeEqual(t, type))) {
333
+ throw new Error(`${where}: \`edit.${what}\` names "${name}", which is not an instant field of the item — a DateTime, a Float or ` +
334
+ "an Integer, a String, or a Plan.Types.Instant");
335
+ }
336
+ if (name === edit.key) {
337
+ throw new Error(`${where}: \`edit.${what}\` names the key field "${name}" — a move would change the item's identity; name its instant field`);
338
+ }
339
+ return { name, type };
340
+ };
341
+ if (end !== undefined && end === start) {
342
+ throw new Error(`${where}: \`edit.start\` and \`edit.end\` both name "${start}" — a move writes two different fields`);
343
+ }
105
344
  return {
106
- ...(cfg.sub !== undefined ? { sub: cfg.sub(r, k) } : {}),
107
- ...(cfg.value !== undefined ? { value: cfg.value(r, k) } : {}),
108
- ...(cfg.status !== undefined ? { status: cfg.status(r, k) } : {}),
109
- ...(cfg.approval !== undefined ? { approval: cfg.approval(r, k) } : {}),
110
- ...(cfg.expand !== undefined ? { expand: cfg.expand(r, k) } : {}),
345
+ item,
346
+ key: edit.key,
347
+ start: instant(start, shape === "span" ? "start" : "at"),
348
+ end: end !== undefined ? instant(end, "end") : undefined,
349
+ edits: { items: printType(toEastTypeValue(itemType)), resize: shape === "span" },
111
350
  };
112
351
  }
352
+ /** Each instant field type's converter, built once (#825). */
353
+ const instantConverters = new ByType();
113
354
  /**
114
- * The shared scaffolding for ONE series' `derive` — the `series.ts` twin of
115
- * `data-forms.ts`'s `ofScaffold`: match filter → optional groupBy levels →
116
- * leaf rows, inside that single series' own reified function. NOT a
117
- * canvas-level compiler — the canvas never composes series into one
118
- * function; `applySeries` calls each series' `derive` in declared order.
355
+ * The converter from an axis instant into an item field's type (#825): the
356
+ * instant on the arm the field holds — a time instant's DateTime, a number
357
+ * instant's Float (an Integer field takes it rounded, half away from zero), an
358
+ * ordinal instant's String, or the instant itself — else `none`.
359
+ *
360
+ * @param fieldType - The field's type (one of {@link INSTANT_FIELD_TYPES})
361
+ * @returns `(instant) → Option<field>`
119
362
  */
120
- function seriesScaffold(rowType, match, groupBy, parentFn, row, prefix) {
121
- const sourceType = DictType(StringType, rowType);
122
- return East.function([sourceType], PlanRowsCollectionType, ($, entries) => {
123
- const matched = $.let(matchedRows(entries, rowType, match), sourceType);
124
- if (groupBy === undefined || groupBy.length === 0 || parentFn === undefined) {
125
- return foldEntriesToDict(matched, PlanRowsCollectionType, LAST_WINS, row);
363
+ function instantInto(fieldType) {
364
+ return instantConverters.get(fieldType, () => {
365
+ const out = OptionType(fieldType);
366
+ const kind = fieldType.type;
367
+ const whole = isTypeEqual(fieldType, PlanInstantType);
368
+ return East.function([PlanInstantType], out, ($, t) => {
369
+ const result = $.let(none, out);
370
+ if (whole) {
371
+ $.assign(result, some(t));
372
+ return result;
373
+ }
374
+ $.match(t, {
375
+ time: ($2, d) => {
376
+ if (kind === "DateTime")
377
+ $2.assign(result, some(d));
378
+ },
379
+ number: ($2, n) => {
380
+ if (kind === "Float") {
381
+ $2.assign(result, some(n));
382
+ }
383
+ else if (kind === "Integer") {
384
+ const rounded = $2.let(n.greaterEqual(0).ifElse(() => n.add(0.5).subtract(n.add(0.5).remainder(1)), () => n.subtract(0.5).subtract(n.subtract(0.5).remainder(1))), FloatType);
385
+ $2.assign(result, some(rounded.toInteger()));
386
+ }
387
+ },
388
+ ordinal: ($2, s) => {
389
+ if (kind === "String")
390
+ $2.assign(result, some(s));
391
+ },
392
+ });
393
+ return result;
394
+ });
395
+ });
396
+ }
397
+ /**
398
+ * The build spec of a data series (span / buckets / chart / heat / table /
399
+ * cards / events / group).
400
+ *
401
+ * @remarks
402
+ * Per key type it builds ONE entry-row function (the entry's gutter, kind and
403
+ * row facts through the one envelope) and the membership, collapse and
404
+ * children functions, each reified once and CALLED in the block's loop. The
405
+ * block walks the collection in order; each matched entry's row is followed by
406
+ * its children's rows — a bare `children` accessor walks the entry's subtree
407
+ * with an explicit pre-order stack (its depth is data), a step-down calls its
408
+ * child series' blocks on the child collection.
409
+ */
410
+ function dataSpec(rowType, cfg, recipe) {
411
+ const where = `Plan.series.${recipe.arm} "${cfg.key}"`;
412
+ const children = cfg.children;
413
+ const steps = children === undefined || typeof children === "function"
414
+ ? [] : (Array.isArray(children) ? children : [children]);
415
+ for (const step of steps) {
416
+ if (!isStepDown(step)) {
417
+ throw new Error(`${where}: \`children\` is an accessor for more of this series, or \`Plan.children(of, [series…])\` step-downs`);
418
+ }
419
+ }
420
+ const nested = steps.flatMap((s) => s.series.map((x) => nestedSpec(x, `${where} › children`)));
421
+ // A recursive entry reaches the accessors as its NODE (`PlanEntryExpr`),
422
+ // so they read its fields directly at every depth.
423
+ const recursive = rowType.type === "Recursive";
424
+ const nodeOf = (value) => recursive
425
+ ? value.unwrap()
426
+ : value;
427
+ /** An accessor over entries, applied to an entry value (its node, when recursive). */
428
+ const onEntry = (accessor) => (v, k) => accessor(nodeOf(v), k);
429
+ // What a gesture writes (#880, #825) — a verdict into `review.verdict`, a
430
+ // dropped card into `edit.items`, a moved element into its item's fields
431
+ // — each a FIELD of the entry (its node, when recursive) or of its items,
432
+ // checked here so a misnamed field fails the build.
433
+ const review = cfg.review;
434
+ const edit = recipe.edit;
435
+ const nodeType = (recursive ? rowType.node : rowType);
436
+ if ((review !== undefined || edit !== undefined) && nodeType.type !== "Struct") {
437
+ throw new Error(`${where}: \`review\` and \`edit\` write a field of the entry — its entries must be structs (a recursive entry's node included)`);
438
+ }
439
+ const entryFields = nodeType.fields ?? {};
440
+ if (review !== undefined) {
441
+ if (cfg.approval !== undefined) {
442
+ throw new Error(`${where}: give \`review.verdict\` (the field a verdict writes, which the row shows) or \`approval\` (a verdict the canvas only shows) — not both`);
443
+ }
444
+ const t = entryFields[review.verdict];
445
+ if (t === undefined || !isTypeEqual(t, ApprovalStateType)) {
446
+ throw new Error(`${where}: \`review.verdict\` names "${review.verdict}", which is not an ApprovalStateType field of the entry`);
447
+ }
448
+ }
449
+ let itemType;
450
+ let move;
451
+ if (edit !== undefined) {
452
+ const t = entryFields[edit.items];
453
+ if (t === undefined || t.type !== "Array") {
454
+ throw new Error(`${where}: \`edit.items\` names "${edit.items}", which is not an Array field of the entry — a gesture writes a list of items`);
455
+ }
456
+ itemType = t.value;
457
+ move = moveFieldsOf(edit, itemType, recipe.arm, where);
458
+ }
459
+ const edits = review !== undefined || edit !== undefined
460
+ ? {
461
+ verdict: review !== undefined,
462
+ drop: edit?.create !== undefined,
463
+ move: move !== undefined ? some(move.edits) : none,
464
+ }
465
+ : undefined;
466
+ const rows = new ByType();
467
+ const matches = new ByType();
468
+ const collapses = new ByType();
469
+ const kids = new ByType();
470
+ const walks = new ByType();
471
+ const emitters = new ByType();
472
+ const blockLists = new ByType();
473
+ const rowFor = (kt) => rows.get(kt, () => building(where, kt, () => East.function([rowType, kt, PlanRowIdType, IdOptType, BooleanType, BooleanType], PlanRowType, ($, value, key, id, parent, hasChildren, collapsed) => {
474
+ // The node every accessor reads, unwrapped once.
475
+ const entry = recursive ? $.let(nodeOf(value)) : value;
476
+ return planRow({
477
+ id, parent,
478
+ gutter: planGutter({
479
+ label: cfg.label(entry, key),
480
+ ...(cfg.id !== undefined ? { id: cfg.id } : {}),
481
+ ...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
482
+ ...(cfg.sub !== undefined ? { sub: cfg.sub(entry, key) } : {}),
483
+ ...(cfg.value !== undefined ? { value: cfg.value(entry, key) } : {}),
484
+ ...(recipe.swatches !== undefined ? { swatches: recipe.swatches } : {}),
485
+ }),
486
+ kind: recipe.kind(entry, key, hasChildren),
487
+ collapsed,
488
+ ...(recipe.pinned !== undefined ? { pinned: recipe.pinned(entry, key) } : {}),
489
+ ...(cfg.status !== undefined ? { status: cfg.status(entry, key) } : {}),
490
+ // A verdict the canvas takes shows from its field (#880) — so
491
+ // a drafted verdict draws where it was made.
492
+ ...(review !== undefined
493
+ ? { approval: some(entry[review.verdict]) }
494
+ : cfg.approval !== undefined ? { approval: cfg.approval(entry, key) } : {}),
495
+ ...(cfg.expand !== undefined ? { expand: cfg.expand(entry, key) } : {}),
496
+ ...(edits !== undefined ? { edits } : {}),
497
+ });
498
+ })));
499
+ const matchFor = (kt) => matches.get(kt, () => {
500
+ const match = cfg.match;
501
+ if (match === undefined)
502
+ return undefined;
503
+ return building(where, kt, () => East.function([rowType, kt], BooleanType, (_$, value, key) => match(nodeOf(value), key)));
504
+ });
505
+ const collapsedFor = (kt) => collapses.get(kt, () => {
506
+ const collapsed = cfg.collapsed;
507
+ if (collapsed === undefined)
508
+ return undefined;
509
+ return building(where, kt, () => East.function([rowType, kt], BooleanType, (_$, value, key) => typeof collapsed === "boolean"
510
+ ? East.value(collapsed, BooleanType)
511
+ : East.value(collapsed(nodeOf(value), key), BooleanType)));
512
+ });
513
+ /** A bare `children` accessor, built for entries keyed by `kt` — the child collection's type is its output. */
514
+ const childOf = (kt) => building(where, kt, () => reifyAccessor([rowType, kt], onEntry(children)));
515
+ /** The subtree walk below one entry — every level's entries in pre-order, over the child collection type. */
516
+ const walkFor = (cc) => walks.get(cc, () => {
517
+ const at = `${where} › children`;
518
+ const { entry, key: kc } = shapeOf(cc, at);
519
+ assertEntry(entry, rowType, `${at} (a bare accessor returns more of this series' entries — step down to another type with Plan.children)`);
520
+ const next = childOf(kc);
521
+ const nextType = Expr.type(next).output;
522
+ if (!isTypeEqual(nextType, cc)) {
523
+ throw new Error(`${at}: a recursive series' children must be the same collection type at every level`);
524
+ }
525
+ const row = rowFor(kc);
526
+ const match = matchFor(kc);
527
+ const collapsed = collapsedFor(kc);
528
+ const seg = segmentOf(kc);
529
+ const Frame = StructType({ value: rowType, key: kc, path: PathType, parent: PlanRowIdType });
530
+ const Frames = ArrayType(Frame);
531
+ // A level's matched entries as frames, REVERSED — so the stack
532
+ // pops the first of them first.
533
+ const framesOf = East.function([cc, PathType, PlanRowIdType], Frames, ($, coll, path, parentId) => {
534
+ const frames = $.let([], Frames);
535
+ const member = match === undefined ? undefined : $.const(match);
536
+ $.for(coll, ($2, value, key) => {
537
+ const add = ($3) => {
538
+ const segs = $3.let(East.value([seg(key)], PathType), PathType);
539
+ $3(frames.pushLast(East.value({ value, key, path: path.concat(segs), parent: parentId }, Frame)));
540
+ };
541
+ if (member === undefined)
542
+ add($2);
543
+ else
544
+ $2.if(member(value, key), add);
545
+ });
546
+ $(frames.reverseInPlace());
547
+ return frames;
548
+ });
549
+ return East.function([cc, PathType, PlanRowIdType], PlanRowsCollectionType, ($, coll, path0, parent0) => {
550
+ // Every function the loop calls, bound ONCE — a captured function
551
+ // referenced in the loop would be re-inlined per use.
552
+ const frames = $.const(framesOf);
553
+ const childrenOf = $.const(next);
554
+ const entryRow = $.const(row);
555
+ const collapse = collapsed === undefined ? undefined : $.const(collapsed);
556
+ const out = $.let([], PlanRowsCollectionType);
557
+ const stack = $.let(frames(coll, path0, parent0), Frames);
558
+ $.while(stack.size().greater(0n), ($2) => {
559
+ const f = $2.let(stack.popLast(), Frame);
560
+ const id = $2.let(East.value(variant("entry", { series: cfg.key, path: f.path }), PlanRowIdType), PlanRowIdType);
561
+ const below = $2.let(frames(childrenOf(f.value, f.key), f.path, id), Frames);
562
+ const parent = $2.let(East.value(some(f.parent), IdOptType), IdOptType);
563
+ const fold = $2.let(collapse === undefined
564
+ ? East.value(false, BooleanType)
565
+ : collapse(f.value, f.key), BooleanType);
566
+ $2(out.pushLast(entryRow(f.value, f.key, id, parent, below.size().greater(0n), fold)));
567
+ $2(stack.append(below));
568
+ });
569
+ return out;
570
+ });
571
+ });
572
+ const childrenFor = (kt) => kids.get(kt, () => {
573
+ if (children === undefined)
574
+ return undefined;
575
+ if (typeof children === "function") {
576
+ const of = childOf(kt);
577
+ const cc = Expr.type(of).output;
578
+ const walk = walkFor(cc);
579
+ return East.function([rowType, kt, PlanRowIdType, PathType], PlanRowsCollectionType, (_$, value, key, id, path) => walk(of(value, key), path, id));
580
+ }
581
+ const parts = steps.map((step, i) => {
582
+ const at = `${where} › children[${i}]`;
583
+ const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(step.of)));
584
+ const cc = Expr.type(of).output;
585
+ const blocks = step.series.map((s) => nestedSpec(s, at).emitter(cc, at));
586
+ return { of, cc, blocks };
587
+ });
588
+ return East.function([rowType, kt, PlanRowIdType, PathType], PlanRowsCollectionType, ($, value, key, id, path) => {
589
+ const out = $.let([], PlanRowsCollectionType);
590
+ const under = $.let(East.value(some(id), IdOptType), IdOptType);
591
+ for (const part of parts) {
592
+ const coll = $.let(part.of(value, key), part.cc);
593
+ for (const block of part.blocks)
594
+ $(out.append(block(coll, path, under)));
595
+ }
596
+ return out;
597
+ });
598
+ });
599
+ // ── Writing a gesture back (#880) ─────────────────────────────────────
600
+ // The entry a writer is handed is DETACHED (the series value's `write`
601
+ // copies it), so a nested entry is written in place in its collection and
602
+ // a dropped card's item joins its list in place: only an entry whose OWN
603
+ // field changes — a verdict — is rebuilt.
604
+ const owns = new ByType();
605
+ const writers = new ByType();
606
+ const entryAccess = (value) => nodeOf(value);
607
+ /** The entry with its verdict field set — rebuilt, every other field as it was. */
608
+ const withVerdict = review === undefined ? undefined : East.function([rowType, ApprovalStateType], rowType, ($, value, verdict) => {
609
+ const node = recursive ? $.let(nodeOf(value)) : value;
610
+ const read = node;
611
+ const rebuilt = East.value(Object.fromEntries(Object.keys(entryFields).map((f) => [f, f === review.verdict ? verdict : read[f]])), nodeType);
612
+ return recursive ? East.wrapRecursive(rebuilt, rowType) : rebuilt;
613
+ });
614
+ /** The item a dropped card becomes, for entries keyed by `kt`. */
615
+ const createFor = (kt, create) => building(where, kt, () => East.function([PlanDropType, rowType, kt], itemType, (_$, drop, value, key) => create(drop, nodeOf(value), key)));
616
+ /**
617
+ * An item at a move's instants (#825) — its declared instant fields set
618
+ * from the axis's instants, each on its own arm, every other field as it
619
+ * was; `none` when an instant rides an arm the field cannot hold (a time
620
+ * instant into a Float field).
621
+ */
622
+ const placed = move === undefined ? undefined : (() => {
623
+ const m = move;
624
+ const startAs = instantInto(m.start.type);
625
+ const endAs = m.end !== undefined ? instantInto(m.end.type) : undefined;
626
+ return East.function([m.item, PlanInstantType, PlanInstantType], OptionType(m.item), ($, item, start, end) => {
627
+ const result = $.let(none, OptionType(m.item));
628
+ const asStart = $.const(startAs);
629
+ const s = $.let(asStart(start), OptionType(m.start.type));
630
+ const read = item;
631
+ const rebuild = (fields) => East.value(Object.fromEntries(Object.keys(m.item.fields).map((f) => [f, fields[f] ?? read[f]])), m.item);
632
+ $.match(s, {
633
+ some: ($2, sv) => {
634
+ if (endAs === undefined || m.end === undefined) {
635
+ $2.assign(result, some(rebuild({ [m.start.name]: sv })));
636
+ return;
637
+ }
638
+ const endName = m.end.name;
639
+ const asEnd = $2.const(endAs);
640
+ const e = $2.let(asEnd(end), OptionType(m.end.type));
641
+ $2.match(e, {
642
+ some: ($3, ev) => {
643
+ $3.assign(result, some(rebuild({ [m.start.name]: sv, [endName]: ev })));
644
+ },
645
+ });
646
+ },
647
+ });
648
+ return result;
649
+ });
650
+ })();
651
+ /**
652
+ * Whether a list holds an item keyed `k` (#825) — the key a move finds its
653
+ * item by, so a row's list keeps its keys unique: a card whose item repeats
654
+ * one, or a move onto a row already holding the moved item's key, is
655
+ * refused rather than leaving two items one key names.
656
+ */
657
+ const holds = move === undefined ? undefined : (list, k) => list.some((_$, item) => East.equal(item[move.key], k));
658
+ /**
659
+ * This series' own edit on one of its entries — the row at `path` is its
660
+ * own: a verdict into its field, a dropped card's item into its list, and
661
+ * a moved element's item re-timed where it is, taken out of the list, or
662
+ * put into it (#825).
663
+ */
664
+ const ownFor = (kt) => owns.get(kt, () => {
665
+ if (review === undefined && edit === undefined)
666
+ return undefined;
667
+ const create = edit?.create !== undefined ? createFor(kt, edit.create) : undefined;
668
+ return East.function([rowType, kt, PathType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, gesture, carried) => {
669
+ const setVerdict = withVerdict === undefined ? undefined : $.const(withVerdict);
670
+ const make = create === undefined ? undefined : $.const(create);
671
+ const place = placed === undefined ? undefined : $.const(placed);
672
+ const result = $.let(none, OptionType(rowType));
673
+ $.match(gesture, {
674
+ verdict: ($2, verdict) => {
675
+ if (setVerdict !== undefined)
676
+ $2.assign(result, some(setVerdict(value, verdict)));
677
+ },
678
+ drop: ($2, drop) => {
679
+ if (make === undefined || edit === undefined)
680
+ return;
681
+ const list = $2.let(entryAccess(value)[edit.items]);
682
+ const item = $2.let(make(drop, value, key), itemType);
683
+ const add = ($3) => {
684
+ $3(list.pushLast(item));
685
+ $3.assign(result, some(value));
686
+ };
687
+ // With the item's key declared, a card whose item
688
+ // repeats a key in the list is refused (#825).
689
+ if (holds === undefined || move === undefined)
690
+ add($2);
691
+ else
692
+ $2.if(holds(list, item[move.key]).not(), add);
693
+ },
694
+ move: ($2, mv) => {
695
+ if (place === undefined || move === undefined || edit === undefined)
696
+ return;
697
+ const keyField = move.key;
698
+ const itemOf = move.item;
699
+ const here = $2.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
700
+ const list = $2.let(entryAccess(value)[edit.items]);
701
+ // The element's item, by its key.
702
+ const at = $2.let(list.firstMap(($3, item, i) => East.equal(item[keyField], mv.key).ifElse(() => East.value(some(i), OptionType(IntegerType)), () => East.value(none, OptionType(IntegerType)))), OptionType(IntegerType));
703
+ $2.if(East.equal(here, mv.from).and(() => East.equal(here, mv.to)), ($3) => {
704
+ // Along its row, or resized: its instants set where it is.
705
+ $3.match(at, {
706
+ some: ($4, i) => {
707
+ const moved = $4.let(place(list.get(i), mv.start, mv.end), OptionType(itemOf));
708
+ $4.match(moved, {
709
+ some: ($5, item) => {
710
+ $5(list.update(i, item));
711
+ $5.assign(result, some(value));
712
+ },
713
+ });
714
+ },
715
+ });
716
+ }).else(($3) => {
717
+ // Its source: taken out of the list, and carried.
718
+ $3.if(East.equal(here, mv.from), ($4) => {
719
+ $4.match(at, {
720
+ some: ($5, i) => {
721
+ $5(carried.update(East.value(some(East.Blob.encodeBeast(list.get(i), "v2")), OptionType(BlobType))));
722
+ const before = $5.let(list.slice(0n, i));
723
+ const after = $5.let(list.slice(i.add(1n), list.size()));
724
+ $5(list.clear());
725
+ $5(list.append(before));
726
+ $5(list.append(after));
727
+ $5.assign(result, some(value));
728
+ },
729
+ });
730
+ });
731
+ // Its target: the carried item put in, at its new
732
+ // instants — unless the list already holds its key,
733
+ // when it is put nowhere and the move lands nowhere.
734
+ $3.if(East.equal(here, mv.to), ($4) => {
735
+ const held = $4.let(carried.get(), OptionType(BlobType));
736
+ $4.match(held, {
737
+ some: ($5, bytes) => {
738
+ $5.if(holds(list, mv.key).not(), ($6) => {
739
+ const item = $6.let(bytes.decodeBeast(itemOf, "v2"), itemOf);
740
+ const moved = $6.let(place(item, mv.start, mv.end), OptionType(itemOf));
741
+ $6.match(moved, {
742
+ some: ($7, placedItem) => {
743
+ $7(list.pushLast(placedItem));
744
+ $7(carried.update(East.value(none, OptionType(BlobType))));
745
+ $7.assign(result, some(value));
746
+ },
747
+ });
748
+ });
749
+ },
750
+ });
751
+ });
752
+ });
753
+ },
754
+ });
755
+ return result;
756
+ });
757
+ });
758
+ /** The walk's writer — a bare `children` accessor: the rows below an entry are this series' own. */
759
+ const walkWriterFor = (kt) => {
760
+ const at = `${where} › children`;
761
+ const of = childOf(kt);
762
+ const cc = Expr.type(of).output;
763
+ const { key: kc } = shapeOf(cc, at);
764
+ const own = ownFor(kc);
765
+ if (own === undefined)
766
+ return undefined;
767
+ const ref = childRefOf(rowType, kt, children);
768
+ if (ref === undefined) {
769
+ throw new Error(`${at}: this series takes gestures, and one on a child row is written back into its entry through ` +
770
+ "`children` — which must read a field of the entry (`r => r.children`), not compute a collection");
126
771
  }
127
- // Every level shares ONE parent constructor (the ofScaffold shape).
128
- const shared = parentFn();
129
- const levels = groupBy.map((by) => ({
130
- by: reifyLevelKey(rowType, by),
131
- parentFn: shared,
132
- }));
133
- // The prefix keys the SYNTHESIZED parents; leaf keys are the source's
134
- // own, carried through by `row` — see `groupRows`.
135
- return groupRows(matched, levels, (subset, _prefix) => foldEntriesToDict(subset, PlanRowsCollectionType, LAST_WINS, row), prefix ?? "");
772
+ const ops = collectionOps(cc);
773
+ const collOf = (entry) => (ref.kind === "field" ? entryAccess(entry)[ref.field] : nodeOf(entry));
774
+ return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, _key, path, depth, series, gesture, carried) => {
775
+ const result = $.let(none, OptionType(rowType));
776
+ // The walk's rows are this series' own, at every depth.
777
+ $.if(series.equal(cfg.key), ($2) => {
778
+ const write = $2.const(own);
779
+ const keyOf = $2.const(segmentKeyOf(kc));
780
+ const cur = $2.let(value, rowType);
781
+ const d = $2.let(depth, IntegerType);
782
+ const last = $2.let(path.size().subtract(1n), IntegerType);
783
+ // Down to the target's parent, then the target itself.
784
+ $2.while(d.less(last), ($3) => {
785
+ const coll = $3.let(collOf(cur), cc);
786
+ $3.assign(cur, ops.get(coll, keyOf(path.get(d))));
787
+ $3.assign(d, d.add(1n));
788
+ });
789
+ const coll = $2.let(collOf(cur), cc);
790
+ const k = $2.let(keyOf(path.get(last)), kc);
791
+ const written = $2.let(write(ops.get(coll, k), k, path, gesture, carried), OptionType(rowType));
792
+ $2.match(written, {
793
+ some: ($3, entry) => {
794
+ $3(ops.put(coll, k, entry));
795
+ $3.assign(result, some(value));
796
+ },
797
+ });
798
+ });
799
+ return result;
800
+ });
801
+ };
802
+ /** The step-downs' writer: a row below an entry is one of a step-down's series', in that step's collection. */
803
+ const stepsWriterFor = (kt) => {
804
+ const parts = steps.flatMap((step, i) => {
805
+ const at = `${where} › children[${i}]`;
806
+ const accessor = step.of;
807
+ const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(accessor)));
808
+ const cc = Expr.type(of).output;
809
+ const writes = step.series
810
+ .map((s) => nestedSpec(s, at).writer(cc, at))
811
+ .filter((w) => w !== undefined);
812
+ if (writes.length === 0)
813
+ return [];
814
+ const ref = childRefOf(rowType, kt, accessor);
815
+ if (ref === undefined) {
816
+ throw new Error(`${at}: a series under this step-down takes gestures, and one on its rows is written back into ` +
817
+ "the entry through `Plan.children(of, …)` — `of` must read a field of the entry (`r => r.lines`) " +
818
+ "or be the entry itself (`g => g`), not compute a collection");
819
+ }
820
+ const { entry: childType, key: kc } = shapeOf(cc, at);
821
+ return [{ ref, cc, kc, childType, writes }];
822
+ });
823
+ if (parts.length === 0)
824
+ return undefined;
825
+ return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, _key, path, depth, series, gesture, carried) => {
826
+ const result = $.let(none, OptionType(rowType));
827
+ for (const part of parts) {
828
+ const ops = collectionOps(part.cc);
829
+ const keyOf = $.const(segmentKeyOf(part.kc));
830
+ const writes = part.writes.map((w) => $.const(w));
831
+ $.if(result.hasTag("none"), ($2) => {
832
+ const coll = $2.let(part.ref.kind === "field" ? entryAccess(value)[part.ref.field] : nodeOf(value), part.cc);
833
+ const k = $2.let(keyOf(path.get(depth)), part.kc);
834
+ $2.if(ops.has(coll, k), ($3) => {
835
+ const child = $3.let(ops.get(coll, k), part.childType);
836
+ for (const write of writes) {
837
+ $3.if(result.hasTag("none"), ($4) => {
838
+ const written = $4.let(write(child, k, path, depth.add(1n), series, gesture, carried), OptionType(part.childType));
839
+ $4.match(written, {
840
+ some: ($5, entry) => {
841
+ $5(ops.put(coll, k, entry));
842
+ $5.assign(result, some(value));
843
+ },
844
+ });
845
+ });
846
+ }
847
+ });
848
+ });
849
+ }
850
+ return result;
851
+ });
852
+ };
853
+ /** This series' writer for entries keyed by `kt`: its own rows' edits, and the rows below them. */
854
+ const writerFor = (kt) => writers.get(kt, () => {
855
+ const own = ownFor(kt);
856
+ const below = children === undefined ? undefined
857
+ : typeof children === "function" ? walkWriterFor(kt) : stepsWriterFor(kt);
858
+ if (own === undefined && below === undefined)
859
+ return undefined;
860
+ return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, depth, series, gesture, carried) => {
861
+ const result = $.let(none, OptionType(rowType));
862
+ $.if(depth.equal(path.size()), ($2) => {
863
+ if (own === undefined)
864
+ return;
865
+ const write = $2.const(own);
866
+ $2.if(series.equal(cfg.key), ($3) => { $3.assign(result, write(value, key, path, gesture, carried)); });
867
+ }).else(($2) => {
868
+ if (below === undefined)
869
+ return;
870
+ const write = $2.const(below);
871
+ $2.assign(result, write(value, key, path, depth, series, gesture, carried));
872
+ });
873
+ return result;
874
+ });
136
875
  });
876
+ const spec = {
877
+ key: cfg.key,
878
+ title: cfg.title,
879
+ arm: recipe.arm,
880
+ nested,
881
+ nests: children !== undefined,
882
+ emitter(collection, at) {
883
+ return emitters.get(collection, () => {
884
+ const { entry, key: kt } = shapeOf(collection, at);
885
+ assertEntry(entry, rowType, where);
886
+ const row = rowFor(kt);
887
+ const match = matchFor(kt);
888
+ const collapsed = collapsedFor(kt);
889
+ const below = childrenFor(kt);
890
+ const seg = segmentOf(kt);
891
+ return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
892
+ // Every function the loop calls, bound ONCE (see the walk).
893
+ const entryRow = $.const(row);
894
+ const member = match === undefined ? undefined : $.const(match);
895
+ const collapse = collapsed === undefined ? undefined : $.const(collapsed);
896
+ const childRows = below === undefined ? undefined : $.const(below);
897
+ const out = $.let([], PlanRowsCollectionType);
898
+ $.for(coll, ($2, value, key) => {
899
+ const emit = ($3) => {
900
+ const segs = $3.let(East.value([seg(key)], PathType), PathType);
901
+ const path = $3.let(prefix.concat(segs), PathType);
902
+ const id = $3.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
903
+ const fold = $3.let(collapse === undefined
904
+ ? East.value(false, BooleanType)
905
+ : collapse(value, key), BooleanType);
906
+ if (childRows === undefined) {
907
+ $3(out.pushLast(entryRow(value, key, id, parent, false, fold)));
908
+ return;
909
+ }
910
+ // The children are built first: a row rolls up
911
+ // (span bands) only when it has child rows.
912
+ const sub = $3.let(childRows(value, key, id, path), PlanRowsCollectionType);
913
+ $3(out.pushLast(entryRow(value, key, id, parent, sub.size().greater(0n), fold)));
914
+ $3(out.append(sub));
915
+ };
916
+ if (member === undefined)
917
+ emit($2);
918
+ else
919
+ $2.if(member(value, key), emit);
920
+ });
921
+ return out;
922
+ });
923
+ });
924
+ },
925
+ // One block of the entries' rows — what a paged canvas pages (#823).
926
+ blocks(collection, at) {
927
+ return blockLists.get(collection, () => oneBlock(collection, spec.emitter(collection, at), false));
928
+ },
929
+ entryParts(entry, kt, at) {
930
+ assertEntry(entry, rowType, at);
931
+ return { match: matchFor(kt), row: rowFor(kt) };
932
+ },
933
+ childrenFor,
934
+ collapsedFor,
935
+ writer(collection, at) {
936
+ const { entry, key: kt } = shapeOf(collection, at);
937
+ assertEntry(entry, rowType, where);
938
+ return writerFor(kt);
939
+ },
940
+ ownFor,
941
+ };
942
+ return spec;
137
943
  }
138
- /** Apply one series value to a rows expression (the exhaustive-arm call). */
139
- export function applySeriesValue(s, rows) {
944
+ /** The build spec of a `views` series. */
945
+ function viewsSpec(rowType, cfg, members) {
946
+ const where = `Plan.series.views "${cfg.key}"`;
947
+ if (members.length === 0)
948
+ throw new Error(`${where}: a views series lists at least one member series`);
949
+ const memberSpecs = members.map((m, i) => {
950
+ const spec = nestedSpec(m, `${where}[${i}]`);
951
+ if (spec.entryParts === undefined || spec.arm === "group") {
952
+ throw new Error(`${where}: member ${nameOf(spec)} is not a row series — a view is one row per entry (span, buckets, chart, heat, table, cards or events)`);
953
+ }
954
+ if (spec.nests) {
955
+ throw new Error(`${where}: member ${nameOf(spec)} declares \`children\` — an entry's children belong to the views (they nest under its first view row); move \`children\` there`);
956
+ }
957
+ return spec;
958
+ });
959
+ // The views' own membership, collapse and children are a data series'
960
+ // envelope without a row of its own — built by the same machinery, so a
961
+ // views' children nest and recurse exactly as a data series' do.
962
+ const host = dataSpec(rowType, {
963
+ key: cfg.key, title: cfg.title, label: () => "",
964
+ ...(cfg.keyType !== undefined ? { keyType: cfg.keyType } : {}),
965
+ ...(cfg.match !== undefined ? { match: cfg.match } : {}),
966
+ ...(cfg.children !== undefined ? { children: cfg.children } : {}),
967
+ ...(cfg.collapsed !== undefined ? { collapsed: cfg.collapsed } : {}),
968
+ }, { arm: "views", kind: () => groupKind(East.value(variant("none", null), PlanGroupSummaryType)) });
969
+ const emitters = new ByType();
970
+ const blockLists = new ByType();
971
+ const writerLists = new ByType();
972
+ const spec = {
973
+ key: cfg.key,
974
+ title: cfg.title,
975
+ arm: "views",
976
+ nested: [...memberSpecs, ...host.nested],
977
+ nests: cfg.children !== undefined,
978
+ emitter(collection, at) {
979
+ return emitters.get(collection, () => {
980
+ const { entry, key: kt } = shapeOf(collection, at);
981
+ assertEntry(entry, rowType, where);
982
+ const parts = memberSpecs.map((m) => m.entryParts(entry, kt, `${where} › ${nameOf(m)}`));
983
+ const hostParts = host.entryParts(entry, kt, where);
984
+ const hostKids = host.childrenFor(kt);
985
+ const hostCollapse = host.collapsedFor(kt);
986
+ const seg = segmentOf(kt);
987
+ return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
988
+ // Every function the loop calls, bound ONCE (see the data walk).
989
+ const rowsOf = parts.map((p) => $.const(p.row));
990
+ const showsOf = parts.map((p) => (p.match === undefined ? undefined : $.const(p.match)));
991
+ const member = hostParts.match === undefined ? undefined : $.const(hostParts.match);
992
+ const childRows = hostKids === undefined ? undefined : $.const(hostKids);
993
+ const collapse = hostCollapse === undefined ? undefined : $.const(hostCollapse);
994
+ const out = $.let([], PlanRowsCollectionType);
995
+ $.for(coll, ($2, value, key) => {
996
+ const emit = ($3) => {
997
+ const segs = $3.let(East.value([seg(key)], PathType), PathType);
998
+ const path = $3.let(prefix.concat(segs), PathType);
999
+ const ids = memberSpecs.map((m) => $3.let(East.value(variant("entry", { series: m.key, path }), PlanRowIdType), PlanRowIdType));
1000
+ const present = showsOf.map((shows) => (shows === undefined
1001
+ ? undefined
1002
+ : $3.let(shows(value, key), BooleanType)));
1003
+ // The first member whose row shows — the entry's
1004
+ // children nest under it.
1005
+ const first = $3.let(-1n, IntegerType);
1006
+ for (let i = parts.length - 1; i >= 0; i--) {
1007
+ const shows = present[i];
1008
+ if (shows === undefined)
1009
+ $3.assign(first, BigInt(i));
1010
+ else
1011
+ $3.if(shows, ($4) => { $4.assign(first, BigInt(i)); });
1012
+ }
1013
+ let sub;
1014
+ if (childRows !== undefined) {
1015
+ const firstId = $3.let(ids[0], PlanRowIdType);
1016
+ for (let i = 1; i < ids.length; i++) {
1017
+ $3.if(first.equal(BigInt(i)), ($4) => { $4.assign(firstId, ids[i]); });
1018
+ }
1019
+ sub = $3.let(first.greaterEqual(0n).ifElse(() => childRows(value, key, firstId, path), () => East.value([], PlanRowsCollectionType)), PlanRowsCollectionType);
1020
+ }
1021
+ const fold = $3.let(collapse === undefined
1022
+ ? East.value(false, BooleanType)
1023
+ : collapse(value, key), BooleanType);
1024
+ const noFold = $3.let(East.value(false, BooleanType), BooleanType);
1025
+ parts.forEach((_p, i) => {
1026
+ const isFirst = $3.let(first.equal(BigInt(i)), BooleanType);
1027
+ const kids = sub;
1028
+ const hasChildren = $3.let(kids === undefined
1029
+ ? East.value(false, BooleanType)
1030
+ : isFirst.and(() => kids.size().greater(0n)), BooleanType);
1031
+ const viewRow = rowsOf[i];
1032
+ const push = ($4) => {
1033
+ $4(out.pushLast(viewRow(value, key, ids[i], parent, hasChildren, isFirst.ifElse(() => fold, () => noFold))));
1034
+ };
1035
+ const shows = present[i];
1036
+ if (shows === undefined)
1037
+ push($3);
1038
+ else
1039
+ $3.if(shows, push);
1040
+ });
1041
+ if (sub !== undefined)
1042
+ $3(out.append(sub));
1043
+ };
1044
+ if (member === undefined)
1045
+ emit($2);
1046
+ else
1047
+ $2.if(member(value, key), emit);
1048
+ });
1049
+ return out;
1050
+ });
1051
+ });
1052
+ },
1053
+ // One block of the entries' view rows — what a paged canvas pages (#823).
1054
+ blocks(collection, at) {
1055
+ return blockLists.get(collection, () => oneBlock(collection, spec.emitter(collection, at), false));
1056
+ },
1057
+ // An entry's view rows are its members' own (each at the entry's path,
1058
+ // under its member's key), and the rows below them the views' (#880).
1059
+ writer(collection, at) {
1060
+ return writerLists.get(collection, () => {
1061
+ const { entry, key: kt } = shapeOf(collection, at);
1062
+ assertEntry(entry, rowType, where);
1063
+ const owns = memberSpecs.flatMap((m) => {
1064
+ const own = m.ownFor?.(kt);
1065
+ return own === undefined ? [] : [{ key: m.key, own }];
1066
+ });
1067
+ const below = host.writer(collection, at);
1068
+ if (owns.length === 0 && below === undefined)
1069
+ return undefined;
1070
+ return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, depth, series, gesture, carried) => {
1071
+ const result = $.let(none, OptionType(rowType));
1072
+ $.if(depth.equal(path.size()), ($2) => {
1073
+ for (const o of owns) {
1074
+ const write = $2.const(o.own);
1075
+ $2.if(series.equal(o.key), ($3) => { $3.assign(result, write(value, key, path, gesture, carried)); });
1076
+ }
1077
+ }).else(($2) => {
1078
+ if (below === undefined)
1079
+ return;
1080
+ const write = $2.const(below);
1081
+ $2.assign(result, write(value, key, path, depth, series, gesture, carried));
1082
+ });
1083
+ return result;
1084
+ });
1085
+ });
1086
+ },
1087
+ };
1088
+ return spec;
1089
+ }
1090
+ /** The build spec of a `section`. */
1091
+ function sectionSpec(cfg, members) {
1092
+ const where = `Plan.series.section "${cfg.key}"`;
1093
+ const memberSpecs = members.map((m, i) => nestedSpec(m, `${where}[${i}]`));
1094
+ const summary = groupSummary(cfg.summary, cfg.summaryAggregate, where);
1095
+ /** The header row — at `id`, nested under `parent`. */
1096
+ const header = (id, parent) => planRow({
1097
+ id, parent,
1098
+ gutter: planGutter({
1099
+ label: cfg.title,
1100
+ ...(cfg.meta !== undefined ? { meta: some(cfg.meta) } : {}),
1101
+ ...(cfg.value !== undefined ? { value: some(cfg.value) } : {}),
1102
+ }),
1103
+ kind: groupKind(summary),
1104
+ collapsed: cfg.collapsed ?? false,
1105
+ ...(cfg.status !== undefined ? { status: some(resolveTag(cfg.status, StatusValueType)) } : {}),
1106
+ });
1107
+ const emitters = new ByType();
1108
+ const blockLists = new ByType();
1109
+ const writerLists = new ByType();
1110
+ return {
1111
+ key: cfg.key,
1112
+ title: cfg.title,
1113
+ arm: "section",
1114
+ nested: memberSpecs,
1115
+ nests: false,
1116
+ emitter(collection, at) {
1117
+ return emitters.get(collection, () => {
1118
+ const blocks = memberSpecs.map((m) => m.emitter(collection, `${at} › ${where}`));
1119
+ return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
1120
+ const id = $.let(East.value(variant("section", { series: cfg.key, path: prefix }), PlanRowIdType), PlanRowIdType);
1121
+ const under = $.let(East.value(some(id), IdOptType), IdOptType);
1122
+ const out = $.let(East.value([header(id, parent)], PlanRowsCollectionType), PlanRowsCollectionType);
1123
+ for (const block of blocks)
1124
+ $(out.append(block(coll, prefix, under)));
1125
+ return out;
1126
+ });
1127
+ });
1128
+ },
1129
+ // The header is a FIXED block — no entry produces it, so every window
1130
+ // serves it alike and the canvas draws it once — and each member lays
1131
+ // out as its own blocks under it, so the series in a section page on
1132
+ // their own (#823).
1133
+ blocks(collection, at) {
1134
+ return blockLists.get(collection, () => {
1135
+ const members = memberSpecs.map((m) => m.blocks(collection, `${at} › ${where}`));
1136
+ return East.function([collection, PathType, IdOptType], PlanBlocksType, ($, coll, prefix, parent) => {
1137
+ const id = $.let(East.value(variant("section", { series: cfg.key, path: prefix }), PlanRowIdType), PlanRowIdType);
1138
+ const under = $.let(East.value(some(id), IdOptType), IdOptType);
1139
+ const head = $.let(East.value({ fixed: true, parent, rows: [header(id, parent)] }, PlanBlockType), PlanBlockType);
1140
+ const out = $.let(East.value([head], PlanBlocksType), PlanBlocksType);
1141
+ for (const member of members)
1142
+ $(out.append(member(coll, prefix, under)));
1143
+ return out;
1144
+ });
1145
+ });
1146
+ },
1147
+ // A section adds no path segment, so an entry's rows under it are its
1148
+ // members' own: the first member that takes the gesture writes it
1149
+ // (#880). The header is written by none.
1150
+ writer(collection, at) {
1151
+ return writerLists.get(collection, () => {
1152
+ const writes = memberSpecs
1153
+ .map((m) => m.writer(collection, `${at} › ${where}`))
1154
+ .filter((w) => w !== undefined);
1155
+ if (writes.length === 0)
1156
+ return undefined;
1157
+ const { entry, key: kt } = shapeOf(collection, at);
1158
+ return East.function([entry, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(entry), ($, value, key, path, depth, series, gesture, carried) => {
1159
+ const result = $.let(none, OptionType(entry));
1160
+ for (const w of writes) {
1161
+ const write = $.const(w);
1162
+ $.if(result.hasTag("none"), ($2) => {
1163
+ $2.assign(result, write(value, key, path, depth, series, gesture, carried));
1164
+ });
1165
+ }
1166
+ return result;
1167
+ });
1168
+ });
1169
+ },
1170
+ };
1171
+ }
1172
+ /** The build spec of a `rows` series — hand-built rows, named and placed. */
1173
+ function rowsSpec(identity, rows) {
1174
+ const emitters = new ByType();
1175
+ const blockLists = new ByType();
1176
+ const emitter = (collection) => emitters.get(collection, () => East.function([collection, PathType, IdOptType], PlanRowsCollectionType, (_$, _coll, prefix, parent) => REBASE_ROWS(rows, identity.key, prefix, parent)));
1177
+ return {
1178
+ key: identity.key,
1179
+ title: identity.title,
1180
+ arm: "rows",
1181
+ nested: [],
1182
+ nests: false,
1183
+ emitter,
1184
+ // A FIXED block: no entry produces hand-built rows, so every window
1185
+ // serves them alike and the canvas draws them once (#823).
1186
+ blocks(collection) {
1187
+ return blockLists.get(collection, () => oneBlock(collection, emitter(collection), true));
1188
+ },
1189
+ // No entry produces them, so no gesture is written into one (#880).
1190
+ writer() {
1191
+ return undefined;
1192
+ },
1193
+ };
1194
+ }
1195
+ // ============================================================================
1196
+ // Keys — unique across the whole series tree
1197
+ // ============================================================================
1198
+ /**
1199
+ * Refuse two series sharing a key anywhere in a series tree — a row's id is
1200
+ * its series key and its path, so a repeated key would give two rows one id.
1201
+ *
1202
+ * @param specs - The series, in order
1203
+ * @param where - Who is asking, for the message
1204
+ * @throws {Error} Naming both series and where each sits
1205
+ */
1206
+ function assertUniqueKeys(specs, where) {
1207
+ const seen = new Map();
1208
+ const walk = (list, trail) => {
1209
+ list.forEach((spec, i) => {
1210
+ const site = `${trail}[${i}] ${nameOf(spec)}`;
1211
+ const prior = seen.get(spec.key);
1212
+ if (prior !== undefined) {
1213
+ throw new Error(`${where}: two series share the key "${spec.key}" — ${prior} and ${site}. A row's id is its ` +
1214
+ "series key and its path, so every series key must be unique across the whole series tree.");
1215
+ }
1216
+ seen.set(spec.key, site);
1217
+ walk(spec.nested, `${site} ›`);
1218
+ });
1219
+ };
1220
+ walk(specs, "series");
1221
+ }
1222
+ // ============================================================================
1223
+ // Series values — the East value, its spec beside it
1224
+ // ============================================================================
1225
+ /** Each spec's top-level writes, by collection type. */
1226
+ const topWrites = new WeakMap();
1227
+ /**
1228
+ * A series' write at the top of a canvas over `collection` (#880) — what the
1229
+ * series value carries as `write`: a gesture on a row of one entry, written
1230
+ * into a DETACHED copy of the entry (a nested entry in place in its
1231
+ * collection); `none` when the row is not the entry's, or no series in the
1232
+ * tree takes the gesture on it. A move to another row carries its item
1233
+ * between its two writes in `carried` (#825).
1234
+ *
1235
+ * @param spec - The series' build spec
1236
+ * @param collection - The source's collection type
1237
+ * @param where - Who is asking, for messages
1238
+ * @returns `(entry, key, row, gesture, carried) → the entry written`
1239
+ */
1240
+ function topWrite(spec, collection, where) {
1241
+ let cache = topWrites.get(spec);
1242
+ if (cache === undefined) {
1243
+ cache = new ByType();
1244
+ topWrites.set(spec, cache);
1245
+ }
1246
+ return cache.get(collection, () => {
1247
+ const { entry: rowType, key: kt } = shapeOf(collection, where);
1248
+ const writer = spec.writer(collection, where);
1249
+ const seg = segmentOf(kt);
1250
+ return East.function([rowType, kt, PlanRowIdType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
1251
+ const result = $.let(none, OptionType(rowType));
1252
+ if (writer === undefined)
1253
+ return result;
1254
+ const write = $.const(writer);
1255
+ $.match(row, {
1256
+ entry: ($2, id) => {
1257
+ // A row's path starts with its entry's key.
1258
+ const mine = $2.let(id.path.size().greater(0n).and(() => id.path.get(0n).equal(seg(key))), BooleanType);
1259
+ $2.if(mine, ($3) => {
1260
+ const detached = $3.let(East.Blob.encodeBeast(entry, "v2").decodeBeast(rowType, "v2"), rowType);
1261
+ $3.assign(result, write(detached, key, id.path, 1n, id.series, gesture, carried));
1262
+ });
1263
+ },
1264
+ });
1265
+ return result;
1266
+ });
1267
+ });
1268
+ }
1269
+ /** Pin a series value against the instantiated series type, recording its spec. */
1270
+ function seriesValue(rowType, keyType, spec, identity) {
1271
+ assertUniqueKeys([spec], `Plan.series.${spec.arm} "${spec.key}"`);
1272
+ const kt = keyType ?? StringType;
1273
+ const source = DictType(kt, rowType);
1274
+ const where = `Plan.series.${spec.arm} "${spec.key}"`;
1275
+ const blocks = spec.blocks(source, where);
1276
+ const derive = East.function([source], PlanBlocksType, ($, data) => {
1277
+ const top = $.let(East.value([], PathType), PathType);
1278
+ return blocks(data, top, East.value(none, IdOptType));
1279
+ });
1280
+ const value = East.value(variant(spec.arm, {
1281
+ key: identity.key,
1282
+ title: identity.title,
1283
+ subtitle: identity.subtitle !== undefined ? some(identity.subtitle) : none,
1284
+ icon: identity.icon !== undefined ? some(resolveIcon(identity.icon)) : none,
1285
+ derive,
1286
+ write: topWrite(spec, source, where),
1287
+ }), PlanSeriesType(rowType, kt));
1288
+ SPECS.set(value, spec);
1289
+ return value;
1290
+ }
1291
+ /** Apply one series value to a source (the exhaustive-arm call) — its blocks. */
1292
+ export function applySeriesValue(s, data) {
1293
+ const d = data;
140
1294
  return s.match({
141
- span: (_$, v) => v.derive(rows),
142
- buckets: (_$, v) => v.derive(rows),
143
- chart: (_$, v) => v.derive(rows),
144
- heat: (_$, v) => v.derive(rows),
145
- table: (_$, v) => v.derive(rows),
146
- cards: (_$, v) => v.derive(rows),
147
- events: (_$, v) => v.derive(rows),
148
- group: (_$, v) => v.derive(rows),
149
- rows: (_$, v) => v.rows,
1295
+ span: (_$, v) => v.derive(d),
1296
+ buckets: (_$, v) => v.derive(d),
1297
+ chart: (_$, v) => v.derive(d),
1298
+ heat: (_$, v) => v.derive(d),
1299
+ table: (_$, v) => v.derive(d),
1300
+ cards: (_$, v) => v.derive(d),
1301
+ events: (_$, v) => v.derive(d),
1302
+ group: (_$, v) => v.derive(d),
1303
+ section: (_$, v) => v.derive(d),
1304
+ views: (_$, v) => v.derive(d),
1305
+ rows: (_$, v) => v.derive(d),
150
1306
  });
151
1307
  }
152
1308
  /**
153
- * Apply the `series` input to the data expression — the inline arm's
154
- * application (Table's `rows_mapped`, per series). A TS array applies each
155
- * value in declared order; an East expression folds at evaluation, so
156
- * `$.const`-bound and computed series lists work identically.
1309
+ * Check a TS series list before it is applied — its keys unique across the
1310
+ * whole tree. An East expression list is checked at render (a repeated id is
1311
+ * a row diagnostic), since its series are not known until it runs.
1312
+ *
1313
+ * @param series - The `series` input
1314
+ * @param where - Who is asking, for the message
1315
+ */
1316
+ export function checkSeries(series, where) {
1317
+ if (!Array.isArray(series))
1318
+ return;
1319
+ const specs = series.map((s) => specOf(s)).filter((s) => s !== undefined);
1320
+ assertUniqueKeys(specs, where);
1321
+ }
1322
+ /**
1323
+ * Apply the `series` input to the source — the canvas's BLOCKS (#823): every
1324
+ * series' blocks, in declared order.
157
1325
  *
158
1326
  * @remarks
159
- * Series are unioned, not concatenated: the canvas is one keyed collection,
160
- * and two series that emit the same key resolve last-wins instead of both
161
- * rows surviving to be walked twice.
1327
+ * A TS array is built for the source's own collection type (so a source keyed
1328
+ * by any type works with series whose accessors ignore the key); an East
1329
+ * expression maps at evaluation, so a `$.const`-bound, picked or stored list
1330
+ * works identically, its series built for their declared key type.
1331
+ *
1332
+ * The blocks stay apart so a paged canvas can page each data series on its
1333
+ * own: applied to one window of a source, this is every block's share of that
1334
+ * window, from one read of it.
1335
+ *
1336
+ * @param series - The `series` input
1337
+ * @param data - The source collection (a whole `Dict`, or one paged window of it)
1338
+ * @returns The blocks
162
1339
  */
163
1340
  export function applySeries(series, data) {
1341
+ const collection = Expr.type(data);
164
1342
  if (Array.isArray(series)) {
165
- return series.reduce((acc, s) => acc.union(applySeriesValue(s, data), LAST_WINS), emptyRows());
1343
+ const lists = series.map((s) => {
1344
+ const spec = specOf(s);
1345
+ return spec !== undefined
1346
+ ? spec.blocks(collection, `Plan.series.${spec.arm} "${spec.key}"`)(data, East.value([], PathType), East.value(none, IdOptType))
1347
+ : applySeriesValue(s, data);
1348
+ });
1349
+ return lists.reduce((acc, list) => acc.concat(list), East.value([], PlanBlocksType));
166
1350
  }
167
- const list = series;
168
- return list.reduce(($, acc, s) => {
169
- $(acc.unionInPlace(applySeriesValue(s, data), LAST_WINS));
170
- return acc;
171
- }, emptyRows());
1351
+ return series.map((_$, s) => applySeriesValue(s, data)).flatMap((_$, list) => list);
1352
+ }
1353
+ /** One series value's `write` (the exhaustive-arm read). */
1354
+ function seriesValueWrite(s) {
1355
+ return s.match({
1356
+ span: (_$, v) => v.write,
1357
+ buckets: (_$, v) => v.write,
1358
+ chart: (_$, v) => v.write,
1359
+ heat: (_$, v) => v.write,
1360
+ table: (_$, v) => v.write,
1361
+ cards: (_$, v) => v.write,
1362
+ events: (_$, v) => v.write,
1363
+ group: (_$, v) => v.write,
1364
+ section: (_$, v) => v.write,
1365
+ views: (_$, v) => v.write,
1366
+ rows: (_$, v) => v.write,
1367
+ });
1368
+ }
1369
+ /**
1370
+ * The canvas's write (#880) — a gesture on a row of one entry, written into
1371
+ * the entry by whichever series in the list made the row: `(entry, key, row,
1372
+ * gesture, carried) → the entry written`, `none` when none takes it. A move
1373
+ * to another row carries its item between its two writes in `carried`
1374
+ * (#825).
1375
+ *
1376
+ * @remarks
1377
+ * The `applySeries` rule: a TS array is built for the source's own collection
1378
+ * type, and an East list (a `$.const` list, a pick's) writes through each
1379
+ * series value's own `write`, built for its declared key type.
1380
+ *
1381
+ * @param series - The `series` input
1382
+ * @param collection - The source's collection type (`Dict<K, R>`)
1383
+ * @returns The write
1384
+ */
1385
+ export function seriesWriteFn(series, collection) {
1386
+ const { entry: rowType, key: kt } = shapeOf(collection, "Plan");
1387
+ const inputs = [rowType, kt, PlanRowIdType, PlanGestureType, PlanCarriedType];
1388
+ if (Array.isArray(series)) {
1389
+ const built = series.map((s) => {
1390
+ const spec = specOf(s);
1391
+ return spec !== undefined ? topWrite(spec, collection, `Plan.series.${spec.arm} "${spec.key}"`) : undefined;
1392
+ });
1393
+ return East.function([...inputs], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
1394
+ const result = $.let(none, OptionType(rowType));
1395
+ series.forEach((s, i) => {
1396
+ const own = built[i];
1397
+ const write = $.const(own !== undefined ? own : seriesValueWrite(s));
1398
+ $.if(result.hasTag("none"), ($2) => { $2.assign(result, write(entry, key, row, gesture, carried)); });
1399
+ });
1400
+ return result;
1401
+ });
1402
+ }
1403
+ return East.function([...inputs], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
1404
+ const result = $.let(none, OptionType(rowType));
1405
+ $.for(series, ($2, s) => {
1406
+ $2.if(result.hasTag("none"), ($3) => {
1407
+ const write = $3.let(seriesValueWrite(s));
1408
+ $3.assign(result, write(entry, key, row, gesture, carried));
1409
+ });
1410
+ });
1411
+ return result;
1412
+ });
172
1413
  }
173
1414
  // ============================================================================
174
1415
  // Builders — Plan.series.*
175
1416
  // ============================================================================
176
1417
  /**
177
- * A data-driven span series — one span row per matched data row, grouped
178
- * to arbitrary depth with rollup parents (the span `.of` surface + `match`).
179
- *
180
- * @typeParam R - The data row type
181
- * @typeParam K - The axis kind the runs imply (inferred from the `runs` accessor; `never` when erased)
182
- * @param rowType - The data row type (every builder takes it first — the `Slice.config` shape)
183
- * @param config - The accessors + grouping ({@link PlanSpanSeriesConfig})
184
- * @returns A series value (`variant("span", { derive })`), branded with its kind
1418
+ * A span series — one state-run row per entry; a row with children shows its
1419
+ * own runs plus bands over its subtree's runs (`rollup`) — each band sums its
1420
+ * runs' quantities unit by unit (#824).
1421
+ *
1422
+ * @typeParam R - The entry type
1423
+ * @typeParam K - The axis kind the runs imply (inferred from `runs`; `never` when erased)
1424
+ * @typeParam KT - The entries' key type (`keyType`; `String` by default)
1425
+ * @param rowType - The entry type (every builder takes it first — the `Slice.config` shape)
1426
+ * @param config - The accessors and nesting ({@link PlanSpanSeriesConfig})
1427
+ * @returns A series value, branded with its kind
185
1428
  */
186
1429
  export function createSeriesSpan(rowType, config) {
187
1430
  const cfg = config;
188
- const derive = seriesScaffold(rowType, cfg.match, cfg.groupBy, () => groupParentFn(spanParentKind(cfg)), spanRowOf(cfg), cfg.keyPrefix);
189
- return seriesValue(rowType, "span", { derive }, cfg);
1431
+ const rollup = cfg.children !== undefined ? resolveTag(cfg.rollup ?? "union", PlanRollupType) : undefined;
1432
+ const spec = dataSpec(rowType, cfg, {
1433
+ arm: "span",
1434
+ kind: (value, key, hasChildren) => spanKind({
1435
+ runs: cfg.runs(value, key),
1436
+ ...(cfg.decisions !== undefined ? { decisions: cfg.decisions(value, key) } : {}),
1437
+ ...(cfg.ports !== undefined ? { ports: cfg.ports(value, key) } : {}),
1438
+ }, rollup === undefined
1439
+ ? East.value(none, OptionType(PlanRollupType))
1440
+ // A row rolls up only when it HAS child rows — a leaf's bands
1441
+ // would repeat its own runs.
1442
+ : hasChildren.ifElse(() => East.value(some(rollup), OptionType(PlanRollupType)), () => East.value(none, OptionType(PlanRollupType)))),
1443
+ edit: cfg.edit,
1444
+ });
1445
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
190
1446
  }
191
1447
  /**
192
- * A data-driven heat series — one heat row per matched data row, grouped
193
- * with per-bucket-aggregated parents (the heat `.of` surface + `match`).
194
- *
195
- * @typeParam R - The data row type
196
- * @typeParam K - The axis kind the cells imply (inferred from the `cells` accessor; `never` when erased)
197
- * @param rowType - The data row type
198
- * @param config - The accessors + grouping ({@link PlanHeatSeriesConfig})
199
- * @returns A series value (`variant("heat", { derive })`), branded with its kind
1448
+ * A heat series — one per-bucket cell row per entry; a row with children and
1449
+ * no cells of its own shows its children's per-bucket `aggregate`.
1450
+ *
1451
+ * @typeParam R - The entry type
1452
+ * @typeParam K - The axis kind the cells imply (inferred from `cells`; `never` when erased)
1453
+ * @typeParam KT - The entries' key type
1454
+ * @param rowType - The entry type
1455
+ * @param config - The accessors and nesting ({@link PlanHeatSeriesConfig})
1456
+ * @returns A series value, branded with its kind
200
1457
  */
201
1458
  export function createSeriesHeat(rowType, config) {
202
1459
  const cfg = config;
203
- const mode = cfg.aggregate ?? "mean";
204
- const derive = seriesScaffold(rowType, cfg.match, cfg.groupBy, () => groupParentFn(heatParentKind(cfg), some(resolveTag(mode, PlanAggregateType).getTag())), heatRowOf(cfg), cfg.keyPrefix);
205
- return seriesValue(rowType, "heat", { derive }, cfg);
1460
+ const aggregate = cfg.children !== undefined
1461
+ ? East.value(some(resolveTag(cfg.aggregate ?? "mean", PlanAggregateType)), OptionType(PlanAggregateType))
1462
+ : East.value(none, OptionType(PlanAggregateType));
1463
+ const spec = dataSpec(rowType, cfg, {
1464
+ arm: "heat",
1465
+ kind: (value, key) => heatKind({
1466
+ cells: cfg.cells(value, key),
1467
+ ...(cfg.scale !== undefined ? { scale: cfg.scale } : {}),
1468
+ }, aggregate),
1469
+ });
1470
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
206
1471
  }
207
1472
  /**
208
- * A data-driven table series — one table row per matched data row, grouped
209
- * with subtotal parents (the table `.of` surface + `match`).
210
- *
211
- * @typeParam R - The data row type
212
- * @typeParam K - The axis kind the cells imply (inferred from the `cells` / `series` accessors; `never` when erased)
213
- * @param rowType - The data row type
214
- * @param config - The accessors + grouping ({@link PlanTableSeriesOfConfig})
215
- * @returns A series value (`variant("table", { derive })`), branded with its kind
1473
+ * A table series — one bucketed-numeral row per entry; a row with children and
1474
+ * no values of its own shows per-position subtotals (`aggregate`, `format`).
1475
+ *
1476
+ * @typeParam R - The entry type
1477
+ * @typeParam K - The axis kind the cells imply (inferred from `cells` / `series`; `never` when erased)
1478
+ * @typeParam KT - The entries' key type
1479
+ * @param rowType - The entry type
1480
+ * @param config - The accessors and nesting ({@link PlanTableSeriesOfConfig})
1481
+ * @returns A series value, branded with its kind
216
1482
  */
217
1483
  export function createSeriesTable(rowType, config) {
218
1484
  const cfg = config;
219
- const mode = cfg.aggregate ?? "sum";
220
- const derive = seriesScaffold(rowType, cfg.match, cfg.groupBy, () => groupParentFn(tableParentKind(cfg), some(resolveTag(mode, TableAggregateType).getTag())), tableRowOf(cfg), cfg.keyPrefix);
221
- return seriesValue(rowType, "table", { derive }, cfg);
1485
+ if (cfg.cells !== undefined && cfg.series !== undefined) {
1486
+ throw new Error(`Plan.series.table "${cfg.key}": pass \`cells\` (one plain value series) OR \`series\` — not both`);
1487
+ }
1488
+ const aggregate = cfg.children !== undefined
1489
+ ? East.value(some(resolveTag(cfg.aggregate ?? "sum", TableAggregateType)), OptionType(TableAggregateType))
1490
+ : East.value(none, OptionType(TableAggregateType));
1491
+ const spec = dataSpec(rowType, cfg, {
1492
+ arm: "table",
1493
+ kind: (value, key) => tableKind({
1494
+ ...(cfg.cells !== undefined ? { cells: cfg.cells(value, key) } : {}),
1495
+ ...(cfg.fold !== undefined ? { fold: cfg.fold } : {}),
1496
+ ...(cfg.series !== undefined ? { series: cfg.series(value, key) } : {}),
1497
+ ...(cfg.split !== undefined ? { split: cfg.split } : {}),
1498
+ ...(cfg.format !== undefined ? { format: cfg.format } : {}),
1499
+ ...(cfg.emphasis !== undefined ? { emphasis: cfg.emphasis } : {}),
1500
+ }, aggregate),
1501
+ });
1502
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
222
1503
  }
223
1504
  /**
224
- * A data-driven bucket series — one bucket row (the Planner surface) per
225
- * matched data row.
1505
+ * A bucket series — one bucket row (the Planner surface) per entry.
226
1506
  *
227
- * @typeParam R - The data row type
228
- * @typeParam K - The axis kind the tiles imply (inferred from the `events` / `markers` accessors; `never` when erased)
229
- * @param rowType - The data row type
1507
+ * @typeParam R - The entry type
1508
+ * @typeParam K - The axis kind the tiles imply (inferred from `events` / `markers`; `never` when erased)
1509
+ * @typeParam KT - The entries' key type
1510
+ * @param rowType - The entry type
230
1511
  * @param config - The accessors ({@link PlanBucketsSeriesConfig})
231
- * @returns A series value (`variant("buckets", { derive })`), branded with its kind
1512
+ * @returns A series value, branded with its kind
232
1513
  */
233
1514
  export function createSeriesBuckets(rowType, config) {
234
1515
  const cfg = config;
235
- const derive = seriesScaffold(rowType, cfg.match, undefined, undefined, (_$, r, k) => applyRowOverrides(createBuckets({
236
- key: rowKey(cfg.keyPrefix, k, cfg.keySuffix),
237
- label: cfg.label(r, k),
238
- ...(cfg.id !== undefined ? { id: cfg.id } : {}),
239
- ...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
240
- ...(cfg.lanes !== undefined ? { lanes: cfg.lanes(r, k) } : {}),
241
- events: cfg.events(r, k),
242
- ...(cfg.markers !== undefined ? { markers: cfg.markers(r, k) } : {}),
243
- }), envelopeOverrides(cfg, r, k)), cfg.keyPrefix);
244
- return seriesValue(rowType, "buckets", { derive }, cfg);
1516
+ const spec = dataSpec(rowType, cfg, {
1517
+ arm: "buckets",
1518
+ kind: (value, key) => bucketsKind({
1519
+ ...(cfg.lanes !== undefined ? { lanes: cfg.lanes(value, key) } : {}),
1520
+ events: cfg.events(value, key),
1521
+ ...(cfg.markers !== undefined ? { markers: cfg.markers(value, key) } : {}),
1522
+ }),
1523
+ edit: cfg.edit,
1524
+ });
1525
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
245
1526
  }
246
1527
  /**
247
- * A data-driven cards series — one cards row (Roster chips) per matched
248
- * data row.
1528
+ * A cards series — one cards row (Roster chips) per entry.
249
1529
  *
250
- * @typeParam R - The data row type
251
- * @typeParam K - The axis kind the chips imply (inferred from the `chips` accessor; `never` when erased)
252
- * @param rowType - The data row type
1530
+ * @typeParam R - The entry type
1531
+ * @typeParam K - The axis kind the chips imply (inferred from `chips`; `never` when erased)
1532
+ * @typeParam KT - The entries' key type
1533
+ * @param rowType - The entry type
253
1534
  * @param config - The accessors ({@link PlanCardsSeriesConfig})
254
- * @returns A series value (`variant("cards", { derive })`), branded with its kind
1535
+ * @returns A series value, branded with its kind
255
1536
  */
256
1537
  export function createSeriesCards(rowType, config) {
257
1538
  const cfg = config;
258
- const derive = seriesScaffold(rowType, cfg.match, undefined, undefined, (_$, r, k) => applyRowOverrides(createCards({
259
- key: rowKey(cfg.keyPrefix, k, cfg.keySuffix),
260
- label: cfg.label(r, k),
261
- ...(cfg.id !== undefined ? { id: cfg.id } : {}),
262
- ...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
263
- chips: cfg.chips(r, k),
264
- }), envelopeOverrides(cfg, r, k)), cfg.keyPrefix);
265
- return seriesValue(rowType, "cards", { derive }, cfg);
1539
+ const spec = dataSpec(rowType, cfg, {
1540
+ arm: "cards",
1541
+ kind: (value, key) => cardsKind(cfg.chips(value, key)),
1542
+ edit: cfg.edit,
1543
+ });
1544
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
266
1545
  }
267
1546
  /**
268
- * A data-driven event series — one instant-mark row per matched data row.
1547
+ * An events series — one instant-mark row per entry.
269
1548
  *
270
- * @typeParam R - The data row type
271
- * @typeParam K - The axis kind the marks imply (inferred from the `marks` accessor; `never` when erased)
272
- * @param rowType - The data row type
1549
+ * @typeParam R - The entry type
1550
+ * @typeParam K - The axis kind the marks imply (inferred from `marks`; `never` when erased)
1551
+ * @typeParam KT - The entries' key type
1552
+ * @param rowType - The entry type
273
1553
  * @param config - The accessors ({@link PlanEventsSeriesConfig})
274
- * @returns A series value (`variant("events", { derive })`), branded with its kind
1554
+ * @returns A series value, branded with its kind
275
1555
  */
276
1556
  export function createSeriesEvents(rowType, config) {
277
1557
  const cfg = config;
278
- const derive = seriesScaffold(rowType, cfg.match, undefined, undefined, (_$, r, k) => applyRowOverrides(createEvents({
279
- key: rowKey(cfg.keyPrefix, k, cfg.keySuffix),
280
- label: cfg.label(r, k),
281
- ...(cfg.id !== undefined ? { id: cfg.id } : {}),
282
- ...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
283
- marks: cfg.marks(r, k),
284
- }), envelopeOverrides(cfg, r, k)), cfg.keyPrefix);
285
- return seriesValue(rowType, "events", { derive }, cfg);
1558
+ const spec = dataSpec(rowType, cfg, {
1559
+ arm: "events",
1560
+ kind: (value, key) => eventsKind(cfg.marks(value, key)),
1561
+ edit: cfg.edit,
1562
+ });
1563
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
286
1564
  }
287
1565
  /**
288
- * A data-driven chart series — one chart row per matched data row, layers
289
- * built from the row's own data. Kind-ERASED: a Chart layer's TS face does
290
- * not expose its x type, so a chart series constrains no axis at compile
291
- * time — the render-time diagnostic holds it to the axis like any row.
292
- *
293
- * @typeParam R - The data row type
294
- * @param rowType - The data row type
295
- * @param config - The accessors + shared axes ({@link PlanChartSeriesConfig})
296
- * @returns A series value (`variant("chart", { derive })`)
1566
+ * A chart series — one chart row per entry, layers built from the entry's own
1567
+ * data. Kind-ERASED: a Chart layer's TS face does not expose its x type, so a
1568
+ * chart series constrains no axis at compile time — the render-time
1569
+ * diagnostic holds it to the axis like any row.
1570
+ *
1571
+ * @typeParam R - The entry type
1572
+ * @typeParam KT - The entries' key type
1573
+ * @param rowType - The entry type
1574
+ * @param config - The accessors and shared axes ({@link PlanChartSeriesConfig})
1575
+ * @returns A series value
297
1576
  */
298
1577
  export function createSeriesChart(rowType, config) {
299
1578
  const cfg = config;
300
- const derive = seriesScaffold(rowType, cfg.match, undefined, undefined, (_$, r, k) => applyRowOverrides(createChart({
301
- key: rowKey(cfg.keyPrefix, k, cfg.keySuffix),
302
- label: cfg.label(r, k),
303
- ...(cfg.id !== undefined ? { id: cfg.id } : {}),
304
- ...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
305
- layers: cfg.layers(r, k),
306
- ...(cfg.left !== undefined ? { left: cfg.left } : {}),
307
- ...(cfg.right !== undefined ? { right: cfg.right } : {}),
308
- ...(cfg.height !== undefined ? { height: cfg.height } : {}),
309
- ...(cfg.expandedHeight !== undefined ? { expandedHeight: cfg.expandedHeight } : {}),
310
- ...(cfg.expandable !== undefined ? { expandable: cfg.expandable } : {}),
1579
+ const spec = dataSpec(rowType, cfg, {
1580
+ arm: "chart",
1581
+ kind: (value, key) => chartKind({
1582
+ layers: cfg.layers(value, key),
1583
+ ...(cfg.left !== undefined ? { left: cfg.left } : {}),
1584
+ ...(cfg.right !== undefined ? { right: cfg.right } : {}),
1585
+ ...(cfg.height !== undefined ? { height: cfg.height } : {}),
1586
+ ...(cfg.expandedHeight !== undefined ? { expandedHeight: cfg.expandedHeight } : {}),
1587
+ ...(cfg.expandable !== undefined ? { expandable: cfg.expandable } : {}),
1588
+ }),
1589
+ ...(cfg.pinned !== undefined
1590
+ ? { pinned: (value, key) => East.value(cfg.pinned(value, key), BooleanType) }
1591
+ : {}),
311
1592
  ...(cfg.swatches !== undefined ? { swatches: cfg.swatches } : {}),
312
- }), {
313
- ...envelopeOverrides(cfg, r, k),
314
- ...(cfg.pinned !== undefined ? { pinned: some(cfg.pinned(r, k)) } : {}),
315
- }), cfg.keyPrefix);
316
- return seriesValue(rowType, "chart", { derive }, cfg);
1593
+ });
1594
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
317
1595
  }
318
1596
  /**
319
- * A group strip AROUND child series — the chrome is literal, the members
320
- * are the children applied to the same data and re-parented beneath it.
1597
+ * A group series — one group strip PER ENTRY, its members the entry's
1598
+ * children (#822). Collapsed, a strip rests as its summary heat strip —
1599
+ * explicit `summary` cells, or the renderer-derived `summaryAggregate` — and
1600
+ * its member count is derived.
321
1601
  *
322
- * @typeParam R - The data row type
323
- * @param rowType - The data row type
324
1602
  * @remarks
325
- * A group is the unit a person picks — "add Machines" — so the STATIC form's
326
- * series identity IS the strip's: `key` and `label` become the series' `key`
327
- * and `title`, and `meta` its subtitle. Nothing extra to write, and the two can
328
- * never disagree about what the section is called.
329
- *
330
- * The DISCOVERED (`by`) form cannot borrow that, because one series makes MANY
331
- * strips — it declares its own `key` / `title` ({@link PlanGroupSeriesByConfig}).
332
- *
333
- * @typeParam K - The union of the children's axis kinds (inferred; `never` when all are erased)
334
- * @param chrome - The group's literal chrome ({@link PlanGroupSeriesChrome})
335
- * @param children - The member series (applied in declared order; their rows sit in KEY order)
336
- * @returns A series value (`variant("group", { derive })`), branded with its children's kinds
1603
+ * Hierarchy comes from the data's nesting: to group a flat source, reshape it
1604
+ * first — inline, one East `groupToDicts` in the canvas function (the entries
1605
+ * are then `Dict<String, Row>` and a group's label is its key); a paged source
1606
+ * is grouped in the dataflow. The `by` form and the static
1607
+ * `group(R, chrome, children)` form are gone — see `Plan.series.section` for a
1608
+ * fixed titled block.
1609
+ *
1610
+ * @typeParam R - The entry type
1611
+ * @typeParam K - The axis kind the summary cells imply
1612
+ * @typeParam KT - The entries' key type
1613
+ * @param rowType - The entry type
1614
+ * @param config - The strip's accessors and its members ({@link PlanGroupSeriesConfig})
1615
+ * @returns A series value, branded with its kind
1616
+ * @throws {Error} When called with the removed `by` or static forms
337
1617
  */
338
- export function createSeriesGroup(rowType, chromeOrBy, children) {
339
- const applyChildren = (subset) => children.reduce((acc, c) => acc.union(applySeriesValue(c, subset), LAST_WINS), emptyRows());
340
- if ("by" in chromeOrBy && typeof chromeOrBy.by === "function") {
341
- // DISCOVERED strips — one group per distinct key, mirroring the
342
- // grouped data form: the strip DECLARES its summary aggregation;
343
- // children apply to each group's rows. The member count is NOT baked
344
- // in here — a strip re-synthesized per paged window would carry that
345
- // window's count — it is derived renderer-side like every other
346
- // aggregate (#568).
347
- const cfg = chromeOrBy;
348
- const kind = East.value(variant("group", {
349
- summary: none,
350
- summaryAggregate: cfg.summaryAggregate !== undefined
351
- ? some(resolveTag(cfg.summaryAggregate, PlanAggregateType))
352
- : none,
353
- collapsed: cfg.collapsed !== undefined ? some(cfg.collapsed) : none,
354
- }), PlanRowKindType);
355
- const parentFn = groupParentFn(kind);
356
- const prefix = cfg.keyPrefix ?? "";
357
- const rt = rowType;
358
- const sourceType = DictType(StringType, rt);
359
- const make = East.function([sourceType], PlanRowsCollectionType, ($, rows) => {
360
- const matched = $.let(matchedRows(rows, rt, cfg.match), sourceType);
361
- const levels = [{ by: reifyLevelKey(rt, cfg.by), parentFn }];
362
- return groupRows(matched, levels, (subset, _prefix) => applyChildren(subset), prefix);
363
- });
364
- return seriesValue(rowType, "group", { derive: make }, cfg);
1618
+ export function createSeriesGroup(rowType, config) {
1619
+ // The removed forms, named — a plain JS caller would otherwise meet an
1620
+ // unhelpful "label is not a function".
1621
+ if (arguments.length > 2 || "by" in config) {
1622
+ throw new Error("Plan.series.group: the `by` and static `group(R, chrome, children)` forms are removed (#822) — a group " +
1623
+ "strip is now one per ENTRY with its members nested in the entry (`children`); reshape a flat source " +
1624
+ "with `groupToDicts` first, and use `Plan.series.section(R, { key, title }, [series…])` for a fixed block");
365
1625
  }
366
- // STATIC chrome — one literal strip around the children.
367
- const chrome = chromeOrBy;
368
- const make = East.function([DictType(StringType, rowType)], PlanRowsCollectionType, (_$, rows) => {
369
- return createGroup({ ...chrome, rows: applyChildren(rows) });
370
- });
371
- // The strip's own identity, reused verbatim — see the remarks above.
372
- return seriesValue(rowType, "group", { derive: make }, {
373
- key: chrome.key,
374
- title: chrome.label,
375
- ...(chrome.meta !== undefined ? { subtitle: chrome.meta } : {}),
1626
+ const cfg = config;
1627
+ const where = `Plan.series.group "${cfg.key}"`;
1628
+ if (cfg.children === undefined) {
1629
+ throw new Error(`${where}: a group strip needs its members — declare \`children\``);
1630
+ }
1631
+ const summary = cfg.summary;
1632
+ // The strip's ONE summary (#824): the per-entry cells, the declared
1633
+ // aggregate, or a plain band — both at once is refused here.
1634
+ const declared = summary === undefined ? groupSummary(undefined, cfg.summaryAggregate, where) : undefined;
1635
+ if (summary !== undefined && cfg.summaryAggregate !== undefined) {
1636
+ throw new Error(`${where}: give \`summary\` (explicit strip cells) OR \`summaryAggregate\` (cells derived from the ` +
1637
+ "members) — a collapsed strip shows one or the other");
1638
+ }
1639
+ const spec = dataSpec(rowType, cfg, {
1640
+ arm: "group",
1641
+ kind: (value, key) => groupKind(declared ?? East.value(variant("cells", East.value(summary(value, key), PlanHeatCellsType)), PlanGroupSummaryType)),
376
1642
  });
1643
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
1644
+ }
1645
+ /**
1646
+ * A section — a fixed titled block over series: one header row, the member
1647
+ * series' rows beneath it (#822). A section adds no path segment: its header's
1648
+ * id is `section { series, path }` at its parent's path, and its members' rows
1649
+ * keep their own.
1650
+ *
1651
+ * @typeParam R - The entry type (the members' — they share the section's entries)
1652
+ * @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
1653
+ * @param rowType - The entry type
1654
+ * @param config - The header ({@link PlanSectionSeriesConfig})
1655
+ * @param members - The member series, in order
1656
+ * @returns A series value, branded with its members' kinds
1657
+ */
1658
+ export function createSeriesSection(rowType, config, members) {
1659
+ const cfg = config;
1660
+ const spec = sectionSpec(cfg, members);
1661
+ return seriesValue(rowType, undefined, spec, cfg);
377
1662
  }
378
1663
  /**
379
- * Literal one-off chrome rows riding beside the data-driven series (a pinned
380
- * KPI chart, a hand-built section) — the finished rows carried directly, no
381
- * data dependence. They are placed by their own KEYS like every other row,
382
- * not by where this series sits in the list.
1664
+ * A views series — each entry gets one row per member series, adjacent and in
1665
+ * declared order; the entry's children follow its view rows, nested under the
1666
+ * first of them (#822). How one entity is shown several ways: a machine's jobs,
1667
+ * utilisation and tonnes as three rows side by side.
383
1668
  *
384
1669
  * @remarks
385
- * Unlike a group, a `rows` series has no single row to borrow identity from —
386
- * it may carry several — so it declares one. That is what lets a hand-built
387
- * section be switched off like anything else, instead of being the one thing on
388
- * the canvas a user cannot turn off.
1670
+ * A member series' own `match` decides whether its row shows for an entry; a
1671
+ * member may not declare `children` (the views own the entry's children). A
1672
+ * view row's id is its member series' key and the entry's path, and a seek on
1673
+ * the entry lands on its first view row.
389
1674
  *
390
- * @typeParam R - The data row type (pins the series against its siblings)
391
- * @typeParam K - The union of the rows' axis kinds (inferred from the kind-factory results; `never` when erased)
392
- * @param rowType - The data row type
393
- * @param identity - How the library lists this section ({@link PlanSeriesIdentity})
394
- * @param rows - The literal rows (kind-factory results)
395
- * @returns A series value (`variant("rows", { rows })`), branded with the rows' kinds
1675
+ * @typeParam R - The entry type
1676
+ * @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
1677
+ * @typeParam KT - The entries' key type
1678
+ * @param rowType - The entry type
1679
+ * @param config - Membership, collapse and children ({@link PlanViewsSeriesConfig})
1680
+ * @param members - The member series — one row each per entry, in order
1681
+ * @returns A series value, branded with its members' kinds
396
1682
  */
397
- export function createSeriesRows(rowType, identity, rows) {
398
- return seriesValue(rowType, "rows", { rows: normalizeRows(rows) }, identity);
1683
+ export function createSeriesViews(rowType, config, members) {
1684
+ const cfg = config;
1685
+ const spec = viewsSpec(rowType, cfg, members);
1686
+ return seriesValue(rowType, cfg.keyType, spec, cfg);
399
1687
  }
400
1688
  /**
401
- * Recover the row type of a data expression (the Table `Expr.type` idiom).
1689
+ * Hand-built rows riding beside the data-driven series (a pinned KPI chart, a
1690
+ * shutdown written out once) — the kind factories' streams, named by this
1691
+ * series and placed as its block.
402
1692
  *
403
1693
  * @remarks
404
- * The paged arm no longer lives here: a Plan's rows channel is the shared
405
- * {@link RowSourceType} contract, and `contracts/source.ts`'s `buildRowSource`
406
- * wraps each window with this module's `applySeries` — the one place the
407
- * domain row type is erased to the canvas row type (#567).
1694
+ * A hand-built row's id is this series' key and the factory `key`s that lead to
1695
+ * it (`Plan.ref("works", "shutdown", "elec")`); keys must be unique among a
1696
+ * row's siblings, or two rows share an id and the renderer draws the second as
1697
+ * a diagnostic.
1698
+ *
1699
+ * @typeParam R - The entry type (pins the series against its siblings)
1700
+ * @typeParam K - The union of the rows' axis kinds (inferred from the kind-factory results; `never` when erased)
1701
+ * @param rowType - The entry type
1702
+ * @param identity - How the library lists this block ({@link PlanSeriesIdentity})
1703
+ * @param rows - The hand-built rows (kind-factory results)
1704
+ * @returns A series value, branded with the rows' kinds
408
1705
  */
409
- export function seriesRowType(data) {
410
- return Expr.type(data).value;
1706
+ export function createSeriesRows(rowType, identity, rows) {
1707
+ const spec = rowsSpec(identity, normalizeRows(rows));
1708
+ return seriesValue(rowType, undefined, spec, identity);
411
1709
  }
412
1710
  //# sourceMappingURL=series.js.map