@elaraai/east-ui 1.0.85 → 1.0.87

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 (561) hide show
  1. package/dist/src/buttons/button/index.d.ts +4 -0
  2. package/dist/src/buttons/button/index.d.ts.map +1 -1
  3. package/dist/src/buttons/button/index.js +8 -1
  4. package/dist/src/buttons/button/index.js.map +1 -1
  5. package/dist/src/buttons/button/types.d.ts +6 -5
  6. package/dist/src/buttons/button/types.d.ts.map +1 -1
  7. package/dist/src/buttons/button/types.js.map +1 -1
  8. package/dist/src/buttons/icon-button/index.d.ts +5 -5
  9. package/dist/src/buttons/icon-button/index.d.ts.map +1 -1
  10. package/dist/src/buttons/icon-button/index.js +8 -2
  11. package/dist/src/buttons/icon-button/index.js.map +1 -1
  12. package/dist/src/buttons/icon-button/types.d.ts +8 -12
  13. package/dist/src/buttons/icon-button/types.d.ts.map +1 -1
  14. package/dist/src/buttons/icon-button/types.js +2 -2
  15. package/dist/src/buttons/icon-button/types.js.map +1 -1
  16. package/dist/src/buttons/toggle/index.d.ts +4 -0
  17. package/dist/src/buttons/toggle/index.d.ts.map +1 -1
  18. package/dist/src/buttons/toggle/index.js +6 -1
  19. package/dist/src/buttons/toggle/index.js.map +1 -1
  20. package/dist/src/buttons/toggle/types.d.ts +3 -2
  21. package/dist/src/buttons/toggle/types.d.ts.map +1 -1
  22. package/dist/src/buttons/toggle/types.js.map +1 -1
  23. package/dist/src/charts/chart/index.d.ts.map +1 -1
  24. package/dist/src/charts/chart/index.js +3 -3
  25. package/dist/src/charts/chart/index.js.map +1 -1
  26. package/dist/src/charts/spec/index.d.ts.map +1 -1
  27. package/dist/src/charts/spec/index.js +5 -2
  28. package/dist/src/charts/spec/index.js.map +1 -1
  29. package/dist/src/collections/deck/index.d.ts.map +1 -1
  30. package/dist/src/collections/deck/index.js +2 -2
  31. package/dist/src/collections/deck/index.js.map +1 -1
  32. package/dist/src/collections/index.d.ts +0 -6
  33. package/dist/src/collections/index.d.ts.map +1 -1
  34. package/dist/src/collections/index.js +0 -6
  35. package/dist/src/collections/index.js.map +1 -1
  36. package/dist/src/collections/library/index.d.ts +1 -1
  37. package/dist/src/collections/library/index.d.ts.map +1 -1
  38. package/dist/src/collections/library/index.js +2 -2
  39. package/dist/src/collections/library/index.js.map +1 -1
  40. package/dist/src/collections/schematic/index.d.ts.map +1 -1
  41. package/dist/src/collections/schematic/index.js +2 -2
  42. package/dist/src/collections/schematic/index.js.map +1 -1
  43. package/dist/src/collections/table/index.d.ts +1 -1
  44. package/dist/src/collections/table/index.d.ts.map +1 -1
  45. package/dist/src/collections/table/index.js +2 -2
  46. package/dist/src/collections/table/index.js.map +1 -1
  47. package/dist/src/collections/tree-view/index.d.ts +11 -32
  48. package/dist/src/collections/tree-view/index.d.ts.map +1 -1
  49. package/dist/src/collections/tree-view/index.js +15 -10
  50. package/dist/src/collections/tree-view/index.js.map +1 -1
  51. package/dist/src/component.d.ts +673 -9344
  52. package/dist/src/component.d.ts.map +1 -1
  53. package/dist/src/component.js +5 -259
  54. package/dist/src/component.js.map +1 -1
  55. package/dist/src/contracts/approval.d.ts +6 -8
  56. package/dist/src/contracts/approval.d.ts.map +1 -1
  57. package/dist/src/contracts/approval.js +6 -8
  58. package/dist/src/contracts/approval.js.map +1 -1
  59. package/dist/src/contracts/drag.d.ts +1 -1
  60. package/dist/src/contracts/drag.d.ts.map +1 -1
  61. package/dist/src/contracts/drag.js +4 -5
  62. package/dist/src/contracts/drag.js.map +1 -1
  63. package/dist/src/contracts/editing.d.ts +19 -19
  64. package/dist/src/contracts/editing.js +14 -14
  65. package/dist/src/contracts/fields.d.ts +471 -0
  66. package/dist/src/contracts/fields.d.ts.map +1 -0
  67. package/dist/src/contracts/fields.js +492 -0
  68. package/dist/src/contracts/fields.js.map +1 -0
  69. package/dist/src/contracts/index.d.ts +1 -0
  70. package/dist/src/contracts/index.d.ts.map +1 -1
  71. package/dist/src/contracts/index.js +1 -0
  72. package/dist/src/contracts/index.js.map +1 -1
  73. package/dist/src/contracts/indexed-source.d.ts +118 -0
  74. package/dist/src/contracts/indexed-source.d.ts.map +1 -0
  75. package/dist/src/contracts/indexed-source.js +89 -0
  76. package/dist/src/contracts/indexed-source.js.map +1 -0
  77. package/dist/src/contracts/pick.d.ts +0 -16
  78. package/dist/src/contracts/pick.d.ts.map +1 -1
  79. package/dist/src/contracts/review.d.ts +4 -6
  80. package/dist/src/contracts/review.d.ts.map +1 -1
  81. package/dist/src/contracts/review.js +4 -6
  82. package/dist/src/contracts/review.js.map +1 -1
  83. package/dist/src/contracts/source.d.ts +2 -2
  84. package/dist/src/contracts/source.d.ts.map +1 -1
  85. package/dist/src/contracts/source.js.map +1 -1
  86. package/dist/src/contracts/states.d.ts +1 -3
  87. package/dist/src/contracts/states.d.ts.map +1 -1
  88. package/dist/src/contracts/states.js +1 -3
  89. package/dist/src/contracts/states.js.map +1 -1
  90. package/dist/src/display/editable-chip/index.d.ts +2 -0
  91. package/dist/src/display/editable-chip/index.d.ts.map +1 -1
  92. package/dist/src/display/editable-chip/index.js +4 -1
  93. package/dist/src/display/editable-chip/index.js.map +1 -1
  94. package/dist/src/display/icon/index.d.ts +14 -31
  95. package/dist/src/display/icon/index.d.ts.map +1 -1
  96. package/dist/src/display/icon/index.js +15 -19
  97. package/dist/src/display/icon/index.js.map +1 -1
  98. package/dist/src/display/icon/solid.d.ts +24 -0
  99. package/dist/src/display/icon/solid.d.ts.map +1 -0
  100. package/dist/src/display/icon/solid.js +39 -0
  101. package/dist/src/display/icon/solid.js.map +1 -0
  102. package/dist/src/display/icon/types.d.ts +14 -38
  103. package/dist/src/display/icon/types.d.ts.map +1 -1
  104. package/dist/src/display/icon/types.js +6 -15
  105. package/dist/src/display/icon/types.js.map +1 -1
  106. package/dist/src/display/metric-chip/index.d.ts +2 -0
  107. package/dist/src/display/metric-chip/index.d.ts.map +1 -1
  108. package/dist/src/display/metric-chip/index.js +4 -1
  109. package/dist/src/display/metric-chip/index.js.map +1 -1
  110. package/dist/src/display/stat/index.d.ts +2 -0
  111. package/dist/src/display/stat/index.d.ts.map +1 -1
  112. package/dist/src/display/stat/index.js +4 -1
  113. package/dist/src/display/stat/index.js.map +1 -1
  114. package/dist/src/display/stat/types.d.ts +3 -5
  115. package/dist/src/display/stat/types.d.ts.map +1 -1
  116. package/dist/src/display/stat/types.js +5 -2
  117. package/dist/src/display/stat/types.js.map +1 -1
  118. package/dist/src/feedback/banner/index.d.ts +5 -3
  119. package/dist/src/feedback/banner/index.d.ts.map +1 -1
  120. package/dist/src/feedback/banner/index.js +4 -1
  121. package/dist/src/feedback/banner/index.js.map +1 -1
  122. package/dist/src/feedback/empty-state/index.d.ts +16 -19
  123. package/dist/src/feedback/empty-state/index.d.ts.map +1 -1
  124. package/dist/src/feedback/empty-state/index.js +17 -10
  125. package/dist/src/feedback/empty-state/index.js.map +1 -1
  126. package/dist/src/feedback/status/index.d.ts +5 -3
  127. package/dist/src/feedback/status/index.d.ts.map +1 -1
  128. package/dist/src/feedback/status/index.js +4 -1
  129. package/dist/src/feedback/status/index.js.map +1 -1
  130. package/dist/src/index.d.ts +5 -6
  131. package/dist/src/index.d.ts.map +1 -1
  132. package/dist/src/index.js +12 -11
  133. package/dist/src/index.js.map +1 -1
  134. package/dist/src/internal.d.ts +23 -16
  135. package/dist/src/internal.d.ts.map +1 -1
  136. package/dist/src/internal.js +29 -13
  137. package/dist/src/internal.js.map +1 -1
  138. package/dist/src/navigation/index.d.ts +0 -2
  139. package/dist/src/navigation/index.d.ts.map +1 -1
  140. package/dist/src/navigation/index.js +0 -2
  141. package/dist/src/navigation/index.js.map +1 -1
  142. package/dist/src/navigation/nav-list/index.d.ts +2 -0
  143. package/dist/src/navigation/nav-list/index.d.ts.map +1 -1
  144. package/dist/src/navigation/nav-list/index.js +4 -1
  145. package/dist/src/navigation/nav-list/index.js.map +1 -1
  146. package/dist/src/navigation/nav-list/types.d.ts +3 -14
  147. package/dist/src/navigation/nav-list/types.d.ts.map +1 -1
  148. package/dist/src/navigation/nav-list/types.js.map +1 -1
  149. package/dist/src/overlays/command-palette/index.d.ts +2 -0
  150. package/dist/src/overlays/command-palette/index.d.ts.map +1 -1
  151. package/dist/src/overlays/command-palette/index.js +5 -0
  152. package/dist/src/overlays/command-palette/index.js.map +1 -1
  153. package/dist/src/overlays/command-palette/types.d.ts +0 -8
  154. package/dist/src/overlays/command-palette/types.d.ts.map +1 -1
  155. package/dist/src/pick/index.d.ts +1 -17
  156. package/dist/src/pick/index.d.ts.map +1 -1
  157. package/dist/src/pick/index.js +1 -1
  158. package/dist/src/platform/slice/index.d.ts +1324 -2039
  159. package/dist/src/platform/slice/index.d.ts.map +1 -1
  160. package/dist/src/platform/slice/index.js +55 -74
  161. package/dist/src/platform/slice/index.js.map +1 -1
  162. package/dist/src/platform/slice/local.d.ts +743 -0
  163. package/dist/src/platform/slice/local.d.ts.map +1 -0
  164. package/dist/src/platform/slice/local.js +133 -0
  165. package/dist/src/platform/slice/local.js.map +1 -0
  166. package/dist/src/platform/slice/record-chrome.d.ts +211 -0
  167. package/dist/src/platform/slice/record-chrome.d.ts.map +1 -0
  168. package/dist/src/platform/slice/record-chrome.js +52 -0
  169. package/dist/src/platform/slice/record-chrome.js.map +1 -0
  170. package/dist/src/platform/slice/record-interval.d.ts +24 -0
  171. package/dist/src/platform/slice/record-interval.d.ts.map +1 -0
  172. package/dist/src/platform/slice/record-interval.js +61 -0
  173. package/dist/src/platform/slice/record-interval.js.map +1 -0
  174. package/dist/src/platform/slice/record-plan.d.ts +54 -0
  175. package/dist/src/platform/slice/record-plan.d.ts.map +1 -0
  176. package/dist/src/platform/slice/record-plan.js +139 -0
  177. package/dist/src/platform/slice/record-plan.js.map +1 -0
  178. package/dist/src/platform/slice/record-types.d.ts +2739 -0
  179. package/dist/src/platform/slice/record-types.d.ts.map +1 -0
  180. package/dist/src/platform/slice/record-types.js +209 -0
  181. package/dist/src/platform/slice/record-types.js.map +1 -0
  182. package/dist/src/platform/slice/record.d.ts +109 -0
  183. package/dist/src/platform/slice/record.d.ts.map +1 -0
  184. package/dist/src/platform/slice/record.js +210 -0
  185. package/dist/src/platform/slice/record.js.map +1 -0
  186. package/dist/src/reactive/match.d.ts +4 -6
  187. package/dist/src/reactive/match.d.ts.map +1 -1
  188. package/dist/src/reactive/match.js +4 -6
  189. package/dist/src/reactive/match.js.map +1 -1
  190. package/dist/src/runtime/collections/deck.d.ts.map +1 -1
  191. package/dist/src/runtime/collections/index.d.ts +0 -6
  192. package/dist/src/runtime/collections/index.d.ts.map +1 -1
  193. package/dist/src/runtime/collections/index.js +0 -6
  194. package/dist/src/runtime/collections/index.js.map +1 -1
  195. package/dist/src/runtime/display/icon.d.ts +6 -5
  196. package/dist/src/runtime/display/icon.d.ts.map +1 -1
  197. package/dist/src/runtime/display/icon.js +6 -5
  198. package/dist/src/runtime/display/icon.js.map +1 -1
  199. package/dist/src/runtime/feedback/empty-state.d.ts +5 -5
  200. package/dist/src/runtime/feedback/empty-state.js +5 -5
  201. package/dist/src/runtime/layout/dock.d.ts +2 -1
  202. package/dist/src/runtime/layout/dock.d.ts.map +1 -1
  203. package/dist/src/runtime/layout/dock.js +2 -1
  204. package/dist/src/runtime/layout/dock.js.map +1 -1
  205. package/dist/src/runtime/navigation/index.d.ts +0 -3
  206. package/dist/src/runtime/navigation/index.d.ts.map +1 -1
  207. package/dist/src/runtime/navigation/index.js +0 -3
  208. package/dist/src/runtime/navigation/index.js.map +1 -1
  209. package/dist/src/runtime/reactive/match.d.ts +2 -3
  210. package/dist/src/runtime/reactive/match.d.ts.map +1 -1
  211. package/dist/src/runtime/reactive/match.js +2 -3
  212. package/dist/src/runtime/reactive/match.js.map +1 -1
  213. package/dist/src/runtime/record-slice-source.d.ts +78 -0
  214. package/dist/src/runtime/record-slice-source.d.ts.map +1 -0
  215. package/dist/src/runtime/record-slice-source.js +201 -0
  216. package/dist/src/runtime/record-slice-source.js.map +1 -0
  217. package/dist/src/runtime/record-slice.d.ts +110 -0
  218. package/dist/src/runtime/record-slice.d.ts.map +1 -0
  219. package/dist/src/runtime/record-slice.js +374 -0
  220. package/dist/src/runtime/record-slice.js.map +1 -0
  221. package/dist/src/runtime/slice.d.ts +11 -13
  222. package/dist/src/runtime/slice.d.ts.map +1 -1
  223. package/dist/src/runtime/slice.js +41 -93
  224. package/dist/src/runtime/slice.js.map +1 -1
  225. package/dist/src/shared/plot-gutter.d.ts +1 -1
  226. package/dist/src/shared/plot-gutter.js +1 -1
  227. package/dist/src/shared/resolve-tag.d.ts +23 -0
  228. package/dist/src/shared/resolve-tag.d.ts.map +1 -0
  229. package/dist/src/shared/resolve-tag.js +26 -0
  230. package/dist/src/shared/resolve-tag.js.map +1 -0
  231. package/dist/src/shared/tree.d.ts +1 -1
  232. package/dist/src/shared/tree.js +1 -1
  233. package/dist/src/slice/breakdown/index.d.ts +1 -1
  234. package/dist/src/slice/breakdown/index.d.ts.map +1 -1
  235. package/dist/src/slice/breakdown/index.js +3 -3
  236. package/dist/src/slice/breakdown/index.js.map +1 -1
  237. package/dist/src/slice/cohort/index.d.ts +4 -5
  238. package/dist/src/slice/cohort/index.d.ts.map +1 -1
  239. package/dist/src/slice/cohort/index.js +6 -7
  240. package/dist/src/slice/cohort/index.js.map +1 -1
  241. package/dist/src/slice/filter/index.d.ts +1 -1
  242. package/dist/src/slice/filter/index.d.ts.map +1 -1
  243. package/dist/src/slice/filter/index.js +3 -3
  244. package/dist/src/slice/filter/index.js.map +1 -1
  245. package/dist/src/slice/index.d.ts +20 -2230
  246. package/dist/src/slice/index.d.ts.map +1 -1
  247. package/dist/src/slice/legend/index.d.ts +2 -2
  248. package/dist/src/slice/legend/index.d.ts.map +1 -1
  249. package/dist/src/slice/legend/index.js +4 -4
  250. package/dist/src/slice/legend/index.js.map +1 -1
  251. package/dist/src/slice/rail/index.d.ts +7 -4
  252. package/dist/src/slice/rail/index.d.ts.map +1 -1
  253. package/dist/src/slice/rail/index.js +10 -5
  254. package/dist/src/slice/rail/index.js.map +1 -1
  255. package/dist/src/slice/range/index.d.ts +1 -1
  256. package/dist/src/slice/range/index.d.ts.map +1 -1
  257. package/dist/src/slice/range/index.js +3 -3
  258. package/dist/src/slice/range/index.js.map +1 -1
  259. package/dist/src/slice/record/index.d.ts +171 -0
  260. package/dist/src/slice/record/index.d.ts.map +1 -0
  261. package/dist/src/slice/record/index.js +10 -0
  262. package/dist/src/slice/record/index.js.map +1 -0
  263. package/dist/src/slice/search/index.d.ts +6 -6
  264. package/dist/src/slice/search/index.d.ts.map +1 -1
  265. package/dist/src/slice/search/index.js +7 -7
  266. package/dist/src/slice/search/index.js.map +1 -1
  267. package/dist/src/slice/summary/index.d.ts +1 -1
  268. package/dist/src/slice/summary/index.d.ts.map +1 -1
  269. package/dist/src/slice/summary/index.js +3 -3
  270. package/dist/src/slice/summary/index.js.map +1 -1
  271. package/dist/src/typography/list/index.d.ts +2 -8
  272. package/dist/src/typography/list/index.d.ts.map +1 -1
  273. package/dist/src/typography/list/index.js +4 -1
  274. package/dist/src/typography/list/index.js.map +1 -1
  275. package/dist/src/typography/list/types.d.ts +0 -8
  276. package/dist/src/typography/list/types.d.ts.map +1 -1
  277. package/dist/test/collections/deck.examples.d.ts.map +1 -1
  278. package/dist/test/collections/deck.examples.js +6 -7
  279. package/dist/test/collections/deck.examples.js.map +1 -1
  280. package/dist/test/collections/index.d.ts +0 -1
  281. package/dist/test/collections/index.d.ts.map +1 -1
  282. package/dist/test/collections/index.js +0 -1
  283. package/dist/test/collections/index.js.map +1 -1
  284. package/dist/test/collections/library.examples.d.ts.map +1 -1
  285. package/dist/test/collections/library.examples.js +6 -7
  286. package/dist/test/collections/library.examples.js.map +1 -1
  287. package/dist/test/collections/schematic.examples.d.ts.map +1 -1
  288. package/dist/test/collections/schematic.examples.js +11 -19
  289. package/dist/test/collections/schematic.examples.js.map +1 -1
  290. package/dist/test/collections/tree-view.examples.js +2 -2
  291. package/dist/test/contracts/editing.examples.d.ts +1 -0
  292. package/dist/test/contracts/editing.examples.d.ts.map +1 -1
  293. package/dist/test/contracts/editing.examples.js +37 -6
  294. package/dist/test/contracts/editing.examples.js.map +1 -1
  295. package/dist/test/contracts/fields.examples.d.ts +7 -0
  296. package/dist/test/contracts/fields.examples.d.ts.map +1 -0
  297. package/dist/test/contracts/fields.examples.js +42 -0
  298. package/dist/test/contracts/fields.examples.js.map +1 -0
  299. package/dist/test/display/avatar.examples.d.ts.map +1 -1
  300. package/dist/test/display/avatar.examples.js +3 -3
  301. package/dist/test/display/avatar.examples.js.map +1 -1
  302. package/dist/test/display/icon.examples.d.ts.map +1 -1
  303. package/dist/test/display/icon.examples.js +12 -11
  304. package/dist/test/display/icon.examples.js.map +1 -1
  305. package/dist/test/feedback/empty-state.examples.js +9 -9
  306. package/dist/test/feedback/empty-state.examples.js.map +1 -1
  307. package/dist/test/layout/dock.examples.d.ts +0 -8
  308. package/dist/test/layout/dock.examples.d.ts.map +1 -1
  309. package/dist/test/layout/dock.examples.js +2 -42
  310. package/dist/test/layout/dock.examples.js.map +1 -1
  311. package/dist/test/layout/snap-grid.examples.d.ts.map +1 -1
  312. package/dist/test/layout/snap-grid.examples.js +18 -28
  313. package/dist/test/layout/snap-grid.examples.js.map +1 -1
  314. package/dist/test/navigation/index.d.ts +0 -2
  315. package/dist/test/navigation/index.d.ts.map +1 -1
  316. package/dist/test/navigation/index.js +0 -2
  317. package/dist/test/navigation/index.js.map +1 -1
  318. package/dist/test/navigation/nav-list.examples.js.map +1 -1
  319. package/dist/test/platform/index.d.ts +1 -0
  320. package/dist/test/platform/index.d.ts.map +1 -1
  321. package/dist/test/platform/index.js +1 -0
  322. package/dist/test/platform/index.js.map +1 -1
  323. package/dist/test/platform/slice-local.examples.d.ts +2 -0
  324. package/dist/test/platform/slice-local.examples.d.ts.map +1 -0
  325. package/dist/test/platform/slice-local.examples.js +31 -0
  326. package/dist/test/platform/slice-local.examples.js.map +1 -0
  327. package/dist/test/platform/slice.examples.js +61 -61
  328. package/dist/test/platform/slice.examples.js.map +1 -1
  329. package/dist/test/slice/slice.examples.d.ts +0 -1
  330. package/dist/test/slice/slice.examples.d.ts.map +1 -1
  331. package/dist/test/slice/slice.examples.js +67 -181
  332. package/dist/test/slice/slice.examples.js.map +1 -1
  333. package/package.json +4 -4
  334. package/dist/src/collections/board/index.d.ts +0 -855
  335. package/dist/src/collections/board/index.d.ts.map +0 -1
  336. package/dist/src/collections/board/index.js +0 -416
  337. package/dist/src/collections/board/index.js.map +0 -1
  338. package/dist/src/collections/board/types.d.ts +0 -111
  339. package/dist/src/collections/board/types.d.ts.map +0 -1
  340. package/dist/src/collections/board/types.js +0 -89
  341. package/dist/src/collections/board/types.js.map +0 -1
  342. package/dist/src/collections/calendar/index.d.ts +0 -446
  343. package/dist/src/collections/calendar/index.d.ts.map +0 -1
  344. package/dist/src/collections/calendar/index.js +0 -287
  345. package/dist/src/collections/calendar/index.js.map +0 -1
  346. package/dist/src/collections/calendar/types.d.ts +0 -338
  347. package/dist/src/collections/calendar/types.d.ts.map +0 -1
  348. package/dist/src/collections/calendar/types.js +0 -206
  349. package/dist/src/collections/calendar/types.js.map +0 -1
  350. package/dist/src/collections/flowchart/index.d.ts +0 -430
  351. package/dist/src/collections/flowchart/index.d.ts.map +0 -1
  352. package/dist/src/collections/flowchart/index.js +0 -292
  353. package/dist/src/collections/flowchart/index.js.map +0 -1
  354. package/dist/src/collections/flowchart/types.d.ts +0 -333
  355. package/dist/src/collections/flowchart/types.d.ts.map +0 -1
  356. package/dist/src/collections/flowchart/types.js +0 -263
  357. package/dist/src/collections/flowchart/types.js.map +0 -1
  358. package/dist/src/collections/plan/assemble.d.ts +0 -2274
  359. package/dist/src/collections/plan/assemble.d.ts.map +0 -1
  360. package/dist/src/collections/plan/assemble.js +0 -210
  361. package/dist/src/collections/plan/assemble.js.map +0 -1
  362. package/dist/src/collections/plan/builders.d.ts +0 -765
  363. package/dist/src/collections/plan/builders.d.ts.map +0 -1
  364. package/dist/src/collections/plan/builders.js +0 -673
  365. package/dist/src/collections/plan/builders.js.map +0 -1
  366. package/dist/src/collections/plan/factories.d.ts +0 -512
  367. package/dist/src/collections/plan/factories.d.ts.map +0 -1
  368. package/dist/src/collections/plan/factories.js +0 -539
  369. package/dist/src/collections/plan/factories.js.map +0 -1
  370. package/dist/src/collections/plan/index.d.ts +0 -324
  371. package/dist/src/collections/plan/index.d.ts.map +0 -1
  372. package/dist/src/collections/plan/index.js +0 -193
  373. package/dist/src/collections/plan/index.js.map +0 -1
  374. package/dist/src/collections/plan/ir.d.ts +0 -2490
  375. package/dist/src/collections/plan/ir.d.ts.map +0 -1
  376. package/dist/src/collections/plan/ir.js +0 -107
  377. package/dist/src/collections/plan/ir.js.map +0 -1
  378. package/dist/src/collections/plan/pick.d.ts +0 -133
  379. package/dist/src/collections/plan/pick.d.ts.map +0 -1
  380. package/dist/src/collections/plan/pick.js +0 -208
  381. package/dist/src/collections/plan/pick.js.map +0 -1
  382. package/dist/src/collections/plan/root.d.ts +0 -297
  383. package/dist/src/collections/plan/root.d.ts.map +0 -1
  384. package/dist/src/collections/plan/root.js +0 -427
  385. package/dist/src/collections/plan/root.js.map +0 -1
  386. package/dist/src/collections/plan/series.d.ts +0 -13887
  387. package/dist/src/collections/plan/series.d.ts.map +0 -1
  388. package/dist/src/collections/plan/series.js +0 -1710
  389. package/dist/src/collections/plan/series.js.map +0 -1
  390. package/dist/src/collections/plan/types.d.ts +0 -12612
  391. package/dist/src/collections/plan/types.d.ts.map +0 -1
  392. package/dist/src/collections/plan/types.js +0 -1345
  393. package/dist/src/collections/plan/types.js.map +0 -1
  394. package/dist/src/collections/roster/index.d.ts +0 -787
  395. package/dist/src/collections/roster/index.d.ts.map +0 -1
  396. package/dist/src/collections/roster/index.js +0 -325
  397. package/dist/src/collections/roster/index.js.map +0 -1
  398. package/dist/src/collections/roster/types.d.ts +0 -97
  399. package/dist/src/collections/roster/types.d.ts.map +0 -1
  400. package/dist/src/collections/roster/types.js +0 -72
  401. package/dist/src/collections/roster/types.js.map +0 -1
  402. package/dist/src/collections/sheet/bridge.d.ts +0 -376
  403. package/dist/src/collections/sheet/bridge.d.ts.map +0 -1
  404. package/dist/src/collections/sheet/bridge.js +0 -773
  405. package/dist/src/collections/sheet/bridge.js.map +0 -1
  406. package/dist/src/collections/sheet/columns.d.ts +0 -410
  407. package/dist/src/collections/sheet/columns.d.ts.map +0 -1
  408. package/dist/src/collections/sheet/columns.js +0 -99
  409. package/dist/src/collections/sheet/columns.js.map +0 -1
  410. package/dist/src/collections/sheet/context-bridge.d.ts +0 -48
  411. package/dist/src/collections/sheet/context-bridge.d.ts.map +0 -1
  412. package/dist/src/collections/sheet/context-bridge.js +0 -216
  413. package/dist/src/collections/sheet/context-bridge.js.map +0 -1
  414. package/dist/src/collections/sheet/draft-bridge.d.ts +0 -37
  415. package/dist/src/collections/sheet/draft-bridge.d.ts.map +0 -1
  416. package/dist/src/collections/sheet/draft-bridge.js +0 -153
  417. package/dist/src/collections/sheet/draft-bridge.js.map +0 -1
  418. package/dist/src/collections/sheet/drafts.d.ts +0 -14
  419. package/dist/src/collections/sheet/drafts.d.ts.map +0 -1
  420. package/dist/src/collections/sheet/drafts.js +0 -14
  421. package/dist/src/collections/sheet/drafts.js.map +0 -1
  422. package/dist/src/collections/sheet/editing-bridge.d.ts +0 -36
  423. package/dist/src/collections/sheet/editing-bridge.d.ts.map +0 -1
  424. package/dist/src/collections/sheet/editing-bridge.js +0 -196
  425. package/dist/src/collections/sheet/editing-bridge.js.map +0 -1
  426. package/dist/src/collections/sheet/editing-types.d.ts +0 -268
  427. package/dist/src/collections/sheet/editing-types.d.ts.map +0 -1
  428. package/dist/src/collections/sheet/editing-types.js +0 -34
  429. package/dist/src/collections/sheet/editing-types.js.map +0 -1
  430. package/dist/src/collections/sheet/edits.d.ts +0 -37
  431. package/dist/src/collections/sheet/edits.d.ts.map +0 -1
  432. package/dist/src/collections/sheet/edits.js +0 -31
  433. package/dist/src/collections/sheet/edits.js.map +0 -1
  434. package/dist/src/collections/sheet/group.d.ts +0 -165
  435. package/dist/src/collections/sheet/group.d.ts.map +0 -1
  436. package/dist/src/collections/sheet/group.js +0 -52
  437. package/dist/src/collections/sheet/group.js.map +0 -1
  438. package/dist/src/collections/sheet/index.d.ts +0 -351
  439. package/dist/src/collections/sheet/index.d.ts.map +0 -1
  440. package/dist/src/collections/sheet/index.js +0 -208
  441. package/dist/src/collections/sheet/index.js.map +0 -1
  442. package/dist/src/collections/sheet/link.d.ts +0 -398
  443. package/dist/src/collections/sheet/link.d.ts.map +0 -1
  444. package/dist/src/collections/sheet/link.js +0 -228
  445. package/dist/src/collections/sheet/link.js.map +0 -1
  446. package/dist/src/collections/sheet/registers.d.ts +0 -278
  447. package/dist/src/collections/sheet/registers.d.ts.map +0 -1
  448. package/dist/src/collections/sheet/registers.js +0 -268
  449. package/dist/src/collections/sheet/registers.js.map +0 -1
  450. package/dist/src/collections/sheet/root.d.ts +0 -328
  451. package/dist/src/collections/sheet/root.d.ts.map +0 -1
  452. package/dist/src/collections/sheet/root.js +0 -432
  453. package/dist/src/collections/sheet/root.js.map +0 -1
  454. package/dist/src/collections/sheet/seed-bridge.d.ts +0 -14
  455. package/dist/src/collections/sheet/seed-bridge.d.ts.map +0 -1
  456. package/dist/src/collections/sheet/seed-bridge.js +0 -51
  457. package/dist/src/collections/sheet/seed-bridge.js.map +0 -1
  458. package/dist/src/collections/sheet/sub-rows.d.ts +0 -249
  459. package/dist/src/collections/sheet/sub-rows.d.ts.map +0 -1
  460. package/dist/src/collections/sheet/sub-rows.js +0 -222
  461. package/dist/src/collections/sheet/sub-rows.js.map +0 -1
  462. package/dist/src/collections/sheet/transactions.d.ts +0 -76
  463. package/dist/src/collections/sheet/transactions.d.ts.map +0 -1
  464. package/dist/src/collections/sheet/transactions.js +0 -40
  465. package/dist/src/collections/sheet/transactions.js.map +0 -1
  466. package/dist/src/collections/sheet/types.d.ts +0 -14992
  467. package/dist/src/collections/sheet/types.d.ts.map +0 -1
  468. package/dist/src/collections/sheet/types.js +0 -1061
  469. package/dist/src/collections/sheet/types.js.map +0 -1
  470. package/dist/src/navigation/app/index.d.ts +0 -150
  471. package/dist/src/navigation/app/index.d.ts.map +0 -1
  472. package/dist/src/navigation/app/index.js +0 -191
  473. package/dist/src/navigation/app/index.js.map +0 -1
  474. package/dist/src/navigation/pages/index.d.ts +0 -165
  475. package/dist/src/navigation/pages/index.d.ts.map +0 -1
  476. package/dist/src/navigation/pages/index.js +0 -152
  477. package/dist/src/navigation/pages/index.js.map +0 -1
  478. package/dist/src/navigation/pages/types.d.ts +0 -111
  479. package/dist/src/navigation/pages/types.d.ts.map +0 -1
  480. package/dist/src/navigation/pages/types.js +0 -101
  481. package/dist/src/navigation/pages/types.js.map +0 -1
  482. package/dist/src/runtime/collections/board.d.ts +0 -73
  483. package/dist/src/runtime/collections/board.d.ts.map +0 -1
  484. package/dist/src/runtime/collections/board.js +0 -68
  485. package/dist/src/runtime/collections/board.js.map +0 -1
  486. package/dist/src/runtime/collections/calendar.d.ts +0 -54
  487. package/dist/src/runtime/collections/calendar.d.ts.map +0 -1
  488. package/dist/src/runtime/collections/calendar.js +0 -54
  489. package/dist/src/runtime/collections/calendar.js.map +0 -1
  490. package/dist/src/runtime/collections/flowchart.d.ts +0 -58
  491. package/dist/src/runtime/collections/flowchart.d.ts.map +0 -1
  492. package/dist/src/runtime/collections/flowchart.js +0 -54
  493. package/dist/src/runtime/collections/flowchart.js.map +0 -1
  494. package/dist/src/runtime/collections/plan.d.ts +0 -181
  495. package/dist/src/runtime/collections/plan.d.ts.map +0 -1
  496. package/dist/src/runtime/collections/plan.js +0 -186
  497. package/dist/src/runtime/collections/plan.js.map +0 -1
  498. package/dist/src/runtime/collections/roster.d.ts +0 -60
  499. package/dist/src/runtime/collections/roster.d.ts.map +0 -1
  500. package/dist/src/runtime/collections/roster.js +0 -59
  501. package/dist/src/runtime/collections/roster.js.map +0 -1
  502. package/dist/src/runtime/collections/sheet.d.ts +0 -122
  503. package/dist/src/runtime/collections/sheet.d.ts.map +0 -1
  504. package/dist/src/runtime/collections/sheet.js +0 -73
  505. package/dist/src/runtime/collections/sheet.js.map +0 -1
  506. package/dist/src/runtime/navigation/app.d.ts +0 -52
  507. package/dist/src/runtime/navigation/app.d.ts.map +0 -1
  508. package/dist/src/runtime/navigation/app.js +0 -53
  509. package/dist/src/runtime/navigation/app.js.map +0 -1
  510. package/dist/src/runtime/navigation/pages.d.ts +0 -50
  511. package/dist/src/runtime/navigation/pages.d.ts.map +0 -1
  512. package/dist/src/runtime/navigation/pages.js +0 -52
  513. package/dist/src/runtime/navigation/pages.js.map +0 -1
  514. package/dist/src/runtime/navigation/route.d.ts +0 -46
  515. package/dist/src/runtime/navigation/route.d.ts.map +0 -1
  516. package/dist/src/runtime/navigation/route.js +0 -47
  517. package/dist/src/runtime/navigation/route.js.map +0 -1
  518. package/dist/test/collections/board.examples.d.ts +0 -18
  519. package/dist/test/collections/board.examples.d.ts.map +0 -1
  520. package/dist/test/collections/board.examples.js +0 -297
  521. package/dist/test/collections/board.examples.js.map +0 -1
  522. package/dist/test/collections/calendar.examples.d.ts +0 -5
  523. package/dist/test/collections/calendar.examples.d.ts.map +0 -1
  524. package/dist/test/collections/calendar.examples.js +0 -147
  525. package/dist/test/collections/calendar.examples.js.map +0 -1
  526. package/dist/test/collections/flowchart.examples.d.ts +0 -17
  527. package/dist/test/collections/flowchart.examples.d.ts.map +0 -1
  528. package/dist/test/collections/flowchart.examples.js +0 -243
  529. package/dist/test/collections/flowchart.examples.js.map +0 -1
  530. package/dist/test/collections/plan.examples.d.ts +0 -200
  531. package/dist/test/collections/plan.examples.d.ts.map +0 -1
  532. package/dist/test/collections/plan.examples.js +0 -3665
  533. package/dist/test/collections/plan.examples.js.map +0 -1
  534. package/dist/test/collections/roster.examples.d.ts +0 -26
  535. package/dist/test/collections/roster.examples.d.ts.map +0 -1
  536. package/dist/test/collections/roster.examples.js +0 -237
  537. package/dist/test/collections/roster.examples.js.map +0 -1
  538. package/dist/test/collections/sheet-link.examples.d.ts +0 -7
  539. package/dist/test/collections/sheet-link.examples.d.ts.map +0 -1
  540. package/dist/test/collections/sheet-link.examples.js +0 -41
  541. package/dist/test/collections/sheet-link.examples.js.map +0 -1
  542. package/dist/test/collections/sheet-transactions.examples.d.ts +0 -7
  543. package/dist/test/collections/sheet-transactions.examples.d.ts.map +0 -1
  544. package/dist/test/collections/sheet-transactions.examples.js +0 -58
  545. package/dist/test/collections/sheet-transactions.examples.js.map +0 -1
  546. package/dist/test/collections/sheet.examples.d.ts +0 -96
  547. package/dist/test/collections/sheet.examples.d.ts.map +0 -1
  548. package/dist/test/collections/sheet.examples.js +0 -1040
  549. package/dist/test/collections/sheet.examples.js.map +0 -1
  550. package/dist/test/navigation/app.examples.d.ts +0 -22
  551. package/dist/test/navigation/app.examples.d.ts.map +0 -1
  552. package/dist/test/navigation/app.examples.js +0 -136
  553. package/dist/test/navigation/app.examples.js.map +0 -1
  554. package/dist/test/navigation/pages.examples.d.ts +0 -13
  555. package/dist/test/navigation/pages.examples.d.ts.map +0 -1
  556. package/dist/test/navigation/pages.examples.js +0 -113
  557. package/dist/test/navigation/pages.examples.js.map +0 -1
  558. package/dist/test/navigation/route.examples.d.ts +0 -13
  559. package/dist/test/navigation/route.examples.d.ts.map +0 -1
  560. package/dist/test/navigation/route.examples.js +0 -69
  561. package/dist/test/navigation/route.examples.js.map +0 -1
@@ -1,3665 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@elaraai/east-ui/jsx-runtime";
2
- /**
3
- * Copyright (c) 2025 Elara AI Pty Ltd
4
- * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
5
- */
6
- /** @jsxImportSource @elaraai/east-ui */
7
- import { ArrayType, BooleanType, DateTimeType, DictType, East, FloatType, IntegerType, NullType, OptionType, RecursiveType, StringType, StructType, VariantType, example, none, some, variant, } from "@elaraai/east";
8
- import { ApprovalStateType, DragEventType, Editing, EventStateType, State, StatusValueType, Style, UIComponentType } from "@elaraai/east-ui";
9
- import { Badge, Box, Button, Chart, Configurator, Format, HStack, Library, Plan, Progress, Reactive, SegmentGroup, Select, Slice, Sparkline, Text, VStack, deriveApproval } from "@elaraai/east-ui";
10
- // The corpus — every canvas is DEFINED the one way (`Plan Data Interface.md`
11
- // §3.5): `data` (RAW domain rows — batches, tonnes, lifecycle states; row
12
- // series discriminated by a field; no factory-built values in the data) +
13
- // `series` (one `Plan.series.*` value per row series, `$.const`-bound and typed
14
- // by the `Plan.Types.Series(Row)` constructor — its accessors DERIVE the
15
- // canvas vocabulary from the raw fields client-side: labels, quantity
16
- // displays, element values via the `Plan.run`/`chip`/`event` expression
17
- // builders) + the root RESOLVER functions (`popover` / `hover` /
18
- // `expandRender`). The series list IS the layout — one block per series, top
19
- // to bottom — and hierarchy comes only from the data's own nesting (#822): a
20
- // series' `children` walk what an entry holds, and a flat source is grouped in
21
- // a data step first (`groupToDicts`). A row's id is its series and the path of
22
- // entry keys to it (`Plan.ref`). The kind factories are subtree vocabulary only
23
- // — the one-off chrome a `Plan.series.rows` entry places (`planLiteralRows`).
24
- // Fixtures are defined INLINE in each example's fn body (the automated docs
25
- // extract only the fn body) and DERIVED with East expressions — a `week(n)`
26
- // ISO-week function plus `East.Array.generate` — never hand-written per
27
- // element; only individually-meaningful mock records stay literal, and
28
- // lifecycle states are plain `EventStateType` variants (the shared contracts
29
- // vocabulary a plan dataset stores).
30
- // ============================================================================
31
- // planTargetState — the §1 flagship (every row kind, ONE source, series)
32
- // ============================================================================
33
- export const planTargetState = example({
34
- keywords: [
35
- "Plan", "canvas", "data", "series", "match", "axis", "window", "resolution", "now",
36
- "span", "run", "group", "section", "heat", "buckets", "cards", "chip", "events", "mark", "chart",
37
- "layers", "rollup", "bands", "review", "footer", "milestone",
38
- "decision", "exception", "pinned", "port", "hovercard", "popover",
39
- "slice", "brush", "horizon", "toolbar", "affordances", "expand",
40
- "expandRender", "resolver", "data-driven", "accessor", "raw", "target state",
41
- "layout", "row id", "Plan.ref", "links", "quantity", "Plan.quantity", "onElementClick",
42
- ],
43
- description: "Every row kind on one axis from a single raw ops source, with slice chrome, expand, review and a status footer",
44
- fn: East.function([], UIComponentType, (_$) => {
45
- // The horizon fixture — the despatch orders the slice narrows (the
46
- // canvas rows are the ops source; the slice is chrome over THESE):
47
- // when, which line, whether the order is at risk of running late,
48
- // and the tonnage booked so far (0 = nothing yet).
49
- const HorizonRow = StructType({ key: StringType, at: DateTimeType, line: StringType, risk: StringType, tonnes: FloatType });
50
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
51
- // The RAW job record — what an ops dataset stores: phase + optional
52
- // batch, the window, optional tonnage, the lifecycle state, an alert.
53
- const JobRow = StructType({
54
- key: StringType, phase: StringType, batch: OptionType(StringType),
55
- start: DateTimeType, end: DateTimeType,
56
- tonnes: OptionType(FloatType), state: EventStateType,
57
- alert: OptionType(StatusValueType),
58
- });
59
- const ShiftRow = StructType({
60
- key: StringType, from: DateTimeType, to: DateTimeType,
61
- hours: FloatType, state: EventStateType,
62
- });
63
- const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
64
- // ONE source — row series discriminated by the kind variant (the
65
- // natural ops-dataset shape; the same rows page from a dataset). The
66
- // arms carry RAW fields; presence (status / expand declaration) is
67
- // per-row Option DATA the accessors pass through.
68
- const OpsRow = StructType({
69
- kind: VariantType({
70
- kpi: StructType({ name: StringType, headline: StringType, pinned: BooleanType,
71
- points: ArrayType(MeasureRow) }),
72
- machine: StructType({ cap: FloatType,
73
- status: OptionType(StatusValueType),
74
- detail: OptionType(Plan.Types.Expand),
75
- jobs: ArrayType(JobRow),
76
- decisions: ArrayType(Plan.Types.DecisionMark),
77
- ports: ArrayType(Plan.Types.Port) }),
78
- load: StructType({ name: StringType, sub: StringType, cells: ArrayType(Plan.Types.HeatCell) }),
79
- dock: StructType({ name: StringType, allocations: ArrayType(AllocRow),
80
- markers: ArrayType(Plan.Types.CellMarker) }),
81
- crew: StructType({ name: StringType, hours: StringType, shifts: ArrayType(ShiftRow) }),
82
- stream: StructType({ name: StringType, marks: ArrayType(Plan.Types.EventMark) }),
83
- }),
84
- });
85
- const cfg = Slice.config(HorizonRow, {
86
- fields: {
87
- at: { label: "Despatched", format: { date: "MMM D" } },
88
- line: { label: "Line" },
89
- risk: { label: "Risk", hints: ["late", "on-time"] },
90
- tonnes: { label: "Tonnes" },
91
- },
92
- rangeFieldId: "at",
93
- searchFieldIds: ["line"],
94
- });
95
- return (_jsx(Reactive, { children: $ => {
96
- // Monday of ISO week n, 2026 (W1 Monday = 2025-12-29). The §1
97
- // window is W27–W38 (half-open at W39); now = W31.
98
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
99
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
100
- return w1.addWeeks(n.subtract(1n));
101
- }));
102
- const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
103
- const loadPcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
104
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
105
- const loadCells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
106
- at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i)),
107
- label: some(East.Float.printFixed(loadPcts.get(i), 0n)),
108
- })));
109
- // 36 despatch orders spread over W21–W47 — the horizon fixture.
110
- // Every third order is at risk of running late (the §1 `Late
111
- // risk` cohort counts 12); every seventh has no tonnage booked
112
- // yet (the `Empty` cohort).
113
- const horizon = $.let(East.Array.generate(36n, HorizonRow, (_$, i) => ({
114
- key: East.str `h${East.print(i.add(1n))}`,
115
- at: week(i.multiply(27n).divide(36n).add(21n)),
116
- line: i.remainder(2n).equal(0n).ifElse(() => "Line 1", () => "Line 2"),
117
- risk: i.remainder(3n).equal(0n).ifElse(() => "late", () => "on-time"),
118
- tonnes: i.remainder(7n).equal(6n).ifElse(() => 0.0, () => i.multiply(11n).remainder(40n).toFloat().add(20.0)),
119
- })));
120
- // Raw jobs → runs: ONE bound mapping function — the bar label and
121
- // the quantity derive CLIENT-SIDE from the raw phase/batch/tonnes
122
- // fields (the series-make application). A quantity is ONE value —
123
- // the number, its unit and how it prints (#824) — so the bar's
124
- // caption and a rollup's sum can never disagree. The run is written
125
- // as a RECORD, so its instants are spelled with `Plan.at.time` —
126
- // the `Plan.run` builder would wrap a DateTime field by itself.
127
- const tonnesFormat = $.const(Format.Number({ maximumFractionDigits: 0n }));
128
- const jobRuns = $.const(East.function([ArrayType(JobRow)], ArrayType(Plan.Types.Run), (_$, jobs) => jobs.map(($, j) => {
129
- const noQuantity = $.const(none, OptionType(Plan.Types.Quantity));
130
- const quantity = $.let(j.tonnes.match({
131
- some: (_$, t) => East.value(some(Plan.quantity(t, { unit: "t", format: tonnesFormat })), OptionType(Plan.Types.Quantity)),
132
- none: (_$) => noQuantity,
133
- }), OptionType(Plan.Types.Quantity));
134
- const label = $.let(j.batch.match({
135
- some: (_$, b) => East.str `${j.phase} · ${b}`,
136
- none: (_$) => j.phase,
137
- }), StringType);
138
- const run = $.let({
139
- key: j.key, start: Plan.at.time(j.start), end: Plan.at.time(j.end), label,
140
- quantity, state: j.state, status: j.alert,
141
- moved: none, icon: none,
142
- }, Plan.Types.Run);
143
- return run;
144
- })));
145
- // Raw shifts → chips: hours print as the chip label, an ADDED
146
- // proposal wearing the `+` prefix — display derives from the
147
- // lifecycle, down to the proposal's flavour (a `removed` shift is
148
- // a proposal too, and `+` would read as its opposite).
149
- const shiftChips = $.const(East.function([ArrayType(ShiftRow)], ArrayType(Plan.Types.Chip), (_$, shifts) => shifts.map(($, s) => {
150
- const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
151
- const label = $.let(s.state.match({
152
- proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
153
- }, _$ => East.str `${hrs}h`), StringType);
154
- return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
155
- })));
156
- // The ONE ops source — every series' rows in one KEYED collection,
157
- // RAW: no display strings the accessors can derive, no built
158
- // elements. A key is its entry's identity, never its place: a row's
159
- // id is the series that made it and this key
160
- // (`Plan.ref("machines", "L1-M03")`), which is what `links`,
161
- // `popover` and `onSelect` speak — the §1 layout is the series list.
162
- const ops = $.const(new Map([
163
- ["coverage", { kind: variant("kpi", { name: "Coverage", headline: "94.2%", pinned: true, points: coverage }) }],
164
- ["L1-M03", { kind: variant("machine", { cap: 120.0, status: some(variant("success", null)), detail: none,
165
- jobs: [
166
- { key: "set", phase: "SET", batch: none, start: week(27n), end: week(28n), tonnes: none, state: variant("actual", null), alert: none },
167
- { key: "b214", phase: "RUN", batch: some("B-214"), start: week(28n), end: week(31n), tonnes: some(96.0), state: variant("in-progress", null), alert: none },
168
- { key: "cln", phase: "CLN", batch: none, start: week(31n), end: week(32n), tonnes: none, state: variant("confirmed", null), alert: none },
169
- { key: "b221", phase: "RUN", batch: some("B-221"), start: week(32n), end: week(35n), tonnes: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none },
170
- ],
171
- decisions: [{ key: "d1", at: Plan.at.time(week(32n)), applied: false }],
172
- ports: [{ at: Plan.at.time(week(31n)), label: some("−24 t") }] }) }],
173
- ["L1-M04", { kind: variant("machine", { cap: 120.0, status: none,
174
- // The expand declaration — a stored plain-data record
175
- // (§3.2); presence is a per-row fact and the ROOT's
176
- // expandRender mounts the body.
177
- detail: some({ height: some("152px"), axis: variant("keep", null) }),
178
- jobs: [
179
- { key: "b208", phase: "RUN", batch: some("B-208"), start: week(27n), end: week(30n), tonnes: some(112.0), state: variant("actual", null), alert: none },
180
- { key: "hld", phase: "HLD", batch: none, start: week(30n), end: week(31n), tonnes: none, state: variant("confirmed", null), alert: none },
181
- { key: "qc", phase: "QC", batch: none, start: week(31n), end: week(33n), tonnes: none, state: variant("confirmed", null), alert: none },
182
- { key: "b231", phase: "RUN", batch: some("B-231"), start: week(33n), end: week(41n), tonnes: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none },
183
- ],
184
- decisions: [], ports: [] }) }],
185
- ["L1-M07", { kind: variant("machine", { cap: 80.0, status: some(variant("warning", null)), detail: none,
186
- jobs: [
187
- { key: "b197", phase: "HLD", batch: some("B-197"), start: week(27n), end: week(31n), tonnes: none, state: variant("actual", null), alert: some(variant("warning", null)) },
188
- { key: "cln", phase: "CLN", batch: none, start: week(34n), end: week(36n), tonnes: none, state: variant("proposed", variant("recommended", null)), alert: none },
189
- ],
190
- decisions: [], ports: [] }) }],
191
- ["l2-load", { kind: variant("load", { name: "L2 load", sub: "%/wk", cells: loadCells }) }],
192
- ["dock2", { kind: variant("dock", { name: "Dock 2",
193
- allocations: [
194
- { key: "a1", at: week(27n), state: variant("confirmed", null) },
195
- { key: "a2", at: week(28n), state: variant("confirmed", null) },
196
- { key: "a3", at: week(29n), state: variant("confirmed", null) },
197
- { key: "a4", at: week(30n), state: variant("confirmed", null) },
198
- { key: "a5", at: week(31n), state: variant("proposed", variant("recommended", null)) },
199
- { key: "a6", at: week(33n), state: variant("proposed", variant("recommended", null)) },
200
- { key: "a7", at: week(35n), state: variant("confirmed", null) },
201
- { key: "a8", at: week(35n), state: variant("proposed", variant("recommended", null)) },
202
- ],
203
- markers: [{ at: Plan.at.time(week(35n)), lane: none, status: variant("warning", null), message: "capacity breach — 2 allocations" }] }) }],
204
- ["crewA", { kind: variant("crew", { name: "Crew A", hours: "152h → 168h", shifts: [
205
- { key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
206
- { key: "s2", from: week(29n), to: week(31n), hours: 72.0, state: variant("confirmed", null) },
207
- { key: "s3", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
208
- { key: "s4", from: week(34n), to: week(35n), hours: 48.0, state: variant("estimated", null) },
209
- { key: "s5", from: week(36n), to: week(38n), hours: 56.0, state: variant("proposed", variant("recommended", null)) },
210
- ] }) }],
211
- ["milestones", { kind: variant("stream", { name: "Milestones", marks: [
212
- { key: "kick", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
213
- { key: "d1", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
214
- { key: "rel", at: Plan.at.time(week(33n)), kind: variant("milestone", null), icon: none, label: some("REL 2.4") },
215
- { key: "audit", at: Plan.at.time(week(35n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
216
- { key: "d2", at: Plan.at.time(week(37n)), kind: variant("decision", { applied: false }), icon: none, label: some("×3") },
217
- ] }) }],
218
- ]), DictType(StringType, OpsRow));
219
- // The series — the list IS the layout (#822): one block per
220
- // series, top to bottom in this order, a section titling the
221
- // blocks beneath it. The whole list is an East value typed by the
222
- // constructor.
223
- const series = $.const([
224
- Plan.series.chart(OpsRow, {
225
- key: "coverage", title: "Coverage",
226
- match: r => r.kind.hasTag("kpi"),
227
- label: r => r.kind.unwrap("kpi").name, id: true,
228
- pinned: r => r.kind.unwrap("kpi").pinned,
229
- value: r => some(r.kind.unwrap("kpi").headline),
230
- status: _r => some(variant("warning", null)),
231
- height: "spark", expandable: true,
232
- layers: r => [
233
- Plan.layer(Chart.Line(r.kind.unwrap("kpi").points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
234
- Chart.refLine({ y: 100, label: "TARGET 100" }),
235
- ],
236
- }),
237
- Plan.series.section(OpsRow, { key: "line1", title: "Line 1", meta: "3 rows · 82%" }, [
238
- Plan.series.span(OpsRow, {
239
- key: "machines", title: "Machines",
240
- match: r => r.kind.hasTag("machine"),
241
- label: (_r, k) => k, id: true,
242
- value: r => some(East.str `${East.Float.printFixed(r.kind.unwrap("machine").cap, 0n)} t`),
243
- status: r => r.kind.unwrap("machine").status,
244
- expand: r => r.kind.unwrap("machine").detail,
245
- runs: r => jobRuns(r.kind.unwrap("machine").jobs),
246
- decisions: r => r.kind.unwrap("machine").decisions,
247
- ports: r => r.kind.unwrap("machine").ports,
248
- }),
249
- ]),
250
- Plan.series.section(OpsRow, { key: "line2", title: "Line 2", value: "98%", status: "warning", collapsed: true, summaryAggregate: "mean" }, [
251
- Plan.series.heat(OpsRow, {
252
- key: "load", title: "Load",
253
- match: r => r.kind.hasTag("load"),
254
- label: r => r.kind.unwrap("load").name,
255
- sub: r => some(r.kind.unwrap("load").sub),
256
- cells: r => Plan.heatCells(r.kind.unwrap("load").cells, { min: 0, max: 100, warnAt: 95 }),
257
- }),
258
- ]),
259
- Plan.series.section(OpsRow, { key: "docks-in", title: "Docks · In", meta: "1 row" }, [
260
- Plan.series.buckets(OpsRow, {
261
- key: "docks", title: "Docks",
262
- match: r => r.kind.hasTag("dock"),
263
- label: r => r.kind.unwrap("dock").name,
264
- sub: _r => some("load/wk"),
265
- events: r => r.kind.unwrap("dock").allocations.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
266
- markers: r => r.kind.unwrap("dock").markers,
267
- }),
268
- ]),
269
- Plan.series.cards(OpsRow, {
270
- key: "crews", title: "Crews",
271
- match: r => r.kind.hasTag("crew"),
272
- label: r => r.kind.unwrap("crew").name, stacked: true,
273
- sub: r => some(r.kind.unwrap("crew").hours),
274
- chips: r => shiftChips(r.kind.unwrap("crew").shifts),
275
- }),
276
- Plan.series.events(OpsRow, {
277
- key: "milestones", title: "Milestones",
278
- match: r => r.kind.hasTag("stream"),
279
- label: r => r.kind.unwrap("stream").name, id: true,
280
- value: r => some(East.print(r.kind.unwrap("stream").marks.length())),
281
- marks: r => r.kind.unwrap("stream").marks,
282
- }),
283
- ], ArrayType(Plan.Types.Series(OpsRow)));
284
- const axis = $.const(Plan.axis({
285
- window: { min: week(27n), max: week(39n) },
286
- resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
287
- }));
288
- // The §1 toolbar is SEEDED slice state: the applied window is the
289
- // range chip (`JUN 29 – SEP 20 · 84d`), and two saved cohorts sit
290
- // beside the filter builder with `Late risk` active — their
291
- // counts are live over the horizon fixture, never printed. A slice
292
- // range is CLOSED — both ends inclusive — so the twelve weeks
293
- // W27–W38 end the millisecond before W39.
294
- const slice = $.let(Slice.bind([HorizonRow], "ex.plan.target", cfg, Slice.state({
295
- range: some(variant("datetime", { from: week(27n), to: week(39n).addMilliseconds(-1n) })),
296
- cohorts: [
297
- { id: "late", name: "Late risk", filters: [variant("string", { fieldId: "risk", op: variant("eq", "late") })] },
298
- { id: "empty", name: "Empty", filters: [variant("float", { fieldId: "tonnes", op: variant("lte", 0.0) })] },
299
- ],
300
- activeCohorts: new Set(["late"]),
301
- }), horizon, none));
302
- // The R2 developer render — the ROOT's resolver, called with the
303
- // focused row's id; ONE function serves every row whose `expand`
304
- // accessor returned some(...). The machine series declares it.
305
- const util = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(17n).remainder(45n).toFloat().add(52.0) })));
306
- const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Chart, { layers: [Chart.Line(util, { x: r => r.week, y: r => r.pct })], height: 120, grid: false }))));
307
- // The generalized element resolvers — ONE stored popover / hover
308
- // function each over Plan.Types.ElementRef (every arm carries the
309
- // row's id); rich bodies build lazily at interaction time, and a
310
- // none result opens no surface.
311
- const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
312
- const noBody = $.const(none, OptionType(UIComponentType));
313
- const m03 = $.const(Plan.ref("machines", "L1-M03"));
314
- return ref.match({
315
- run: (_$, ev) => ev.run.equal("b221").ifElse(() => some(_jsx(Text, { children: "Proposed by run 412 \u2014 fills the W32 idle window." })), () => noBody),
316
- mark: (_$, ev) => East.equal(ev.row, m03).and(() => ev.mark.equal("d1")).ifElse(() => some(_jsx(Text, { children: "Schedule B-221" })), () => noBody),
317
- }, _$ => noBody);
318
- }));
319
- const hover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
320
- const noBody = $.const(none, OptionType(UIComponentType));
321
- return ref.match({
322
- run: (_$, ev) => ev.run.equal("b197").ifElse(() => some(_jsx(Text, { children: "Waiting on QC gate 4 \u2014 2.6\u00D7 median dwell." })), () => noBody),
323
- }, _$ => noBody);
324
- }));
325
- // Behavior props — bound once so memoized renderers keep identity.
326
- const onRow = $.const(East.function([Plan.Types.RowId], NullType, (_$, _id) => null));
327
- // ONE element callback (#824) — a run, tile, mark, chip, cell or
328
- // link ribbon, by the same ref the popover resolver receives.
329
- const onElementClick = $.const(East.function([Plan.Types.ElementRef], NullType, (_$, _ref) => null));
330
- const onGroupToggle = $.const(East.function([Plan.Types.GroupToggleEvent], NullType, (_$, _e) => null));
331
- const onBatch = $.const(East.function([], NullType, (_$) => null));
332
- return (_jsx(Plan, { slice: { slice, affordances: ["cohort", "filter", "search", "range", "resolution", "brush", "summary"] }, axis: axis,
333
- // The link graph (R1) — the W31 −24 t transfer, its ends
334
- // named by row id: hover a linked machine for the
335
- // links-focus control. Its quantity weighs the ribbon, and
336
- // `text` says it the author's way.
337
- links: [
338
- Plan.link({
339
- key: "t-w31",
340
- from: Plan.ref("machines", "L1-M03"), fromRun: "b214",
341
- to: Plan.ref("machines", "L1-M04"), toRun: "qc",
342
- quantity: Plan.quantity(24, { unit: "t", text: "−24 t" }),
343
- }),
344
- ], data: ops, series: series,
345
- // The review chrome — its foot and Rerun. A verdict is a
346
- // draft of an editing session (`planReview`, `planEditing`).
347
- review: {
348
- summary: _jsx(Text, { children: "4 JOBS \u00B7 2 FLAGGED NEED A CALL \u00B7 +6H FLOAT" }),
349
- onRerun: onBatch,
350
- }, expandRender: expandRender, popover: popover, hover: hover, onSelect: onRow, onElementClick: onElementClick, onGroupToggle: onGroupToggle, footer: [
351
- { text: "512 RESOURCES · 12 GROUPS · 3 IN VIEW" },
352
- { text: "318 OBSERVED · 966 PLANNED" },
353
- { text: "3 EXCEPTIONS", tone: "warning" },
354
- { text: "RUN 412 · W27–W38", end: true },
355
- ] }));
356
- } }));
357
- }),
358
- inputs: [],
359
- });
360
- // ============================================================================
361
- // planVariants — THE Plan configurator (#571): axis presets × style sweeps
362
- // ============================================================================
363
- /**
364
- * THE Plan configurator — the slot-2 variant-space surface the retired Gantt /
365
- * Planner configurators held (#571). ONE live canvas; every axis feeds it as
366
- * an expression. The window presets are whole `Plan.axis` VALUES riding a
367
- * typed preset struct: the ops week window, the year roadmap at month
368
- * resolution, and the 14-day sprint at day resolution with `ddd DD` labels
369
- * (the #309 pinned-day-columns contract the Planner's `day` preset and the
370
- * AlignedStack date-axis panel guarded). The style sweeps ride `style` —
371
- * density rhythm and gutter width — over a canvas holding every row kind, so
372
- * one click re-rhythms them all; every callback (select / element click /
373
- * group toggle / grain change) logs to the aside, the retired configurators'
374
- * pattern. Fill sizing stays `planFill`'s; the per-kind visual grammars stay
375
- * the static per-kind panels.
376
- */
377
- export const planVariants = example({
378
- keywords: [
379
- "Plan", "configurator", "Configurator", "variants", "preset", "axis", "window",
380
- "resolution", "month", "week", "day", "roadmap", "sprint", "ops", "format",
381
- "ddd", "#309", "density", "condensed", "compact", "comfortable", "gutterWidth",
382
- "gutter", "style", "onSelect", "onElementClick", "ElementRef", "onGroupToggle", "onGrainChange",
383
- "callback", "aside", "Reactive", "State", "SegmentGroup", "Select", "getTag",
384
- "every row kind", "span", "chart", "expandable", "heat", "buckets", "table",
385
- "tableSeries", "vertical", "cards", "events", "group", "section", "row id", "print",
386
- ],
387
- description: "Plan configurator — axis window presets (ops week / year roadmap / 14-day sprint) with density and gutter-width sweeps over every row kind on one live canvas; every callback logs to the aside",
388
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
389
- // Monday of ISO week n, 2026 — the ops window is W27–W38
390
- // (half-open at W39), now = W31; the roadmap preset walks the
391
- // whole ISO year and the sprint preset a 14-day slice of it.
392
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
393
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
394
- return w1.addWeeks(n.subtract(1n));
395
- }));
396
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
397
- const JobRow = StructType({
398
- key: StringType, label: StringType,
399
- start: DateTimeType, end: DateTimeType, state: EventStateType,
400
- });
401
- const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
402
- const ShiftRow = StructType({
403
- key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
404
- });
405
- const RawCell = StructType({ at: DateTimeType, value: OptionType(FloatType) });
406
- // ONE flat source (the `planExpand` shape): `series` names the
407
- // series that claims the row, and every channel a row's kind does
408
- // not read stays empty.
409
- const OpsRow = StructType({
410
- series: StringType, label: StringType,
411
- sub: OptionType(StringType),
412
- jobs: ArrayType(JobRow),
413
- points: ArrayType(MeasureRow),
414
- cells: ArrayType(Plan.Types.HeatCell),
415
- allocs: ArrayType(AllocRow),
416
- act: ArrayType(RawCell),
417
- plan: ArrayType(RawCell),
418
- shifts: ArrayType(ShiftRow),
419
- marks: ArrayType(Plan.Types.EventMark),
420
- });
421
- const noJobs = $.const([], ArrayType(JobRow));
422
- const noPoints = $.const([], ArrayType(MeasureRow));
423
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
424
- const noAllocs = $.const([], ArrayType(AllocRow));
425
- const noRaw = $.const([], ArrayType(RawCell));
426
- const noShifts = $.const([], ArrayType(ShiftRow));
427
- const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
428
- const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
429
- const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
430
- const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
431
- at: Plan.at.time(week(i.multiply(2n).add(27n))),
432
- value: some(pcts.get(i)),
433
- label: some(East.Float.printFixed(pcts.get(i), 0n)),
434
- })));
435
- // Despatched tonnes and their signed Δ against plan, fortnightly —
436
- // the table row's two positions.
437
- const act = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
438
- at: week(i.multiply(2n).add(27n)), value: some(pcts.get(i).multiply(1.5)),
439
- })));
440
- const delta = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
441
- at: week(i.multiply(2n).add(27n)), value: some(pcts.get(i).subtract(70.0)),
442
- })));
443
- const base = {
444
- sub: none, jobs: noJobs, points: noPoints, cells: noCells,
445
- allocs: noAllocs, act: noRaw, plan: noRaw, shifts: noShifts, marks: noMarks,
446
- };
447
- // Every row kind on purpose: the density sweep re-rhythms them all
448
- // at once — compact tightens the shared row and the bars in it,
449
- // while a two-line gutter keeps its floor and a stacked table a
450
- // line per position — and the gutter sweep re-widths every label.
451
- const ops = $.const(new Map([
452
- ["util", { ...base, series: "util", label: "Util %", points }],
453
- ["m03", { ...base, series: "mach", label: "L1-M03", sub: some("cap 120 t"),
454
- jobs: [
455
- { key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) },
456
- { key: "b221", label: "RUN · B-221", start: week(32n), end: week(35n), state: variant("proposed", variant("recommended", null)) },
457
- ] }],
458
- ["m04", { ...base, series: "mach", label: "L1-M04",
459
- jobs: [
460
- { key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
461
- ] }],
462
- ["load", { ...base, series: "load", label: "L2 load", sub: some("%/wk"), cells }],
463
- ["dock2", { ...base, series: "dock", label: "Dock 2",
464
- allocs: [
465
- { key: "a1", at: week(28n), state: variant("confirmed", null) },
466
- { key: "a2", at: week(31n), state: variant("proposed", variant("recommended", null)) },
467
- { key: "a3", at: week(33n), state: variant("confirmed", null) },
468
- ] }],
469
- ["desp", { ...base, series: "desp", label: "Despatch t", act, plan: delta }],
470
- ["crewA", { ...base, series: "crew", label: "Crew A",
471
- shifts: [
472
- { key: "s1", from: week(27n), to: week(29n), label: "80h", state: variant("confirmed", null) },
473
- { key: "s2", from: week(31n), to: week(33n), label: "+64h", state: variant("proposed", variant("recommended", null)) },
474
- ] }],
475
- ["ms", { ...base, series: "ms", label: "Milestones",
476
- marks: [
477
- { key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
478
- { key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
479
- ] }],
480
- ["m11", { ...base, series: "gmach", label: "L3-M11",
481
- jobs: [
482
- { key: "b301", label: "RUN · B-301", start: week(29n), end: week(33n), state: variant("confirmed", null) },
483
- ] }],
484
- ]), DictType(StringType, OpsRow));
485
- const series = $.const([
486
- // The spark's gutter opens it to the expanded chart, at any density.
487
- Plan.series.chart(OpsRow, {
488
- key: "util", title: "Utilisation",
489
- match: r => r.series.equal("util"),
490
- label: r => r.label, id: true, height: "spark", expandable: true,
491
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
492
- }),
493
- Plan.series.span(OpsRow, {
494
- key: "mach", title: "Machine jobs",
495
- match: r => r.series.equal("mach"),
496
- label: r => r.label, id: true, sub: r => r.sub,
497
- runs: r => r.jobs.map((_$, j) => Plan.run({
498
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
499
- })),
500
- }),
501
- Plan.series.heat(OpsRow, {
502
- key: "load", title: "Line load",
503
- match: r => r.series.equal("load"),
504
- label: r => r.label, sub: r => r.sub,
505
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
506
- }),
507
- Plan.series.buckets(OpsRow, {
508
- key: "dock", title: "Dock allocations",
509
- match: r => r.series.equal("dock"),
510
- label: r => r.label,
511
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
512
- }),
513
- // The positions STACKED — the row grows a line per position.
514
- Plan.series.table(OpsRow, {
515
- key: "desp", title: "Despatch",
516
- match: r => r.series.equal("desp"),
517
- label: r => r.label, split: "vertical",
518
- series: r => [
519
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
520
- Plan.tableSeries({
521
- tone: "muted",
522
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
523
- cells: Plan.tableCells(r.plan),
524
- }),
525
- ],
526
- format: Format.Number({ maximumFractionDigits: 0n }),
527
- }),
528
- Plan.series.cards(OpsRow, {
529
- key: "crew", title: "Crew shifts",
530
- match: r => r.series.equal("crew"),
531
- label: r => r.label,
532
- chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
533
- }),
534
- Plan.series.events(OpsRow, {
535
- key: "ms", title: "Milestones",
536
- match: r => r.series.equal("ms"),
537
- label: r => r.label, id: true,
538
- marks: r => r.marks,
539
- }),
540
- // A section header, so the group-toggle callback has something to fire on.
541
- Plan.series.section(OpsRow, { key: "line3", title: "Line 3", meta: "1 row" }, [
542
- Plan.series.span(OpsRow, {
543
- key: "gmach", title: "Grouped jobs",
544
- match: r => r.series.equal("gmach"),
545
- label: r => r.label, id: true,
546
- runs: r => r.jobs.map((_$, j) => Plan.run({
547
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
548
- })),
549
- }),
550
- ]),
551
- ], ArrayType(Plan.Types.Series(OpsRow)));
552
- // Every preset is DATA — a whole axis value in a typed struct, so
553
- // switching presets swaps window, resolution, format and now
554
- // through ONE expression-fed prop.
555
- const presets = $.const([
556
- { key: "ops", axis: Plan.axis({
557
- window: { min: week(27n), max: week(39n) },
558
- resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
559
- }) },
560
- { key: "roadmap", axis: Plan.axis({
561
- window: { min: week(1n), max: week(53n) },
562
- resolution: "month", format: "MMM", now: week(31n),
563
- }) },
564
- { key: "sprint", axis: Plan.axis({
565
- window: { min: week(30n), max: week(32n) },
566
- resolution: "day", format: "ddd DD", now: week(31n),
567
- }) },
568
- ], ArrayType(StructType({ key: StringType, axis: Plan.Types.Axis })));
569
- const presetKeys = $.const(["ops", "roadmap", "sprint"], ArrayType(StringType));
570
- const densities = $.const([
571
- variant("condensed", null), variant("compact", null), variant("comfortable", null),
572
- ], ArrayType(Style.Types.Density));
573
- const gutters = $.const(["168px", "200px", "240px"], ArrayType(StringType));
574
- const presetBind = $.let(State.bind([StringType], "plan_variants_preset", "ops"));
575
- const densityBind = $.let(State.bind([StringType], "plan_variants_density", "compact"));
576
- const gutterBind = $.let(State.bind([StringType], "plan_variants_gutter", "168px"));
577
- const lastEventBind = $.let(State.bind([StringType], "plan_variants_last_event", ""));
578
- const pKey = $.let(presetBind.read());
579
- const dKey = $.let(densityBind.read());
580
- const gKey = $.let(gutterBind.read());
581
- const lastEvent = $.let(lastEventBind.read());
582
- const onPreset = $.const(East.function([StringType], NullType, ($, next) => { $(presetBind.write(next)); }));
583
- const onDensity = $.const(East.function([StringType], NullType, ($, next) => { $(densityBind.write(next)); }));
584
- const onGutter = $.const(East.function([StringType], NullType, ($, next) => { $(gutterBind.write(next)); }));
585
- // The interactive surface — every callback writes the aside line,
586
- // naming the row by its id (the series that made it and the path
587
- // of keys to it) in its `.east` text.
588
- const onSelect = $.const(East.function([Plan.Types.RowId], NullType, ($, id) => {
589
- $(lastEventBind.write(East.str `onSelect · ${East.print(id)}`));
590
- }));
591
- // ONE callback for every element (#824) — the ref's arm says which
592
- // kind was clicked, and each arm names its row and element.
593
- const onElementClick = $.const(East.function([Plan.Types.ElementRef], NullType, ($, ref) => {
594
- $(lastEventBind.write(East.str `onElementClick · ${ref.getTag()} · ${East.print(ref)}`));
595
- }));
596
- const onGroupToggle = $.const(East.function([Plan.Types.GroupToggleEvent], NullType, ($, ev) => {
597
- const state = $.let(ev.expanded.ifElse(() => "expanded", () => "collapsed"), StringType);
598
- $(lastEventBind.write(East.str `onGroupToggle · ${East.print(ev.row)} → ${state}`));
599
- }));
600
- const onGrainChange = $.const(East.function([Plan.Types.Grain], NullType, ($, grain) => {
601
- $(lastEventBind.write(East.str `onGrainChange · ${grain.getTag()}`));
602
- }));
603
- const sel = $.let(presets.filter((_$, o) => o.key.equal(pKey)).get(0n, _$ => presets.get(0n)));
604
- const densitySel = $.let(densities.filter((_$, v) => v.getTag().equal(dKey)).get(0n));
605
- return (_jsx(Configurator, { controls: [
606
- Configurator.Control("Preset", pKey, _jsx(Select, { value: pKey, onChange: onPreset, size: "sm", items: presetKeys.map((_$, s) => Select.Item(s, s)) })),
607
- Configurator.Control("Density", dKey, _jsx(SegmentGroup, { value: dKey, onChange: onDensity, size: "sm", items: densities.map((_$, v) => SegmentGroup.Item(v.getTag(), _jsx(Text, { children: v.getTag().upperCase() }))) })),
608
- Configurator.Control("Gutter", gKey, _jsx(SegmentGroup, { value: gKey, onChange: onGutter, size: "sm", items: gutters.map((_$, g) => SegmentGroup.Item(g, _jsx(Text, { children: g }))) })),
609
- ], preview: _jsx(Plan, { axis: sel.axis, data: ops, series: series, onSelect: onSelect, onElementClick: onElementClick, onGroupToggle: onGroupToggle, onGrainChange: onGrainChange, style: { density: densitySel, gutterWidth: gKey } }), live: true, aside: {
610
- label: "Events · Reactive",
611
- body: (_jsx(Badge, { colorPalette: "brand", variant: "outline", children: East.equal(lastEvent.length(), 0n).ifElse(_$ => "Interact with the canvas", _$ => lastEvent) })),
612
- }, spec: [
613
- Configurator.Spec("Resolution", sel.axis.unwrap("time").resolution.getTag()),
614
- Configurator.Spec("Rows", East.print(ops.size())),
615
- ] }));
616
- } }))),
617
- inputs: [],
618
- });
619
- // ============================================================================
620
- // Per-kind examples — one canvas per row kind; the config sweep is DATA
621
- // ============================================================================
622
- export const planSpanRows = example({
623
- keywords: ["Plan", "data", "series", "span", "run", "state", "estimated", "removed", "rejected", "decision", "port", "rollup", "union", "byStatus", "children", "nested", "recursive", "RecursiveType", "bands", "section", "stacked", "gutter", "links", "link", "Plan.ref", "row id", "focus", "expand", "expandRender", "match", "raw", "quantity", "Plan.quantity", "unit", "sum per unit"],
624
- description: "Span rows over one raw machine source — proposal flavours, decision diamonds and ports, programs rolling their machines up into bands, and a link graph",
625
- fn: East.function([], UIComponentType, ($) => {
626
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
627
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
628
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
629
- return w1.addWeeks(n.subtract(1n));
630
- }));
631
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
632
- // The RAW job record; `series` picks the series, everything else —
633
- // including the expand declaration — is per-row data.
634
- const JobRow = StructType({
635
- key: StringType, phase: StringType, batch: OptionType(StringType),
636
- start: DateTimeType, end: DateTimeType,
637
- tonnes: OptionType(FloatType), state: EventStateType,
638
- });
639
- // A PROGRAM is a record too, holding its machines (`machines`): the
640
- // hierarchy is the data's own (#822), so a program's row nests them —
641
- // and the entry type is recursive, to whatever depth the data has.
642
- const MachineRow = RecursiveType((self) => StructType({
643
- series: StringType,
644
- sub: OptionType(StringType), value: OptionType(StringType),
645
- expand: OptionType(Plan.Types.Expand),
646
- jobs: ArrayType(JobRow),
647
- decisions: ArrayType(Plan.Types.DecisionMark),
648
- ports: ArrayType(Plan.Types.Port),
649
- machines: DictType(StringType, self),
650
- }));
651
- const noMachines = $.const(new Map(), DictType(StringType, MachineRow));
652
- const machines = $.const(new Map([
653
- // Proposal flavours: forecast ghost · proposed cut · declined.
654
- ["L1-M07", { series: "flavours", sub: none, value: some("80 t"), expand: none,
655
- jobs: [
656
- { key: "run", phase: "RUN", batch: some("B-197"), start: week(27n), end: week(30n), tonnes: some(64.0), state: variant("in-progress", null) },
657
- { key: "gho", phase: "FORECAST", batch: none, start: week(30n), end: week(32n), tonnes: none, state: variant("estimated", null) },
658
- { key: "rem", phase: "CUT", batch: none, start: week(33n), end: week(35n), tonnes: none, state: variant("proposed", variant("removed", null)) },
659
- { key: "rej", phase: "DECLINED", batch: none, start: week(36n), end: week(38n), tonnes: none, state: variant("rejected", null) },
660
- ], decisions: [], ports: [], machines: noMachines }],
661
- // Tonnage + an applied decision + a port on a stacked two-line
662
- // gutter; the EXPAND DECLARATION is row data (R2) — the render is
663
- // the root's expandRender resolver.
664
- ["L1-M09", { series: "detail", sub: some("cap 120 t"), value: none,
665
- expand: some({ height: some("152px"), axis: variant("dim", null) }),
666
- jobs: [
667
- { key: "a", phase: "RUN", batch: some("B-208"), start: week(27n), end: week(31n), tonnes: some(112.0), state: variant("actual", null) },
668
- { key: "b", phase: "RUN", batch: some("B-231"), start: week(31n), end: week(34n), tonnes: some(104.0), state: variant("proposed", variant("recommended", null)) },
669
- ],
670
- decisions: [{ key: "d1", at: Plan.at.time(week(31n)), applied: true }],
671
- ports: [{ at: Plan.at.time(week(31n)), label: some("−24 t") }], machines: noMachines }],
672
- // A program and its machines — the program's row rolls their runs
673
- // up into union bands (renderer-derived).
674
- ["Program A", { series: "rollup", sub: none, value: none, expand: none, jobs: [], decisions: [], ports: [],
675
- machines: new Map([
676
- ["L1-M03", { series: "rollup", sub: none, value: none, expand: none,
677
- jobs: [
678
- { key: "b214", phase: "RUN", batch: some("B-214"), start: week(28n), end: week(31n), tonnes: some(96.0), state: variant("actual", null) },
679
- { key: "b221", phase: "RUN", batch: some("B-221"), start: week(32n), end: week(35n), tonnes: some(88.0), state: variant("proposed", variant("recommended", null)) },
680
- ], decisions: [], ports: [], machines: noMachines }],
681
- ["L2-M11", { series: "rollup", sub: none, value: none, expand: none,
682
- jobs: [{ key: "b241", phase: "RUN", batch: some("B-241"), start: week(29n), end: week(33n), tonnes: some(92.0), state: variant("confirmed", null) }],
683
- decisions: [], ports: [], machines: noMachines }],
684
- ]) }],
685
- // Linked despatch whose run starts BEYOND the window — in links
686
- // focus its landing renders as the edge fade.
687
- ["dsp", { series: "despatch", sub: none, value: none, expand: none,
688
- jobs: [{ key: "d1", phase: "DSP", batch: none, start: week(39n), end: week(42n), tonnes: some(91.0), state: variant("proposed", variant("recommended", null)) }],
689
- decisions: [], ports: [], machines: noMachines }],
690
- // The program under the Line 2 section, rolled up byStatus.
691
- ["Program B", { series: "line2", sub: none, value: none, expand: none, jobs: [], decisions: [], ports: [],
692
- machines: new Map([
693
- ["L2-M12", { series: "line2", sub: none, value: none, expand: none,
694
- jobs: [
695
- { key: "r1", phase: "RUN", batch: some("B-198"), start: week(28n), end: week(32n), tonnes: some(64.0), state: variant("actual", null) },
696
- { key: "r2", phase: "RUN", batch: some("B-202"), start: week(30n), end: week(34n), tonnes: some(40.0), state: variant("proposed", variant("recommended", null)) },
697
- ], decisions: [], ports: [], machines: noMachines }],
698
- ]) }],
699
- ]), DictType(StringType, MachineRow));
700
- // Raw jobs → runs, once — every span series shares the mapping. A
701
- // job's tonnage is its run's QUANTITY: the bar prints it, and a
702
- // program's bands sum its machines' tonnes unit by unit (#824).
703
- const tonnesFormat = $.const(Format.Number({ maximumFractionDigits: 0n }));
704
- const jobRuns = $.const(East.function([ArrayType(JobRow)], ArrayType(Plan.Types.Run), (_$, jobs) => jobs.map(($, j) => {
705
- const noQuantity = $.const(none, OptionType(Plan.Types.Quantity));
706
- const quantity = $.let(j.tonnes.match({
707
- some: (_$, t) => East.value(some(Plan.quantity(t, { unit: "t", format: tonnesFormat })), OptionType(Plan.Types.Quantity)),
708
- none: (_$) => noQuantity,
709
- }), OptionType(Plan.Types.Quantity));
710
- const label = $.let(j.batch.match({
711
- some: (_$, b) => East.str `${j.phase} · ${b}`,
712
- none: (_$) => j.phase,
713
- }), StringType);
714
- const run = $.let({
715
- key: j.key, start: Plan.at.time(j.start), end: Plan.at.time(j.end), label,
716
- quantity, state: j.state,
717
- status: none, moved: none, icon: none,
718
- }, Plan.Types.Run);
719
- return run;
720
- })));
721
- const series = $.const([
722
- Plan.series.span(MachineRow, {
723
- key: "flavours", title: "Flavours",
724
- match: r => r.series.equal("flavours"),
725
- label: (_r, k) => k, id: true,
726
- value: r => r.value,
727
- runs: r => jobRuns(r.jobs),
728
- }),
729
- Plan.series.span(MachineRow, {
730
- key: "detail", title: "Detail",
731
- match: r => r.series.equal("detail"),
732
- label: (_r, k) => k, id: true, stacked: true,
733
- sub: r => r.sub, expand: r => r.expand,
734
- runs: r => jobRuns(r.jobs), decisions: r => r.decisions, ports: r => r.ports,
735
- }),
736
- // A program's machines nest under it (`children` — more of this
737
- // series, to any depth), and its row rolls their runs up.
738
- Plan.series.span(MachineRow, {
739
- key: "rollup", title: "Rollup",
740
- match: r => r.series.equal("rollup"),
741
- label: (_r, k) => k, id: true,
742
- runs: r => jobRuns(r.jobs),
743
- children: r => r.machines, rollup: "union",
744
- }),
745
- Plan.series.span(MachineRow, {
746
- key: "despatch", title: "Despatch",
747
- match: r => r.series.equal("despatch"),
748
- label: (_r, k) => k, id: true,
749
- runs: r => jobRuns(r.jobs),
750
- }),
751
- Plan.series.section(MachineRow, { key: "line2", title: "Line 2", meta: "1 row" }, [
752
- Plan.series.span(MachineRow, {
753
- key: "programs", title: "Programs",
754
- match: r => r.series.equal("line2"),
755
- label: (_r, k) => k, id: true,
756
- runs: r => jobRuns(r.jobs),
757
- children: r => r.machines, rollup: "byStatus",
758
- }),
759
- ]),
760
- ], ArrayType(Plan.Types.Series(MachineRow)));
761
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
762
- // The R2 developer render — the ROOT's resolver, called with the
763
- // focused row's id; ONE function serves every declaring row.
764
- const util = $.let(East.Array.generate(8n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(13n).remainder(40n).toFloat().add(55.0) })));
765
- const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Chart, { layers: [Chart.Column(util, { x: r => r.week, y: r => r.pct })], height: 100, grid: false }))));
766
- // The generalized popover resolver — decision diamonds ride the mark
767
- // arm of the element ref.
768
- const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
769
- const noBody = $.const(none, OptionType(UIComponentType));
770
- return ref.match({
771
- mark: (_$, ev) => ev.mark.equal("d1").ifElse(() => some(_jsx(Text, { children: "Approved by run 411." })), () => noBody),
772
- }, _$ => noBody);
773
- }));
774
- // The linked rows, by id — the series that made each and the path of
775
- // entry keys to it (a program's machine sits under the program).
776
- const m07 = $.const(Plan.ref("flavours", "L1-M07"));
777
- const m09 = $.const(Plan.ref("detail", "L1-M09"));
778
- const m03 = $.const(Plan.ref("rollup", "Program A", "L1-M03"));
779
- const m11 = $.const(Plan.ref("rollup", "Program A", "L2-M11"));
780
- const dsp = $.const(Plan.ref("despatch", "dsp"));
781
- return (_jsx(Plan, { expandRender: expandRender, popover: popover,
782
- // A denser gutter (value + carets) — widen it (the shared
783
- // CSS-px height/width vocabulary).
784
- style: { gutterWidth: "200px" }, axis: axis,
785
- // The link graph (R1) — hover a linked row for the ⌁ control.
786
- // The edges deliberately cover the routing permutations:
787
- // forward, a same-row seam feed, loopbacks, a rising loop,
788
- // and an off-window landing. Each moves a QUANTITY (#824):
789
- // its value weighs the ribbon and its caption prints on it.
790
- links: [
791
- Plan.link({ key: "l1", from: m07, fromRun: "run", to: m09, toRun: "a", quantity: Plan.quantity(24, { unit: "t" }) }),
792
- Plan.link({ key: "l2", from: m09, fromRun: "a", to: m09, toRun: "b", quantity: Plan.quantity(40, { unit: "t" }) }),
793
- Plan.link({ key: "l3", from: m09, fromRun: "b", to: m03, toRun: "b221", quantity: Plan.quantity(88, { unit: "t" }) }),
794
- Plan.link({ key: "l4", from: m03, fromRun: "b214", to: m11, toRun: "b241", quantity: Plan.quantity(32, { unit: "t" }) }),
795
- Plan.link({ key: "l5", from: m11, fromRun: "b241", to: m09, toRun: "b", quantity: Plan.quantity(18, { unit: "t" }) }),
796
- Plan.link({ key: "l6", from: m09, fromRun: "b", to: dsp, toRun: "d1", quantity: Plan.quantity(91, { unit: "t" }) }),
797
- ], data: machines, series: series }));
798
- }),
799
- inputs: [],
800
- });
801
- export const planBucketRows = example({
802
- keywords: ["Plan", "data", "series", "buckets", "Planner", "lane", "lanes", "AM", "PM", "event", "tile", "marker", "tone", "color", "colorPalette", "stretch", "pulse", "icon", "hovercard", "popover", "mixed", "unbucketed", "section", "match", "gutter", "raw"],
803
- description: "Bucket rows over one dock source — tiles derived in the accessor, and stored tile records with lanes, tones, colours and markers",
804
- fn: East.function([], UIComponentType, ($) => {
805
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
806
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
807
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
808
- return w1.addWeeks(n.subtract(1n));
809
- }));
810
- const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
811
- const DockRow = StructType({
812
- series: StringType, label: StringType,
813
- sub: OptionType(StringType),
814
- lanes: ArrayType(Plan.Types.Lane),
815
- allocations: ArrayType(AllocRow),
816
- tiles: ArrayType(Plan.Types.BucketEvent),
817
- markers: ArrayType(Plan.Types.CellMarker),
818
- });
819
- // Raw weekly allocations; every third one is a proposal.
820
- const inbound = $.let(East.Array.generate(6n, AllocRow, ($, i) => {
821
- const confirmed = $.const(variant("confirmed", null), EventStateType);
822
- const recommended = $.const(variant("proposed", variant("recommended", null)), EventStateType);
823
- return {
824
- key: East.str `a${East.print(i)}`,
825
- at: week(i.add(27n)),
826
- state: i.remainder(3n).equal(2n).ifElse(() => recommended, () => confirmed),
827
- };
828
- }));
829
- const docks = $.const(new Map([
830
- ["dock2", { series: "inbound", label: "Dock 2", sub: some("load/wk"),
831
- lanes: [], allocations: inbound, tiles: [],
832
- markers: [{ at: Plan.at.time(week(29n)), lane: none, status: variant("warning", null), message: "capacity 90%" }] }],
833
- // The grammar showcase — tiles stored IN the element vocabulary
834
- // (plain `PlanBucketEventType` records; no builders in data).
835
- ["dock5", { series: "outbound", label: "Dock 5", sub: some("day · am/pm"),
836
- lanes: [{ key: "am", label: some("AM") }, { key: "pm", label: some("PM") }],
837
- allocations: [],
838
- tiles: [
839
- { key: "m1", at: Plan.at.time(week(27n)), lane: some("am"), label: none, icon: none, state: variant("confirmed", null),
840
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
841
- { key: "m2", at: Plan.at.time(week(27n)), lane: some("pm"), label: none, icon: none, state: variant("confirmed", null),
842
- tone: some(variant("warning", null)), color: none, colorPalette: none, stretch: none, content: none, animation: none },
843
- { key: "m3", at: Plan.at.time(week(28n)), lane: some("am"), label: none, icon: none, state: variant("proposed", variant("recommended", null)),
844
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: some(variant("pulse", null)) },
845
- { key: "m4", at: Plan.at.time(week(29n)), lane: none, label: some("MIXED"), icon: none, state: variant("confirmed", null),
846
- tone: none, color: none, colorPalette: none, stretch: some(variant("horizontal", null)),
847
- content: some({ horizontal: some(variant("center", null)), vertical: none }), animation: none },
848
- { key: "m5", at: Plan.at.time(week(30n)), lane: some("pm"), label: none,
849
- icon: some({ prefix: "fas", name: "truck", label: none, style: none }),
850
- state: variant("proposed", variant("recommended", null)),
851
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
852
- { key: "m6", at: Plan.at.time(week(31n)), lane: some("am"), label: some("QC"), icon: none, state: variant("estimated", null),
853
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
854
- // The colour channels (#571, from the Planner's colors
855
- // preset): `color` is a raw token override, `colorPalette`
856
- // recolours the whole lifecycle treatment.
857
- { key: "m7", at: Plan.at.time(week(32n)), lane: some("am"), label: some("S-A"), icon: none, state: variant("confirmed", null),
858
- tone: none, color: some("teal.solid"), colorPalette: none, stretch: none, content: none, animation: none },
859
- { key: "m8", at: Plan.at.time(week(32n)), lane: some("pm"), label: some("S-B"), icon: none, state: variant("confirmed", null),
860
- tone: none, color: none, colorPalette: some(variant("brand", null)), stretch: none, content: none, animation: none },
861
- ],
862
- markers: [{ at: Plan.at.time(week(29n)), lane: none, status: variant("danger", null), message: "capacity breach" }] }],
863
- ]), DictType(StringType, DockRow));
864
- const series = $.const([
865
- // Raw allocations → resting tiles, in the accessor.
866
- Plan.series.buckets(DockRow, {
867
- key: "inbound", title: "Inbound",
868
- match: r => r.series.equal("inbound"),
869
- label: r => r.label,
870
- sub: r => r.sub,
871
- events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
872
- markers: r => r.markers,
873
- }),
874
- Plan.series.section(DockRow, { key: "docks-out", title: "Docks · Out", meta: "1 row" }, [
875
- // Stored vocabulary records pass straight through.
876
- Plan.series.buckets(DockRow, {
877
- key: "outbound", title: "Outbound",
878
- match: r => r.series.equal("outbound"),
879
- label: r => r.label,
880
- sub: r => r.sub,
881
- lanes: r => r.lanes, events: r => r.tiles, markers: r => r.markers,
882
- }),
883
- ]),
884
- ], ArrayType(Plan.Types.Series(DockRow)));
885
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
886
- // The generalized resolvers — tiles ride the event arm of the ref.
887
- const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
888
- const noBody = $.const(none, OptionType(UIComponentType));
889
- return ref.match({
890
- event: (_$, ev) => ev.event.equal("m5").ifElse(() => some(_jsx(Text, { children: "Load 41 \u00B7 8 pallets" })), () => noBody),
891
- }, _$ => noBody);
892
- }));
893
- const hover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
894
- const noBody = $.const(none, OptionType(UIComponentType));
895
- return ref.match({
896
- event: (_$, ev) => ev.event.equal("m3").ifElse(() => some(_jsx(Text, { children: "Urgent \u2014 overtime window" })), () => noBody),
897
- }, _$ => noBody);
898
- }));
899
- return (_jsx(Plan, { popover: popover, hover: hover, axis: axis, data: docks, series: series }));
900
- }),
901
- inputs: [],
902
- });
903
- export const planChartRows = example({
904
- keywords: ["Plan", "data", "series", "chart", "layers", "spark", "expanded", "fixed", "refLine", "refBand", "refDot", "breach", "stacked", "dual-axis", "swatches", "Area", "Band", "Scatter", "Column", "Line", "domain", "tickValues", "section", "match", "gutter", "raw"],
905
- description: "Chart rows over one measure source — one series per mark kind, plus annotations and a fixed dual-axis composition",
906
- fn: East.function([], UIComponentType, ($) => {
907
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
908
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
909
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
910
- return w1.addWeeks(n.subtract(1n));
911
- }));
912
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
913
- const BandRow = StructType({ week: DateTimeType, lo: FloatType, hi: FloatType });
914
- // ONE raw source — each series's points ride the row (`extra` carries
915
- // the second set for stacked / dual compositions).
916
- const ChartRow = StructType({
917
- series: StringType, label: StringType,
918
- sub: OptionType(StringType), value: OptionType(StringType),
919
- points: ArrayType(MeasureRow),
920
- extra: ArrayType(MeasureRow),
921
- band: ArrayType(BandRow),
922
- });
923
- const pcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
924
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: pcts.get(i) })));
925
- const cum = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.toFloat().multiply(14.0).add(40.0) })));
926
- const out1 = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(23n).remainder(17n).toFloat().add(28.0) })));
927
- const out2 = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(31n).remainder(13n).toFloat().add(14.0) })));
928
- const ppm = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: i.multiply(37n).remainder(60n).toFloat().add(120.0) })));
929
- const band = $.let(East.Array.generate(12n, BandRow, (_$, i) => ({ week: week(i.add(27n)), lo: pcts.get(i).subtract(3.0), hi: pcts.get(i).add(3.0) })));
930
- const measures = $.const(new Map([
931
- ["spark", { series: "spark", label: "Coverage", sub: none, value: some("94.2%"), points: coverage, extra: [], band: [] }],
932
- ["cum", { series: "cum", label: "Cumulative · t", sub: none, value: some("194 t"), points: cum, extra: [], band: [] }],
933
- ["stacked", { series: "stacked", label: "Output · t", sub: some("t/wk"), value: none, points: out1, extra: out2, band: [] }],
934
- ["ppm", { series: "ppm", label: "Defects · ppm", sub: none, value: some("161"), points: ppm, extra: [], band: [] }],
935
- ["refs", { series: "refs", label: "Coverage + refs", sub: none, value: none, points: coverage, extra: [], band: [] }],
936
- ["dual", { series: "dual", label: "Out + coverage", sub: none, value: none, points: out1, extra: coverage, band }],
937
- ]), DictType(StringType, ChartRow));
938
- const series = $.const([
939
- // Line — the KPI spark with a breach threshold; the caret opens
940
- // it to a custom 120px (expandedHeight, default 88).
941
- Plan.series.chart(ChartRow, {
942
- key: "spark", title: "Spark",
943
- match: r => r.series.equal("spark"),
944
- label: r => r.label, id: true,
945
- value: r => r.value, status: _r => some(variant("warning", null)),
946
- height: "spark", expandable: true, expandedHeight: "120px",
947
- layers: r => [Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } })],
948
- }),
949
- // Area — the cumulative fill.
950
- Plan.series.chart(ChartRow, {
951
- key: "cum", title: "Cumulative",
952
- match: r => r.series.equal("cum"),
953
- label: r => r.label, id: true, value: r => r.value,
954
- layers: r => [Chart.Area(r.points, { x: p => p.week, y: p => p.pct })],
955
- }),
956
- // Columns — the row's two point sets stacked by one series id,
957
- // on a two-line gutter (label over sub).
958
- Plan.series.chart(ChartRow, {
959
- key: "stacked", title: "Stacked",
960
- match: r => r.series.equal("stacked"),
961
- label: r => r.label, id: true, stacked: true, sub: r => r.sub,
962
- layers: r => [
963
- Plan.layer(Chart.Column(r.points, { x: p => p.week, y: p => p.pct }), { series: "L1" }),
964
- Plan.layer(Chart.Column(r.extra, { x: p => p.week, y: p => p.pct }), { series: "L2" }),
965
- ],
966
- }),
967
- // Scatter — the defect cloud.
968
- Plan.series.chart(ChartRow, {
969
- key: "ppm", title: "Ppm",
970
- match: r => r.series.equal("ppm"),
971
- label: r => r.label, id: true, value: r => r.value,
972
- layers: r => [Chart.Scatter(r.points, { x: p => p.week, y: p => p.pct })],
973
- }),
974
- // Line + every annotation kind, at expanded density.
975
- Plan.series.chart(ChartRow, {
976
- key: "refs", title: "Refs",
977
- match: r => r.series.equal("refs"),
978
- label: r => r.label, id: true,
979
- height: "expanded",
980
- layers: r => [
981
- Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
982
- Chart.refLine({ y: 100, label: "TARGET 100" }),
983
- Chart.refBand({ x: [week(34n), week(36n)], label: "CRUNCH" }),
984
- Chart.refDot({ x: week(36n), y: 91.4, label: "LOW" }),
985
- ],
986
- }),
987
- // The composed dual-axis chart under a section header; axes take
988
- // Chart.Root's vocabulary — domain / tickValues. Output columns
989
- // scale left; the coverage line + its band scale right.
990
- Plan.series.section(ChartRow, { key: "quality", title: "Quality", meta: "1 row" }, [
991
- Plan.series.chart(ChartRow, {
992
- key: "dual", title: "Dual",
993
- match: r => r.series.equal("dual"),
994
- label: r => r.label, id: true,
995
- height: Plan.fixed("120px"),
996
- left: { domain: [0, 60], tickValues: [0, 25, 50] },
997
- right: { domain: [80, 105], tickValues: [85, 95, 105] },
998
- swatches: [{ color: "ink.3", label: "out" }, { color: "brand.d", label: "cov · rh" }],
999
- layers: r => [
1000
- Chart.Column(r.points, { x: p => p.week, y: p => p.pct }),
1001
- Plan.layer(Chart.Line(r.extra, { x: p => p.week, y: p => p.pct }), { axis: "right" }),
1002
- Plan.layer(Chart.Band(r.band, { x: p => p.week, low: p => p.lo, high: p => p.hi }), { axis: "right" }),
1003
- ],
1004
- }),
1005
- ]),
1006
- ], ArrayType(Plan.Types.Series(ChartRow)));
1007
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1008
- return (_jsx(Plan, { axis: axis, data: measures, series: series }));
1009
- }),
1010
- inputs: [],
1011
- });
1012
- export const planHeatRows = example({
1013
- keywords: ["Plan", "data", "series", "heat", "Matrix", "cells", "depth", "aggregate", "mean", "children", "nested", "recursive", "RecursiveType", "scale", "warnAt", "weightCells", "segmentCells", "segment", "no-data", "hatch", "section", "match", "gutter", "raw"],
1014
- description: "Heat rows over one line source — colour-depth cells under a line that averages them, weight bars, and status segments",
1015
- fn: East.function([], UIComponentType, ($) => {
1016
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1017
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1018
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1019
- return w1.addWeeks(n.subtract(1n));
1020
- }));
1021
- // A LINE holds its machines' load rows (`children`): the hierarchy is
1022
- // the data's (#822), so the line's row derives its own cells from
1023
- // theirs.
1024
- const HeatRow = RecursiveType((self) => StructType({
1025
- series: StringType, label: StringType,
1026
- sub: OptionType(StringType),
1027
- cells: ArrayType(Plan.Types.HeatCell),
1028
- weights: ArrayType(Plan.Types.WeightCell),
1029
- segs: ArrayType(Plan.Types.SegmentCell),
1030
- children: DictType(StringType, self),
1031
- }));
1032
- const noChildren = $.const(new Map(), DictType(StringType, HeatRow));
1033
- const pcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
1034
- const cells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
1035
- at: Plan.at.time(week(i.add(27n))),
1036
- value: i.equal(4n).ifElse(() => none, () => some(pcts.get(i))), // W31 = no data
1037
- label: i.equal(4n).ifElse(() => none, () => some(East.Float.printFixed(pcts.get(i), 0n))),
1038
- })));
1039
- // Booked-vs-free fractions; the back half is the planned pale tail.
1040
- const weights = $.let(East.Array.generate(6n, Plan.Types.WeightCell, (_$, i) => ({
1041
- at: Plan.at.time(week(i.multiply(2n).add(27n))),
1042
- fraction: i.toFloat().multiply(-0.11).add(0.9),
1043
- planned: i.greaterEqual(3n),
1044
- })));
1045
- const lines = $.const(new Map([
1046
- // A line with no cells of its own — its row shows the per-bucket
1047
- // mean of its machines' (the series declares `aggregate`).
1048
- ["line1", { series: "depth", label: "Line 1", sub: none, cells: [], weights: [], segs: [],
1049
- children: new Map([
1050
- ["m03h", { series: "depth", label: "L1-M03", sub: none, cells, weights: [], segs: [], children: noChildren }],
1051
- ["m04h", { series: "depth", label: "L1-M04", sub: none, cells, weights: [], segs: [], children: noChildren }],
1052
- ]) }],
1053
- ["booked", { series: "booked", label: "Crew A", sub: some("booked h"), cells: [], weights, segs: [], children: noChildren }],
1054
- // Segment compositions — plain `{ fill, weight, label }` records.
1055
- ["pack", { series: "segments", label: "Pack line", sub: some("capacity"), cells: [], weights: [], children: noChildren,
1056
- segs: [
1057
- { at: Plan.at.time(week(27n)), segments: [
1058
- { fill: variant("success", null), weight: 60.0, label: some("60%") },
1059
- { fill: variant("warning", null), weight: 25.0, label: some("25%") },
1060
- { fill: variant("slack", null), weight: 15.0, label: none },
1061
- ] },
1062
- { at: Plan.at.time(week(28n)), segments: [
1063
- { fill: variant("success", null), weight: 70.0, label: some("70%") },
1064
- { fill: variant("slack", null), weight: 30.0, label: none },
1065
- ] },
1066
- { at: Plan.at.time(week(29n)), segments: [
1067
- { fill: variant("danger", null), weight: 40.0, label: some("40%") },
1068
- { fill: variant("free", null), weight: 60.0, label: none },
1069
- ] },
1070
- ] }],
1071
- ]), DictType(StringType, HeatRow));
1072
- const series = $.const([
1073
- // A line's machines nest under it, and its row is their per-bucket
1074
- // mean — painted on `scale`, the scale a parent's DERIVED cells
1075
- // take (#824; a mean of rows on 0–100 would inherit it anyway).
1076
- Plan.series.heat(HeatRow, {
1077
- key: "depth", title: "Depth",
1078
- match: r => r.series.equal("depth"),
1079
- label: r => r.label, id: true,
1080
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 95 }),
1081
- children: r => r.children, aggregate: "mean",
1082
- scale: { min: 0, max: 100, warnAt: 95 },
1083
- }),
1084
- Plan.series.heat(HeatRow, {
1085
- key: "booked", title: "Booked",
1086
- match: r => r.series.equal("booked"),
1087
- label: r => r.label,
1088
- sub: r => r.sub,
1089
- cells: r => Plan.weightCells(r.weights),
1090
- }),
1091
- Plan.series.section(HeatRow, { key: "packing", title: "Packing", meta: "1 row" }, [
1092
- Plan.series.heat(HeatRow, {
1093
- key: "segments", title: "Segments",
1094
- match: r => r.series.equal("segments"),
1095
- label: r => r.label,
1096
- sub: r => r.sub,
1097
- cells: r => Plan.segmentCells(r.segs),
1098
- }),
1099
- ]),
1100
- ], ArrayType(Plan.Types.Series(HeatRow)));
1101
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1102
- return (_jsx(Plan, { axis: axis, data: lines, series: series }));
1103
- }),
1104
- inputs: [],
1105
- });
1106
- export const planTableRows = example({
1107
- keywords: ["Plan", "data", "series", "table", "cells", "tableCells", "subtotal", "aggregate", "sum", "format", "emphasis", "footer", "children", "nested", "recursive", "RecursiveType", "depth", "em-dash", "neg", "match", "gutter", "tableSeries", "split", "horizontal", "vertical", "multi-value", "multi-cell", "stacked", "two-line", "strong", "muted", "rollup", "mirror", "position", "raw"],
1108
- description: "Table rows over one order source — subtotals at every level of the data's nesting, footer emphasis, and every split × gutter combination",
1109
- fn: East.function([], UIComponentType, ($) => {
1110
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1111
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1112
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1113
- return w1.addWeeks(n.subtract(1n));
1114
- }));
1115
- const RawCell = StructType({ at: DateTimeType, value: OptionType(FloatType) });
1116
- // The RAW order record — actuals and the plan Δ as per-bucket value
1117
- // arrays; every display decision lives in the series configs. Orders
1118
- // nest (`children`): a top holds its programs, a program its orders —
1119
- // the hierarchy is the data's own (#822), to whatever depth it has.
1120
- const OrderRow = RecursiveType((self) => StructType({
1121
- series: StringType, name: StringType,
1122
- sub: OptionType(StringType),
1123
- act: ArrayType(RawCell),
1124
- plan: ArrayType(RawCell),
1125
- children: DictType(StringType, self),
1126
- }));
1127
- const noChildren = $.const(new Map(), DictType(StringType, OrderRow));
1128
- const act = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
1129
- at: week(i.add(27n)),
1130
- value: i.equal(9n).ifElse(() => none, () => some(i.toFloat().multiply(7.0).add(40.0))),
1131
- })));
1132
- const deltas = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
1133
- at: week(i.add(27n)),
1134
- value: i.remainder(4n).equal(3n).ifElse(() => none, () => some(i.toFloat().multiply(1.5).subtract(8.0))),
1135
- })));
1136
- const outflow = $.let(East.Array.generate(12n, RawCell, (_$, i) => ({
1137
- at: week(i.add(27n)),
1138
- value: some(i.toFloat().multiply(-3.0).subtract(12.0)),
1139
- })));
1140
- const orders = $.const(new Map([
1141
- // Two levels of nesting — a top holds its programs, a program its
1142
- // orders; every level with no values of its own is a subtotal.
1143
- ["despatches", { series: "orders", name: "Despatches", sub: none, act: [], plan: [],
1144
- children: new Map([
1145
- ["program-a", { series: "orders", name: "Program A", sub: none, act: [], plan: [],
1146
- children: new Map([
1147
- ["or-1188", { series: "orders", name: "OR-1188", sub: none, act, plan: [], children: noChildren }],
1148
- ["or-1204", { series: "orders", name: "OR-1204", sub: none, act, plan: [], children: noChildren }],
1149
- ]) }],
1150
- ["program-b", { series: "orders", name: "Program B", sub: none, act: [], plan: [],
1151
- children: new Map([
1152
- ["or-1219", { series: "orders", name: "OR-1219", sub: none, act, plan: [], children: noChildren }],
1153
- ]) }],
1154
- ]) }],
1155
- ["returns", { series: "orders", name: "Returns", sub: none, act: [], plan: [],
1156
- children: new Map([
1157
- ["program-b", { series: "orders", name: "Program B", sub: none, act: [], plan: [],
1158
- children: new Map([
1159
- ["rt-0031", { series: "orders", name: "RT-0031", sub: none, act, plan: [], children: noChildren }],
1160
- ]) }],
1161
- ]) }],
1162
- // Footer emphasis + negative tone + the muted em-dash.
1163
- ["net", { series: "net", name: "Net flow", sub: none, plan: [], children: noChildren,
1164
- act: [
1165
- { at: week(27n), value: some(22.0) }, { at: week(28n), value: some(-26.0) },
1166
- { at: week(29n), value: none },
1167
- ] }],
1168
- // Multi-value series — raw act + plan arrays per row. The SPLIT
1169
- // (how the positions sit against each other) and the GUTTER (one
1170
- // line or two) are independent choices, so all four combinations
1171
- // are here: the pair that reads well depends on the numbers, not
1172
- // on the split.
1173
- ["actplan", { series: "actplan", name: "Act · Δ plan", sub: some("t/wk"), act, plan: deltas, children: noChildren }],
1174
- ["inout", { series: "inout", name: "In / out", sub: none, act, plan: outflow, children: noChildren }],
1175
- // Horizontal, on a ONE-line gutter — the pair reads as a single
1176
- // fact ("booked beside free"), so a sub label would only repeat it.
1177
- ["sidebyside", { series: "sidebyside", name: "Booked · free", sub: none, act, plan: outflow, children: noChildren }],
1178
- // Vertical, on a TWO-line gutter — the stack needs the unit spelled
1179
- // out, because the positions are the same measure at two times.
1180
- ["overunder", { series: "overunder", name: "Act / plan", sub: some("t/wk"), act, plan: deltas, children: noChildren }],
1181
- // NESTED and multi-value: the subtotal parent mirrors its members,
1182
- // an act subtotal beside a Δ subtotal.
1183
- ["flows", { series: "flow", name: "Flows", sub: none, act: [], plan: [],
1184
- children: new Map([
1185
- ["fl-1", { series: "flow", name: "FL-2201", sub: none, act, plan: deltas, children: noChildren }],
1186
- ["fl-2", { series: "flow", name: "FL-2202", sub: none, act, plan: deltas, children: noChildren }],
1187
- ]) }],
1188
- // The same, STACKED: members and their subtotal both put the two
1189
- // positions on their own lines, so the parent has to grow too.
1190
- ["stacks", { series: "stack", name: "Stacks", sub: none, act: [], plan: [],
1191
- children: new Map([
1192
- ["st-1", { series: "stack", name: "ST-3301", sub: none, act, plan: outflow, children: noChildren }],
1193
- ["st-2", { series: "stack", name: "ST-3302", sub: none, act, plan: outflow, children: noChildren }],
1194
- ]) }],
1195
- ]), DictType(StringType, OrderRow));
1196
- const series = $.const([
1197
- // Each order nests under its program, each program under its top
1198
- // (`children`, to any depth), and every parent sums its children.
1199
- Plan.series.table(OrderRow, {
1200
- key: "orders", title: "Orders",
1201
- match: r => r.series.equal("orders"),
1202
- label: r => r.name,
1203
- cells: r => Plan.tableCells(r.act),
1204
- children: r => r.children, aggregate: "sum",
1205
- format: Format.Number({ maximumFractionDigits: 0n }),
1206
- }),
1207
- Plan.series.table(OrderRow, {
1208
- key: "net", title: "Net",
1209
- match: r => r.series.equal("net"),
1210
- label: r => r.name, emphasis: "footer",
1211
- cells: r => Plan.tableCells(r.act),
1212
- format: Format.Number({ maximumFractionDigits: 0n }),
1213
- }),
1214
- // Per-POSITION style declared ONCE, in the CONFIG — a strong
1215
- // rolled-up actual beside its muted, always-signed plan Δ.
1216
- Plan.series.table(OrderRow, {
1217
- key: "actplan", title: "Actual vs plan",
1218
- match: r => r.series.equal("actplan"),
1219
- label: r => r.name, stacked: true, sub: r => r.sub,
1220
- series: r => [
1221
- Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(r.act) }),
1222
- Plan.tableSeries({
1223
- tone: "muted",
1224
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1225
- cells: Plan.tableCells(r.plan),
1226
- }),
1227
- ],
1228
- format: Format.Number({ maximumFractionDigits: 0n }),
1229
- }),
1230
- // The VERTICAL split stacks the positions; the row grows.
1231
- Plan.series.table(OrderRow, {
1232
- key: "inout", title: "Inout",
1233
- match: r => r.series.equal("inout"),
1234
- label: r => r.name, split: "vertical",
1235
- series: r => [
1236
- Plan.tableSeries({ cells: Plan.tableCells(r.act) }),
1237
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
1238
- ],
1239
- format: Format.Number({ maximumFractionDigits: 0n }),
1240
- }),
1241
- // HORIZONTAL on a ONE-line gutter — the other half of the pair
1242
- // above: the split is a cell-layout choice and the gutter a label
1243
- // choice, so neither implies the other.
1244
- Plan.series.table(OrderRow, {
1245
- key: "sidebyside", title: "Side by side",
1246
- match: r => r.series.equal("sidebyside"),
1247
- label: r => r.name, split: "horizontal",
1248
- series: r => [
1249
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
1250
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
1251
- ],
1252
- format: Format.Number({ maximumFractionDigits: 0n }),
1253
- }),
1254
- // A MULTI-VALUE series under a subtotal parent — every position
1255
- // rolls up, so the parent shows an act subtotal beside a Δ subtotal
1256
- // instead of collapsing to one number and looking complete. Flag a
1257
- // position `rollup: true` to narrow it back to that one.
1258
- Plan.series.table(OrderRow, {
1259
- key: "flow", title: "Flow",
1260
- match: r => r.series.equal("flow"),
1261
- label: r => r.name,
1262
- series: r => [
1263
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
1264
- Plan.tableSeries({
1265
- tone: "muted",
1266
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1267
- cells: Plan.tableCells(r.plan),
1268
- }),
1269
- ],
1270
- children: r => r.children, aggregate: "sum",
1271
- format: Format.Number({ maximumFractionDigits: 0n }),
1272
- }),
1273
- // NESTED and VERTICAL — the subtotal stacks its positions the way
1274
- // its members do. The parent's positions carry no values of their
1275
- // own (they are derived), so its height comes from its members'
1276
- // count: a parent that read its own empty cells as one line would
1277
- // render as two.
1278
- Plan.series.table(OrderRow, {
1279
- key: "stack", title: "Stack",
1280
- match: r => r.series.equal("stack"),
1281
- label: r => r.name, split: "vertical",
1282
- series: r => [
1283
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
1284
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(r.plan) }),
1285
- ],
1286
- children: r => r.children, aggregate: "sum",
1287
- format: Format.Number({ maximumFractionDigits: 0n }),
1288
- }),
1289
- // VERTICAL on a TWO-line gutter — the remaining combination, and
1290
- // the one that grows the row in BOTH directions at once.
1291
- Plan.series.table(OrderRow, {
1292
- key: "overunder", title: "Over / under",
1293
- match: r => r.series.equal("overunder"),
1294
- label: r => r.name, split: "vertical", stacked: true, sub: r => r.sub,
1295
- series: r => [
1296
- Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(r.act) }),
1297
- Plan.tableSeries({
1298
- tone: "muted",
1299
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1300
- cells: Plan.tableCells(r.plan),
1301
- }),
1302
- ],
1303
- format: Format.Number({ maximumFractionDigits: 0n }),
1304
- }),
1305
- ], ArrayType(Plan.Types.Series(OrderRow)));
1306
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1307
- return (_jsx(Plan, { axis: axis, data: orders, series: series }));
1308
- }),
1309
- inputs: [],
1310
- });
1311
- // ============================================================================
1312
- // planFold — a coarser resolution FOLDS each bucket's values (#824)
1313
- // ============================================================================
1314
- /**
1315
- * Temporal fold (#824). The data is weekly; the canvas shows it at whatever
1316
- * resolution the axis states, and at MONTH every row shows ONE value per
1317
- * month — the fold of its weeks there — where it used to stack four or five
1318
- * on top of each other. Each cell builder and chart layer declares its fold,
1319
- * defaulting to what its values mean: a heat level and a line fold by `mean`,
1320
- * a table numeral and a column by `sum`, a weight fraction by `mean`. A row
1321
- * overrides it where the meaning differs — peak load by `max`, closing stock
1322
- * by `last`. The switch is the resolution; nothing in the data changes, and a
1323
- * row whose weeks were never folded (WEEK) shows them as they are.
1324
- */
1325
- export const planFold = example({
1326
- keywords: [
1327
- "Plan", "fold", "temporal fold", "resolution", "week", "month", "rebucket", "bucket",
1328
- "sum", "mean", "max", "last", "count", "default", "override", "heatCells", "weightCells",
1329
- "tableCells", "tableSeries", "layer", "Plan.layer", "column", "line", "format",
1330
- "Reactive", "State", "SegmentGroup", "#824",
1331
- ],
1332
- description: "Temporal fold — weekly data at MONTH resolution shows one folded cell per month per row: heat and lines by mean, tables and columns by sum, with per-row overrides (peak load by max, closing stock by last)",
1333
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
1334
- // Monday of ISO week n, 2026 — twelve weeks, W27–W38.
1335
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1336
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1337
- return w1.addWeeks(n.subtract(1n));
1338
- }));
1339
- const Weekly = StructType({ at: DateTimeType, value: OptionType(FloatType) });
1340
- const MeasureRow = StructType({ week: DateTimeType, v: FloatType });
1341
- // ONE flat weekly source; `series` picks the row's series.
1342
- const OpsRow = StructType({
1343
- series: StringType, label: StringType,
1344
- heat: ArrayType(Plan.Types.HeatCell),
1345
- weights: ArrayType(Plan.Types.WeightCell),
1346
- weekly: ArrayType(Weekly),
1347
- points: ArrayType(MeasureRow),
1348
- });
1349
- const noHeat = $.const([], ArrayType(Plan.Types.HeatCell));
1350
- const noWeights = $.const([], ArrayType(Plan.Types.WeightCell));
1351
- const noWeekly = $.const([], ArrayType(Weekly));
1352
- const noPoints = $.const([], ArrayType(MeasureRow));
1353
- // Twelve weeks of each measure, derived — never hand-written.
1354
- const load = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
1355
- at: Plan.at.time(week(i.add(27n))),
1356
- value: some(i.multiply(17n).remainder(50n).toFloat().add(45.0)),
1357
- label: none,
1358
- })));
1359
- const booked = $.let(East.Array.generate(12n, Plan.Types.WeightCell, (_$, i) => ({
1360
- at: Plan.at.time(week(i.add(27n))),
1361
- fraction: i.multiply(7n).remainder(10n).toFloat().multiply(0.06).add(0.4),
1362
- planned: i.greaterEqual(5n),
1363
- })));
1364
- const tonnes = $.let(East.Array.generate(12n, Weekly, (_$, i) => ({
1365
- at: week(i.add(27n)), value: some(i.multiply(29n).remainder(60n).toFloat().add(80.0)),
1366
- })));
1367
- const stock = $.let(East.Array.generate(12n, Weekly, (_$, i) => ({
1368
- at: week(i.add(27n)), value: some(i.toFloat().multiply(-22.0).add(420.0)),
1369
- })));
1370
- const output = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
1371
- week: week(i.add(27n)), v: i.multiply(23n).remainder(40n).toFloat().add(60.0),
1372
- })));
1373
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
1374
- week: week(i.add(27n)), v: i.multiply(13n).remainder(9n).toFloat().add(88.0),
1375
- })));
1376
- const base = { heat: noHeat, weights: noWeights, weekly: noWeekly, points: noPoints };
1377
- const ops = $.const(new Map([
1378
- ["load", { ...base, series: "load", label: "Line load %", heat: load }],
1379
- ["peak", { ...base, series: "peak", label: "Peak load %", heat: load }],
1380
- ["book", { ...base, series: "book", label: "Booked", weights: booked }],
1381
- ["desp", { ...base, series: "desp", label: "Despatch t", weekly: tonnes }],
1382
- ["stock", { ...base, series: "stock", label: "Closing stock t", weekly: stock }],
1383
- ["out", { ...base, series: "out", label: "Output · t", points: output }],
1384
- ["cov", { ...base, series: "cov", label: "Coverage %", points: coverage }],
1385
- ]), DictType(StringType, OpsRow));
1386
- const whole = $.const(Format.Number({ maximumFractionDigits: 0n }));
1387
- const series = $.const([
1388
- // A level — a month shows its weeks' MEAN (the default). The
1389
- // declared format prints the folded values.
1390
- Plan.series.heat(OpsRow, {
1391
- key: "load", title: "Line load",
1392
- match: r => r.series.equal("load"), label: r => r.label,
1393
- cells: r => Plan.heatCells(r.heat, { min: 0, max: 100, format: whole }),
1394
- }),
1395
- // The same weeks, folded by their MAX — the peak a month hit.
1396
- Plan.series.heat(OpsRow, {
1397
- key: "peak", title: "Peak load",
1398
- match: r => r.series.equal("peak"), label: r => r.label,
1399
- cells: r => Plan.heatCells(r.heat, { min: 0, max: 100, fold: "max", format: whole }),
1400
- }),
1401
- // A fraction of each bucket booked — a month's is its weeks' mean.
1402
- Plan.series.heat(OpsRow, {
1403
- key: "book", title: "Booked",
1404
- match: r => r.series.equal("book"), label: r => r.label,
1405
- cells: r => Plan.weightCells(r.weights),
1406
- }),
1407
- // An amount — a month shows its weeks' SUM (the default).
1408
- Plan.series.table(OpsRow, {
1409
- key: "desp", title: "Despatch",
1410
- match: r => r.series.equal("desp"), label: r => r.label,
1411
- cells: r => Plan.tableCells(r.weekly), format: whole,
1412
- }),
1413
- // A balance — a month shows where it CLOSED, its last week.
1414
- Plan.series.table(OpsRow, {
1415
- key: "stock", title: "Closing stock",
1416
- match: r => r.series.equal("stock"), label: r => r.label,
1417
- cells: r => Plan.tableCells(r.weekly), fold: "last", format: whole,
1418
- }),
1419
- // Columns sum; a line averages.
1420
- Plan.series.chart(OpsRow, {
1421
- key: "out", title: "Output",
1422
- match: r => r.series.equal("out"), label: r => r.label, id: true,
1423
- layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.v })],
1424
- }),
1425
- Plan.series.chart(OpsRow, {
1426
- key: "cov", title: "Coverage",
1427
- match: r => r.series.equal("cov"), label: r => r.label, id: true,
1428
- layers: r => [Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.v }), { fold: "mean" })],
1429
- }),
1430
- ], ArrayType(Plan.Types.Series(OpsRow)));
1431
- // The resolution is the switch — two whole axis values, picked by key.
1432
- const axes = $.const([
1433
- { key: "month", axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "month", now: week(31n) }) },
1434
- { key: "week", axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }) },
1435
- ], ArrayType(StructType({ key: StringType, axis: Plan.Types.Axis })));
1436
- const resBind = $.let(State.bind([StringType], "plan_fold_resolution", "month"));
1437
- const resKey = $.let(resBind.read());
1438
- const onRes = $.const(East.function([StringType], NullType, ($, next) => { $(resBind.write(next)); }));
1439
- const sel = $.let(axes.filter((_$, a) => a.key.equal(resKey)).get(0n, _$ => axes.get(0n)));
1440
- return (_jsxs(VStack, { gap: "2", align: "stretch", children: [_jsx(SegmentGroup, { value: resKey, onChange: onRes, size: "sm", items: [SegmentGroup.Item("month", _jsx(Text, { children: "MONTH" })), SegmentGroup.Item("week", _jsx(Text, { children: "WEEK" }))] }), _jsx(Plan, { axis: sel.axis, data: ops, series: series })] }));
1441
- } }))),
1442
- inputs: [],
1443
- });
1444
- export const planCardRows = example({
1445
- keywords: ["Plan", "data", "series", "cards", "Roster", "chip", "lifecycle", "confirmed", "recommended", "removed", "estimated", "icon", "popover", "stacked", "format", "axis", "section", "match", "gutter", "raw"],
1446
- description: "Cards rows over one crew source — chip labels derived from hours × lifecycle, plus stored chip records",
1447
- fn: East.function([], UIComponentType, ($) => {
1448
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1449
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1450
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1451
- return w1.addWeeks(n.subtract(1n));
1452
- }));
1453
- const ShiftRow = StructType({
1454
- key: StringType, from: DateTimeType, to: DateTimeType,
1455
- hours: FloatType, state: EventStateType,
1456
- });
1457
- const CrewRow = StructType({
1458
- series: StringType, name: StringType,
1459
- sub: OptionType(StringType), value: OptionType(StringType),
1460
- shifts: ArrayType(ShiftRow),
1461
- chips: ArrayType(Plan.Types.Chip),
1462
- });
1463
- const crews = $.const(new Map([
1464
- // RAW shifts — hours + lifecycle; chip labels derive client-side.
1465
- ["crewA", { series: "main", name: "Crew A", sub: some("152h → 168h"), value: none, chips: [], shifts: [
1466
- { key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
1467
- { key: "s2", from: week(29n), to: week(31n), hours: 56.0, state: variant("proposed", variant("removed", null)) },
1468
- { key: "s3", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
1469
- { key: "s4", from: week(34n), to: week(35n), hours: 48.0, state: variant("estimated", null) },
1470
- ] }],
1471
- // STORED vocabulary — plain chip records (the §3.2 element
1472
- // shapes), here carrying the shift-type icon.
1473
- ["crewB", { series: "pool", name: "Crew B", sub: none, value: some("128h"), shifts: [], chips: [
1474
- { key: "b1", from: Plan.at.time(week(28n)), to: Plan.at.time(week(31n)), label: "96h", state: variant("confirmed", null),
1475
- icon: some({ prefix: "fas", name: "user-group", label: none, style: none }) },
1476
- { key: "b2", from: Plan.at.time(week(33n)), to: Plan.at.time(week(36n)), label: "+32h", state: variant("proposed", variant("recommended", null)), icon: none },
1477
- ] }],
1478
- ]), DictType(StringType, CrewRow));
1479
- const series = $.const([
1480
- Plan.series.cards(CrewRow, {
1481
- key: "main", title: "Main",
1482
- match: r => r.series.equal("main"),
1483
- label: r => r.name, stacked: true,
1484
- sub: r => r.sub,
1485
- chips: r => r.shifts.map(($, s) => {
1486
- const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
1487
- // The `+` means ADDED hours, so it rides the proposal's
1488
- // flavour, not the mere fact of being a proposal — a
1489
- // `removed` shift is a proposal too, and prefixing it `+`
1490
- // would read as the opposite of what it does.
1491
- const label = $.let(s.state.match({
1492
- proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
1493
- }, _$ => East.str `${hrs}h`), StringType);
1494
- return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
1495
- }),
1496
- }),
1497
- Plan.series.section(CrewRow, { key: "relief", title: "Relief pool", meta: "1 row" }, [
1498
- Plan.series.cards(CrewRow, {
1499
- key: "pool", title: "Pool",
1500
- match: r => r.series.equal("pool"),
1501
- label: r => r.name,
1502
- value: r => r.value,
1503
- chips: r => r.chips,
1504
- }),
1505
- ]),
1506
- ], ArrayType(Plan.Types.Series(CrewRow)));
1507
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n), format: "D MMM" }));
1508
- // The generalized popover resolver — chips ride the chip arm.
1509
- const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
1510
- const noBody = $.const(none, OptionType(UIComponentType));
1511
- return ref.match({
1512
- chip: (_$, ev) => ev.chip.equal("s3").ifElse(() => some(_jsx(Text, { children: "Overtime proposal." })), () => noBody),
1513
- }, _$ => noBody);
1514
- }));
1515
- return (_jsx(Plan, { popover: popover, axis: axis, data: crews, series: series }));
1516
- }),
1517
- inputs: [],
1518
- });
1519
- export const planEventRows = example({
1520
- keywords: ["Plan", "data", "series", "events", "mark", "milestone", "decision", "exception", "markKind", "applied", "icon", "label", "popover", "section", "match", "stacked", "gutter", "raw"],
1521
- description: "Event rows over one stream source — milestone dots, decision diamonds, an exception, and a custom glyph",
1522
- fn: East.function([], UIComponentType, ($) => {
1523
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1524
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1525
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1526
- return w1.addWeeks(n.subtract(1n));
1527
- }));
1528
- const StreamRow = StructType({
1529
- series: StringType, name: StringType,
1530
- sub: OptionType(StringType), value: OptionType(StringType),
1531
- marks: ArrayType(Plan.Types.EventMark),
1532
- });
1533
- const streams = $.const(new Map([
1534
- ["ms", { series: "main", name: "Milestones", sub: none, value: some("5"), marks: [
1535
- { key: "kick", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
1536
- { key: "d1", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
1537
- { key: "rel", at: Plan.at.time(week(33n)), kind: variant("milestone", null),
1538
- icon: some({ prefix: "fas", name: "rocket", label: none, style: none }), label: some("REL 2.4") },
1539
- { key: "audit", at: Plan.at.time(week(35n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
1540
- { key: "d2", at: Plan.at.time(week(37n)), kind: variant("decision", { applied: false }), icon: none, label: some("×3") },
1541
- ] }],
1542
- ["release", { series: "programs", name: "Releases", sub: some("6-wk cadence"), value: none, marks: [
1543
- { key: "r1", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("2.3") },
1544
- { key: "r2", at: Plan.at.time(week(36n)), kind: variant("milestone", null), icon: none, label: some("2.4") },
1545
- ] }],
1546
- ]), DictType(StringType, StreamRow));
1547
- const series = $.const([
1548
- Plan.series.events(StreamRow, {
1549
- key: "milestones", title: "Milestones",
1550
- match: r => r.series.equal("main"),
1551
- label: r => r.name, id: true,
1552
- value: r => r.value,
1553
- marks: r => r.marks,
1554
- }),
1555
- Plan.series.section(StreamRow, { key: "programs", title: "Programs", meta: "1 row" }, [
1556
- Plan.series.events(StreamRow, {
1557
- key: "releases", title: "Releases",
1558
- match: r => r.series.equal("programs"),
1559
- label: r => r.name, id: true, stacked: true,
1560
- sub: r => r.sub,
1561
- marks: r => r.marks,
1562
- }),
1563
- ]),
1564
- ], ArrayType(Plan.Types.Series(StreamRow)));
1565
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1566
- // The generalized popover resolver — event marks ride the mark arm.
1567
- const popover = $.const(East.function([Plan.Types.ElementRef], OptionType(UIComponentType), ($, ref) => {
1568
- const noBody = $.const(none, OptionType(UIComponentType));
1569
- return ref.match({
1570
- mark: (_$, ev) => ev.mark.equal("rel").ifElse(() => some(_jsx(Text, { children: "Go/no-go review." })), () => noBody),
1571
- }, _$ => noBody);
1572
- }));
1573
- return (_jsx(Plan, { popover: popover, axis: axis, data: streams, series: series }));
1574
- }),
1575
- inputs: [],
1576
- });
1577
- export const planGroupedRows = example({
1578
- keywords: ["Plan", "data", "series", "group", "groups", "strip", "summary", "summaryAggregate", "collapsed", "groupToDicts", "grouping", "data step", "children", "Plan.children", "step down", "member count", "heterogeneous", "match", "nesting", "raw"],
1579
- description: "Group strips over grouped data — one `groupToDicts` makes each line an entry holding its rows, and each line's strip nests them, collapsed strips resting as their mean",
1580
- fn: East.function([], UIComponentType, ($) => {
1581
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1582
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1583
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1584
- return w1.addWeeks(n.subtract(1n));
1585
- }));
1586
- const JobRow = StructType({
1587
- key: StringType, batch: StringType,
1588
- start: DateTimeType, end: DateTimeType, state: EventStateType,
1589
- });
1590
- // The RAW rows, flat — each names its line, and carries jobs, load
1591
- // cells, or both.
1592
- const LineRow = StructType({
1593
- line: StringType, label: StringType,
1594
- jobs: ArrayType(JobRow),
1595
- cells: ArrayType(Plan.Types.HeatCell),
1596
- });
1597
- const pcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
1598
- const cells = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
1599
- at: Plan.at.time(week(i.add(27n))),
1600
- value: some(pcts.get(i)),
1601
- label: some(East.Float.printFixed(pcts.get(i), 0n)),
1602
- })));
1603
- const rows = $.const(new Map([
1604
- // Line 1 — mixed kinds: a machine's jobs and its load.
1605
- ["m03", { line: "Line 1", label: "L1-M03",
1606
- jobs: [{ key: "r", batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }],
1607
- cells: [] }],
1608
- ["m03h", { line: "Line 1", label: "L1-M03 load", jobs: [], cells }],
1609
- ["l2", { line: "Line 2", label: "L2 load", jobs: [], cells }],
1610
- ["m21", { line: "Line 3", label: "L3-M21", jobs: [], cells }],
1611
- ["m22", { line: "Line 3", label: "L3-M22", jobs: [], cells }],
1612
- ["m31", { line: "Line 4", label: "L4-M31", jobs: [], cells }],
1613
- ]), DictType(StringType, LineRow));
1614
- // Grouping is a DATA step (#822): one `groupToDicts` makes each line an
1615
- // entry holding its rows, keyed as the source keys them. A strip nests
1616
- // exactly what its entry holds, so it reads the same inline or paged.
1617
- const lines = $.let(rows.groupToDicts(($, r) => r.line, ($, _r, k) => k));
1618
- const LineGroup = DictType(StringType, LineRow);
1619
- const series = $.const([
1620
- // One strip PER LINE, its members the line's rows — stepped down
1621
- // into (`Plan.children`) and laid out like a top-level list: the
1622
- // jobs block, then the load block. Line 1 rests open; the others
1623
- // rest as their DECLARED mean strip, wearing their member count.
1624
- Plan.series.group(LineGroup, {
1625
- key: "lines", title: "Lines",
1626
- label: (_g, line) => line,
1627
- collapsed: (_g, line) => line.equal("Line 1").not(),
1628
- summaryAggregate: "mean",
1629
- children: Plan.children((g) => g, [
1630
- Plan.series.span(LineRow, {
1631
- key: "line-jobs", title: "Jobs",
1632
- match: r => r.jobs.size().greater(0n),
1633
- label: r => r.label, id: true,
1634
- runs: r => r.jobs.map((_$, j) => Plan.run({
1635
- key: j.key, start: j.start, end: j.end,
1636
- label: East.str `RUN · ${j.batch}`, state: j.state,
1637
- })),
1638
- }),
1639
- Plan.series.heat(LineRow, {
1640
- key: "line-load", title: "Load",
1641
- match: r => r.cells.size().greater(0n),
1642
- label: r => r.label,
1643
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
1644
- }),
1645
- ]),
1646
- }),
1647
- ], ArrayType(Plan.Types.Series(LineGroup)));
1648
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1649
- return (_jsx(Plan, { axis: axis, data: lines, series: series }));
1650
- }),
1651
- inputs: [],
1652
- });
1653
- // ============================================================================
1654
- // planSeriesData — the minimal data + series introduction
1655
- // ============================================================================
1656
- export const planSeriesData = example({
1657
- keywords: ["Plan", "data", "series", "match", "variant", "span", "cards", "group", "rows", "groupToDicts", "grouping", "data step", "children", "Plan.children", "step down", "nesting", "rollup", "Series", "data-driven", "accessor", "raw", "one source", "layout"],
1658
- description: "The data + series canvas, minimally — one raw source grouped into blocks in one data step, and one `Plan.series.*` entry per block, the list the layout",
1659
- fn: East.function([], UIComponentType, ($) => {
1660
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1661
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1662
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1663
- return w1.addWeeks(n.subtract(1n));
1664
- }));
1665
- // The RAW domain shape — series discriminated by a variant field
1666
- // (the natural ops-dataset form; the same rows page from a dataset).
1667
- const JobRow = StructType({
1668
- batch: StringType, start: DateTimeType, end: DateTimeType,
1669
- tonnes: FloatType, state: EventStateType,
1670
- });
1671
- const ShiftRow = StructType({
1672
- key: StringType, from: DateTimeType, to: DateTimeType,
1673
- hours: FloatType, state: EventStateType,
1674
- });
1675
- const OpsRow = StructType({
1676
- line: StringType,
1677
- kind: VariantType({
1678
- machine: StructType({ jobs: ArrayType(JobRow) }),
1679
- crew: StructType({ shifts: ArrayType(ShiftRow) }),
1680
- }),
1681
- });
1682
- const ops = $.const(new Map([
1683
- ["L1-M03", { line: "Line 1", kind: variant("machine", { jobs: [
1684
- { batch: "B-214", start: week(28n), end: week(31n), tonnes: 96.0, state: variant("in-progress", null) },
1685
- { batch: "B-221", start: week(32n), end: week(35n), tonnes: 88.0, state: variant("proposed", variant("recommended", null)) },
1686
- ] }) }],
1687
- ["L1-M04", { line: "Line 1", kind: variant("machine", { jobs: [
1688
- { batch: "B-208", start: week(27n), end: week(30n), tonnes: 112.0, state: variant("actual", null) },
1689
- ] }) }],
1690
- ["L2-M11", { line: "Line 2", kind: variant("machine", { jobs: [
1691
- { batch: "B-241", start: week(29n), end: week(33n), tonnes: 92.0, state: variant("confirmed", null) },
1692
- ] }) }],
1693
- ["crewA", { line: "Line 1", kind: variant("crew", { shifts: [
1694
- { key: "s1", from: week(27n), to: week(29n), hours: 80.0, state: variant("confirmed", null) },
1695
- { key: "s2", from: week(31n), to: week(33n), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
1696
- ] }) }],
1697
- ]), DictType(StringType, OpsRow));
1698
- // Hierarchy is the DATA's (#822): one `groupToDicts` groups the rows
1699
- // into the canvas's blocks — each machine under its line, the crews
1700
- // under one "Crews" block. An entry of the result holds its rows.
1701
- const blocks = $.let(ops.groupToDicts(($, r) => r.kind.hasTag("crew").ifElse(() => "Crews", () => r.line), ($, _r, k) => k));
1702
- const Block = DictType(StringType, OpsRow);
1703
- // The series — real East values bound in the body, typed by the
1704
- // constructor. The list IS the layout: one block per series, top to
1705
- // bottom. The accessors are where raw fields become canvas vocabulary:
1706
- // labels, quantity displays and chip text all derive CLIENT-SIDE,
1707
- // inside each series' `derive`.
1708
- const series = $.const([
1709
- // One row per line, its machines stepped down into
1710
- // (`Plan.children`) and their runs rolled up into its bands —
1711
- // which sum the runs' quantities, unit by unit.
1712
- Plan.series.span(Block, {
1713
- key: "lines", title: "Lines",
1714
- match: (_b, name) => name.equal("Crews").not(),
1715
- label: (_b, name) => name,
1716
- runs: _b => [],
1717
- rollup: "union",
1718
- children: Plan.children((b) => b, [
1719
- Plan.series.span(OpsRow, {
1720
- key: "machines", title: "Machines",
1721
- match: r => r.kind.hasTag("machine"),
1722
- label: (_r, k) => k, id: true,
1723
- runs: r => r.kind.unwrap("machine").jobs.map((_$, j) => Plan.run({
1724
- key: j.batch, start: j.start, end: j.end,
1725
- label: East.str `RUN · ${j.batch}`,
1726
- // A quantity is one value: the bar prints `96 t`,
1727
- // and the line's band sums the tonnes.
1728
- quantity: Plan.quantity(j.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
1729
- state: j.state,
1730
- })),
1731
- }),
1732
- ]),
1733
- }),
1734
- // One strip per matching block — here the one "Crews" block,
1735
- // wearing its member count.
1736
- Plan.series.group(Block, {
1737
- key: "crews", title: "Crews",
1738
- match: (_b, name) => name.equal("Crews"),
1739
- label: (_b, name) => name,
1740
- children: Plan.children((b) => b, [
1741
- Plan.series.cards(OpsRow, {
1742
- key: "crew-shifts", title: "Crew shifts",
1743
- match: r => r.kind.hasTag("crew"),
1744
- label: (_r, k) => k,
1745
- chips: r => r.kind.unwrap("crew").shifts.map(($, s) => {
1746
- const hrs = $.let(East.Float.printFixed(s.hours, 0n), StringType);
1747
- // `+` marks ADDED hours — a removed proposal keeps the
1748
- // plain figure (see planCardRows for the full ladder).
1749
- const label = $.let(s.state.match({
1750
- proposed: (_$, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
1751
- }, _$ => East.str `${hrs}h`), StringType);
1752
- return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
1753
- }),
1754
- }),
1755
- ]),
1756
- }),
1757
- Plan.series.rows(Block, { key: "chrome", title: "Milestones", subtitle: "one-off chrome" }, [Plan.events({ key: "ms", label: "Milestones", id: true, marks: [
1758
- Plan.mark({ key: "kick", at: week(28n), kind: "milestone", label: "KICKOFF" }),
1759
- Plan.mark({ key: "rel", at: week(33n), kind: "milestone", label: "REL 2.4" }),
1760
- ] })]),
1761
- ], ArrayType(Plan.Types.Series(Block)));
1762
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1763
- return (_jsx(Plan, { axis: axis, data: blocks, series: series }));
1764
- }),
1765
- inputs: [],
1766
- });
1767
- // ============================================================================
1768
- // planLiteralRows — the kind factories: rows no dataset holds
1769
- // ============================================================================
1770
- export const planLiteralRows = example({
1771
- keywords: [
1772
- "Plan", "span", "Plan.span", "literal", "rows", "series.rows", "chrome", "one-off",
1773
- "kind factory", "subtree", "nested", "parent", "rollup", "union", "bands", "run", "layout",
1774
- ],
1775
- description: "Literal rows — `Plan.span` builds a one-off subtree (a shutdown parent rolling up two trades' runs) that `Plan.series.rows` places beside the data-driven series",
1776
- fn: East.function([], UIComponentType, ($) => {
1777
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1778
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1779
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1780
- return w1.addWeeks(n.subtract(1n));
1781
- }));
1782
- const JobRow = StructType({
1783
- batch: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
1784
- });
1785
- const MachineRow = StructType({ jobs: ArrayType(JobRow) });
1786
- const machines = $.const(new Map([
1787
- ["L1-M03", { jobs: [{ batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
1788
- ["L1-M04", { jobs: [{ batch: "B-208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
1789
- ]), DictType(StringType, MachineRow));
1790
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1791
- return (_jsx(Plan, { axis: axis, data: machines, series: [
1792
- Plan.series.span(MachineRow, {
1793
- key: "machines", title: "Machines",
1794
- label: (_r, k) => k, id: true,
1795
- runs: r => r.jobs.map((_$, j) => Plan.run({
1796
- key: j.batch, start: j.start, end: j.end,
1797
- label: East.str `RUN · ${j.batch}`, state: j.state,
1798
- })),
1799
- }),
1800
- // Rows no dataset holds — the planned shutdown, written out once.
1801
- // `Plan.span` nests: the parent DECLARES its rollup and the canvas
1802
- // derives the band from its two rows' runs. The series list is the
1803
- // layout, so this block sits below the machines.
1804
- Plan.series.rows(MachineRow, { key: "works", title: "Planned works", subtitle: "literal rows" }, [
1805
- Plan.span({
1806
- key: "shutdown", label: "Shutdown", rollup: "union", rows: [
1807
- Plan.span({ key: "elec", label: "Electrical", runs: [
1808
- Plan.run({ key: "iso", start: week(33n), end: week(34n), label: "ISOLATE", state: "confirmed" }),
1809
- ] }),
1810
- Plan.span({ key: "mech", label: "Mechanical", runs: [
1811
- Plan.run({ key: "reline", start: week(34n), end: week(36n), label: "RELINE", state: "recommended" }),
1812
- ] }),
1813
- ],
1814
- }),
1815
- ]),
1816
- ] }));
1817
- }),
1818
- inputs: [],
1819
- });
1820
- // ============================================================================
1821
- // planPick — the series library, minimally (#590)
1822
- // ============================================================================
1823
- export const planPick = example({
1824
- keywords: [
1825
- "Plan", "pick", "Plan.pick", "Pick", "library", "panel", "series", "hidden",
1826
- "toggle", "eye", "show", "hide", "choose", "persisted", "Reactive", "State", "#590",
1827
- ],
1828
- description: "The series library, minimally — `Plan.pick` binds which series show, and `<Plan pick>` mounts the library beside the canvas",
1829
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
1830
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1831
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1832
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1833
- return w1.addWeeks(n.subtract(1n));
1834
- }));
1835
- const JobRow = StructType({
1836
- batch: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
1837
- });
1838
- const OpsRow = StructType({
1839
- series: StringType, jobs: ArrayType(JobRow), cells: ArrayType(Plan.Types.HeatCell),
1840
- });
1841
- const noJobs = $.const([], ArrayType(JobRow));
1842
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
1843
- const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
1844
- const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
1845
- at: Plan.at.time(week(i.multiply(2n).add(27n))), value: some(pcts.get(i)), label: none,
1846
- })));
1847
- const ops = $.const(new Map([
1848
- ["L1-M03", { series: "machines", cells: noCells,
1849
- jobs: [{ batch: "B-214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
1850
- ["L1-M04", { series: "machines", cells: noCells,
1851
- jobs: [{ batch: "B-208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
1852
- ["L2-load", { series: "load", cells, jobs: noJobs }],
1853
- ]), DictType(StringType, OpsRow));
1854
- // Every series that COULD show — the library lists these, and the
1855
- // canvas shows the ones switched on in this order.
1856
- const all = $.const([
1857
- Plan.series.span(OpsRow, {
1858
- key: "machines", title: "Machine jobs", subtitle: "one row per machine",
1859
- match: r => r.series.equal("machines"),
1860
- label: (_r, k) => k, id: true,
1861
- runs: r => r.jobs.map((_$, j) => Plan.run({
1862
- key: j.batch, start: j.start, end: j.end,
1863
- label: East.str `RUN · ${j.batch}`, state: j.state,
1864
- })),
1865
- }),
1866
- Plan.series.heat(OpsRow, {
1867
- key: "load", title: "Line load", subtitle: "% per fortnight",
1868
- match: r => r.series.equal("load"),
1869
- label: (_r, k) => k,
1870
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
1871
- }),
1872
- ], ArrayType(Plan.Types.Series(OpsRow)));
1873
- // The handle is STATE — which series are switched off, persisted
1874
- // under its key — so it lives inside the Reactive. "load" starts off.
1875
- const shown = $.let(Plan.pick("ex.plan.pick", all, { hidden: ["load"] }));
1876
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
1877
- // `pick` REPLACES `series`: the canvas shows the picked series and
1878
- // mounts the library itself, so nothing else is wired.
1879
- return (_jsx(Plan, { axis: axis, data: ops, pick: shown }));
1880
- } }))),
1881
- inputs: [],
1882
- });
1883
- // ============================================================================
1884
- // planLibraryDnd — the pickable series library over every row kind
1885
- // ============================================================================
1886
- export const planLibraryDnd = example({
1887
- keywords: [
1888
- "Plan", "data", "series", "library", "Pick", "pick", "Panel", "pickItems", "hidden",
1889
- "toggle", "eye", "kind icon", "group", "section", "views", "nested", "children", "rows",
1890
- "chrome", "span", "buckets", "chart", "heat", "table", "cards", "events", "duplicate",
1891
- "same entity", "multiple views", "adjacent", "seek", "layout", "order",
1892
- "Reactive", "State", "#590",
1893
- ],
1894
- description: "The series library across every row kind — duplicate kinds, sections, a group per entry, and a `views` series showing one asset three ways",
1895
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
1896
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
1897
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
1898
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1899
- return w1.addWeeks(n.subtract(1n));
1900
- }));
1901
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
1902
- const JobRow = StructType({
1903
- key: StringType, label: StringType,
1904
- start: DateTimeType, end: DateTimeType, state: EventStateType,
1905
- });
1906
- const ShiftRow = StructType({
1907
- key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
1908
- });
1909
- const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
1910
- // ONE flat source (the `planExpand` shape): `pick` names the series
1911
- // that claims the row, and every other channel is empty for the
1912
- // series that do not use it. A program holds its members
1913
- // (`members`), so the entry type is recursive.
1914
- const OpsRow = RecursiveType((self) => StructType({
1915
- pick: StringType, label: StringType,
1916
- jobs: ArrayType(JobRow),
1917
- points: ArrayType(MeasureRow),
1918
- cells: ArrayType(Plan.Types.HeatCell),
1919
- allocs: ArrayType(AllocRow),
1920
- nums: ArrayType(Plan.Types.TableCell),
1921
- shifts: ArrayType(ShiftRow),
1922
- marks: ArrayType(Plan.Types.EventMark),
1923
- members: DictType(StringType, self),
1924
- }));
1925
- const noJobs = $.const([], ArrayType(JobRow));
1926
- const noPoints = $.const([], ArrayType(MeasureRow));
1927
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
1928
- const noAllocs = $.const([], ArrayType(AllocRow));
1929
- const noNums = $.const([], ArrayType(Plan.Types.TableCell));
1930
- const noShifts = $.const([], ArrayType(ShiftRow));
1931
- const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
1932
- const noMembers = $.const(new Map(), DictType(StringType, OpsRow));
1933
- const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
1934
- const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
1935
- const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
1936
- at: Plan.at.time(week(i.multiply(2n).add(27n))),
1937
- value: some(pcts.get(i)),
1938
- label: some(East.Float.printFixed(pcts.get(i), 0n)),
1939
- })));
1940
- // The lifecycle states as consts, so each tag stays a LITERAL —
1941
- // `variant(someString, null)` widens to `variant<string, null>` and
1942
- // stops satisfying `EventStateType`.
1943
- const ACTUAL = variant("actual", null);
1944
- const CONFIRMED = variant("confirmed", null);
1945
- const RUNNING = variant("in-progress", null);
1946
- const PROPOSED = variant("proposed", variant("recommended", null));
1947
- const base = {
1948
- jobs: noJobs, points: noPoints, cells: noCells,
1949
- allocs: noAllocs, nums: noNums, shifts: noShifts, marks: noMarks, members: noMembers,
1950
- };
1951
- // The keys are the entries' identities; the LAYOUT is the series
1952
- // list below (#822).
1953
- const ops = $.const(new Map([
1954
- ["util", { ...base, pick: "util", label: "Util %", points }],
1955
- // ONE asset, THREE views — the `views` series below gives each
1956
- // machine a jobs row, a utilisation chart and a tonnes table,
1957
- // from these channels; nothing about the row is duplicated.
1958
- ["m03", { ...base, pick: "machines", label: "L1-M03",
1959
- jobs: [{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: RUNNING }],
1960
- points,
1961
- nums: [
1962
- { at: Plan.at.time(week(28n)), value: some(96.0), text: none, tone: none },
1963
- { at: Plan.at.time(week(31n)), value: some(88.0), text: none, tone: none },
1964
- ] }],
1965
- ["m04", { ...base, pick: "machines", label: "L1-M04",
1966
- jobs: [{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: ACTUAL }],
1967
- points,
1968
- nums: [
1969
- { at: Plan.at.time(week(28n)), value: some(112.0), text: none, tone: none },
1970
- { at: Plan.at.time(week(31n)), value: some(-24.0), text: none, tone: none },
1971
- ] }],
1972
- // SAME KIND as the machines' jobs, different entry — a kind is
1973
- // not an identity, which is why the library keys on `key`.
1974
- ["c01", { ...base, pick: "contract", label: "CON-01",
1975
- jobs: [{ key: "c1", label: "RUN · C-1", start: week(30n), end: week(34n), state: CONFIRMED }] }],
1976
- ["load", { ...base, pick: "load", label: "L2 load", cells }],
1977
- ["qual", { ...base, pick: "quality", label: "Quality", cells }],
1978
- ["dock2", { ...base, pick: "docks", label: "Dock 2",
1979
- allocs: [
1980
- { key: "a1", at: week(28n), state: variant("confirmed", null) },
1981
- { key: "a2", at: week(31n), state: variant("proposed", variant("recommended", null)) },
1982
- ] }],
1983
- ["desp", { ...base, pick: "table", label: "Despatch t",
1984
- nums: [
1985
- { at: Plan.at.time(week(28n)), value: some(128.0), text: none, tone: none },
1986
- { at: Plan.at.time(week(30n)), value: some(-96.0), text: none, tone: none },
1987
- ] }],
1988
- ["crewA", { ...base, pick: "cards", label: "Crew A",
1989
- shifts: [
1990
- { key: "s1", from: week(27n), to: week(29n), label: "80h", state: variant("confirmed", null) },
1991
- { key: "s2", from: week(31n), to: week(33n), label: "+64h", state: variant("proposed", variant("recommended", null)) },
1992
- ] }],
1993
- ["ms", { ...base, pick: "events", label: "Milestones",
1994
- marks: [
1995
- { key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
1996
- { key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
1997
- ] }],
1998
- // Members of the three sections — each section lays its series
1999
- // out under its header.
2000
- ["m11", { ...base, pick: "gspan", label: "L3-M11",
2001
- jobs: [{ key: "b301", label: "RUN · B-301", start: week(29n), end: week(33n), state: CONFIRMED }] }],
2002
- ["m12", { ...base, pick: "gspan", label: "L3-M12",
2003
- jobs: [{ key: "b302", label: "RUN · B-302", start: week(31n), end: week(36n), state: ACTUAL }] }],
2004
- ["l4", { ...base, pick: "gheat", label: "L4 load", cells }],
2005
- ["d5", { ...base, pick: "gbuckets", label: "Dock 5",
2006
- allocs: [{ key: "a3", at: week(33n), state: variant("confirmed", null) }] }],
2007
- // Two PROGRAMS, each holding its runs — one strip per program,
2008
- // its members nested in it.
2009
- ["Program A", { ...base, pick: "programs", label: "Program A",
2010
- members: new Map([
2011
- ["p1", { ...base, pick: "programs", label: "PR-A1",
2012
- jobs: [{ key: "p1", label: "RUN · P-1", start: week(28n), end: week(32n), state: CONFIRMED }] }],
2013
- ["p2", { ...base, pick: "programs", label: "PR-A2",
2014
- jobs: [{ key: "p2", label: "RUN · P-2", start: week(33n), end: week(37n), state: PROPOSED }] }],
2015
- ]) }],
2016
- ["Program B", { ...base, pick: "programs", label: "Program B",
2017
- members: new Map([
2018
- ["p3", { ...base, pick: "programs", label: "PR-B1",
2019
- jobs: [{ key: "p3", label: "RUN · P-3", start: week(30n), end: week(35n), state: ACTUAL }] }],
2020
- ]) }],
2021
- ]), DictType(StringType, OpsRow));
2022
- // The whole library, in layout order: every kind once, two kinds
2023
- // TWICE, a views series, three sections each wrapping a different
2024
- // kind, a group per entry, and literal chrome. Fourteen entries,
2025
- // eleven distinct arms.
2026
- const all = $.const([
2027
- // Literal one-off chrome — it names itself, so it can be
2028
- // switched off like anything else.
2029
- Plan.series.rows(OpsRow, { key: "chrome", title: "Section header", subtitle: "literal chrome" }, [Plan.events({ key: "hdr", label: "Plan", id: true })]),
2030
- Plan.series.chart(OpsRow, {
2031
- key: "util", title: "Utilisation", subtitle: "% per fortnight",
2032
- match: r => r.pick.equal("util"),
2033
- label: r => r.label, id: true, height: "spark",
2034
- layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.pct })],
2035
- }),
2036
- // ── THE SAME ASSET, SEEN THREE WAYS ───────────────────────
2037
- // A `views` series gives each machine one row per member
2038
- // series, ADJACENT and in this order — its jobs, its
2039
- // utilisation, its tonnes — and a seek on a machine lands on
2040
- // its first view row. Each row's id is its member series and
2041
- // the machine's key, so all three are addressable apart.
2042
- Plan.series.views(OpsRow, {
2043
- key: "machines", title: "Machines", subtitle: "one asset, three views",
2044
- match: r => r.pick.equal("machines"),
2045
- }, [
2046
- Plan.series.span(OpsRow, {
2047
- key: "machine-jobs", title: "Machine jobs",
2048
- label: r => r.label, id: true,
2049
- runs: r => r.jobs.map((_$, j) => Plan.run({
2050
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2051
- })),
2052
- }),
2053
- // The label stays the ASSET — all three rows are the same
2054
- // machine, and pretending otherwise would hide that. What
2055
- // distinguishes them is the VIEW, which is what the gutter
2056
- // sub-line is for.
2057
- Plan.series.chart(OpsRow, {
2058
- key: "machine-util", title: "Machine · utilisation",
2059
- label: r => r.label, stacked: true, sub: _r => some("utilisation %"), height: "spark",
2060
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
2061
- }),
2062
- Plan.series.table(OpsRow, {
2063
- key: "machine-tonnes", title: "Machine · tonnes",
2064
- label: r => r.label, stacked: true, sub: _r => some("tonnes · plan Δ"),
2065
- cells: r => r.nums,
2066
- format: Format.Number({ maximumFractionDigits: 0n }),
2067
- }),
2068
- ]),
2069
- Plan.series.span(OpsRow, {
2070
- key: "contract", title: "Contractor jobs", subtitle: "same KIND, own entry",
2071
- match: r => r.pick.equal("contract"),
2072
- label: r => r.label, id: true,
2073
- runs: r => r.jobs.map((_$, j) => Plan.run({
2074
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2075
- })),
2076
- }),
2077
- Plan.series.heat(OpsRow, {
2078
- key: "load", title: "Line load", subtitle: "% per fortnight",
2079
- match: r => r.pick.equal("load"),
2080
- label: r => r.label,
2081
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
2082
- }),
2083
- Plan.series.heat(OpsRow, {
2084
- key: "quality", title: "Quality index", subtitle: "same KIND, own entry",
2085
- match: r => r.pick.equal("quality"),
2086
- label: r => r.label,
2087
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
2088
- }),
2089
- Plan.series.buckets(OpsRow, {
2090
- key: "docks", title: "Dock allocations", subtitle: "tiles per bucket",
2091
- match: r => r.pick.equal("docks"),
2092
- label: r => r.label,
2093
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
2094
- }),
2095
- Plan.series.table(OpsRow, {
2096
- key: "table", title: "Despatch tonnes", subtitle: "per bucket",
2097
- match: r => r.pick.equal("table"),
2098
- label: r => r.label,
2099
- cells: r => r.nums,
2100
- format: Format.Number({ maximumFractionDigits: 0n }),
2101
- }),
2102
- Plan.series.cards(OpsRow, {
2103
- key: "cards", title: "Crew shifts", subtitle: "assignments",
2104
- match: r => r.pick.equal("cards"),
2105
- label: r => r.label,
2106
- chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
2107
- }),
2108
- Plan.series.events(OpsRow, {
2109
- key: "events", title: "Milestones", subtitle: "instant marks",
2110
- match: r => r.pick.equal("events"),
2111
- label: r => r.label, id: true,
2112
- marks: r => r.marks,
2113
- }),
2114
- // SECTIONS — each wrapping a DIFFERENT kind. A section is the
2115
- // unit a person picks — "Line 3" — and switching it off takes
2116
- // its whole block with it, because its members are built by its
2117
- // own `derive`.
2118
- //
2119
- // The metas are terse because the gutter is 168px and truncates
2120
- // the title if the meta crowds it.
2121
- Plan.series.section(OpsRow, { key: "line3", title: "Line 3", subtitle: "a section of spans", meta: "span" }, [
2122
- Plan.series.span(OpsRow, {
2123
- key: "gspan", title: "Line 3 jobs", subtitle: "member",
2124
- match: r => r.pick.equal("gspan"),
2125
- label: r => r.label, id: true,
2126
- runs: r => r.jobs.map((_$, j) => Plan.run({
2127
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2128
- })),
2129
- }),
2130
- ]),
2131
- Plan.series.section(OpsRow, { key: "loads", title: "Load", subtitle: "a section of heat", meta: "heat" }, [
2132
- Plan.series.heat(OpsRow, {
2133
- key: "gheat", title: "Load rows", subtitle: "member",
2134
- match: r => r.pick.equal("gheat"),
2135
- label: r => r.label,
2136
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
2137
- }),
2138
- ]),
2139
- Plan.series.section(OpsRow, { key: "dock-group", title: "Docks", subtitle: "a section of buckets", meta: "buckets" }, [
2140
- Plan.series.buckets(OpsRow, {
2141
- key: "gbuckets", title: "Dock rows", subtitle: "member",
2142
- match: r => r.pick.equal("gbuckets"),
2143
- label: r => r.label,
2144
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
2145
- }),
2146
- ]),
2147
- // One strip PER PROGRAM, its runs nested in the program's entry
2148
- // — and ONE library entry for all of them.
2149
- Plan.series.group(OpsRow, {
2150
- key: "programs", title: "Programs", subtitle: "one strip per program",
2151
- match: r => r.pick.equal("programs"),
2152
- label: r => r.label,
2153
- children: Plan.children((r) => r.members, [
2154
- Plan.series.span(OpsRow, {
2155
- key: "program-runs", title: "Program runs", subtitle: "member",
2156
- label: r => r.label, id: true,
2157
- runs: r => r.jobs.map((_$, j) => Plan.run({
2158
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2159
- })),
2160
- }),
2161
- ]),
2162
- }),
2163
- ], ArrayType(Plan.Types.Series(OpsRow)));
2164
- // The library lists every series by title, subtitle and kind; the
2165
- // only option is which start switched off. There are no row counts
2166
- // (#822): a count means something only when every entry is in hand.
2167
- const shown = $.let(Plan.pick("ex.plan.library", all, {
2168
- hidden: ["quality", "cards", "dock-group"],
2169
- }));
2170
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
2171
- // `pick` REPLACES `series`: the handle already carries the list, so
2172
- // the canvas feeds itself the picked ones and mounts the library.
2173
- // Nothing here wires the panel to the canvas.
2174
- return (_jsx(Plan, { axis: axis, data: ops, pick: shown, style: { height: "620px" } }));
2175
- } }))),
2176
- inputs: [],
2177
- });
2178
- // ============================================================================
2179
- // planRowDrop — the canvas as a drag TARGET, and which rows can receive
2180
- // ============================================================================
2181
- /**
2182
- * A Plan is a drag target, and a heterogeneous one — which is what makes it
2183
- * different from every other target in the grammar.
2184
- *
2185
- * Roster, Board and Blend have ONE kind of cell, so "can you drop here" is a
2186
- * question about the cell's contents. A Plan's rows are nine different things,
2187
- * so the question is answered at three levels:
2188
- *
2189
- * 1. **Structurally, by series.** A card lands only on a row whose series
2190
- * declares `edit` — WHERE it lands (`items`, the entry's list the row's
2191
- * elements come from) and HOW it becomes one (`create`, from the drop: the
2192
- * card, the row and the bucket's instant). Only the kinds holding discrete
2193
- * scheduled objects can — `span` (runs), `buckets` (tiles), `events`
2194
- * (marks), `cards` (chips). A `chart` / `heat` / `table` row renders
2195
- * DERIVED values, and section headers and group strips are wayfinding:
2196
- * they register no cell and never light up during a drag.
2197
- * 2. **By policy, with `canDrop`.** Of the rows that can receive, this canvas
2198
- * admits only the matching FAMILY: a job goes on a machine, a delivery on
2199
- * a dock, a shift on a crew, a milestone on a stream. The `PALLET` card
2200
- * belongs to no family and is therefore refused everywhere — the ⊘ stage
2201
- * on every row, which is what a card with nowhere to go should look like.
2202
- * 3. **As a draft (#880).** A drop is a gesture of the editing session: the
2203
- * entry is drafted with the new item in its list and its rows derived
2204
- * again — drawn at once with the pending mark, undone with ⌘Z — and Apply
2205
- * writes every draft as ONE checked batch, here through the live handle's
2206
- * inline adapter (`editing.onUpdate`).
2207
- *
2208
- * The canvas is bounded shorter than its rows, so the Line 3 machine starts
2209
- * below the fold: a card held at the canvas's bottom edge scrolls it there
2210
- * (#608). A card is also a keyboard control — Space picks it up, the arrows
2211
- * carry it from row to row and along a row's buckets, Space drops it and
2212
- * Escape cancels — and every step is said to a screen reader.
2213
- */
2214
- export const planRowDrop = example({
2215
- keywords: [
2216
- "Plan", "Library", "DnD", "drag", "drop", "canDrop", "sources", "id", "edit", "items", "create",
2217
- "add", "target", "surface", "cell", "slot", "row kind", "selective", "veto",
2218
- "invalid", "span", "buckets", "events", "cards", "chart", "heat", "table", "section", "row id", "row text",
2219
- "droppable", "inert", "bucket instant", "editing", "onUpdate", "onPatch", "draft", "Apply", "undo",
2220
- "Plan.Types.PatchEvent", "Reactive", "State", "live handle", "re-derive", "#880",
2221
- "auto-scroll", "keyboard", "screen reader", "announcements", "#608",
2222
- ],
2223
- description: "Library + Plan DnD — a card lands only on a series that declares `edit`, `canDrop` admits only the matching family, and every drop is a draft applied as one checked batch",
2224
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
2225
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
2226
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
2227
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
2228
- return w1.addWeeks(n.subtract(1n));
2229
- }));
2230
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
2231
- const JobRow = StructType({
2232
- key: StringType, label: StringType,
2233
- start: DateTimeType, end: DateTimeType, state: EventStateType,
2234
- });
2235
- const ShiftRow = StructType({
2236
- key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
2237
- });
2238
- const AllocRow = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
2239
- const OpsRow = StructType({
2240
- series: StringType, label: StringType,
2241
- jobs: ArrayType(JobRow),
2242
- points: ArrayType(MeasureRow),
2243
- cells: ArrayType(Plan.Types.HeatCell),
2244
- allocs: ArrayType(AllocRow),
2245
- nums: ArrayType(Plan.Types.TableCell),
2246
- shifts: ArrayType(ShiftRow),
2247
- marks: ArrayType(Plan.Types.EventMark),
2248
- });
2249
- const noJobs = $.const([], ArrayType(JobRow));
2250
- const noPoints = $.const([], ArrayType(MeasureRow));
2251
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
2252
- const noAllocs = $.const([], ArrayType(AllocRow));
2253
- const noNums = $.const([], ArrayType(Plan.Types.TableCell));
2254
- const noShifts = $.const([], ArrayType(ShiftRow));
2255
- const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
2256
- const pcts = $.const([46.0, 58.0, 66.0, 72.0, 84.0, 96.0], ArrayType(FloatType));
2257
- const points = $.let(East.Array.generate(6n, MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: pcts.get(i) })));
2258
- const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, (_$, i) => ({
2259
- at: Plan.at.time(week(i.multiply(2n).add(27n))),
2260
- value: some(pcts.get(i)),
2261
- label: some(East.Float.printFixed(pcts.get(i), 0n)),
2262
- })));
2263
- const CONFIRMED = variant("confirmed", null);
2264
- const RUNNING = variant("in-progress", null);
2265
- // Everything a drop creates is a PROPOSAL — it is a suggestion the
2266
- // host has not committed, and the lifecycle is how the canvas says so.
2267
- const ADDED = variant("proposed", variant("added", null));
2268
- const base = {
2269
- jobs: noJobs, points: noPoints, cells: noCells,
2270
- allocs: noAllocs, nums: noNums, shifts: noShifts, marks: noMarks,
2271
- };
2272
- // The series list below orders the canvas, interleaving the
2273
- // droppable and inert kinds — a solid block of receiving rows would
2274
- // not show that the line is drawn per kind. The source is a LIVE
2275
- // handle: the canvas reads it, and Apply writes the drafts back.
2276
- const ops = $.let(State.bind([DictType(StringType, OpsRow)], "ex.plan.ops", new Map([
2277
- ["util", { ...base, series: "util", label: "Util %", points }],
2278
- ["m03", { ...base, series: "mach", label: "L1-M03",
2279
- jobs: [{ key: "b214", label: "RUN · B-214", start: week(28n), end: week(31n), state: RUNNING }] }],
2280
- ["m04", { ...base, series: "mach", label: "L1-M04",
2281
- jobs: [{ key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: CONFIRMED }] }],
2282
- ["load", { ...base, series: "load", label: "L2 load", cells }],
2283
- ["dock2", { ...base, series: "dock", label: "Dock 2",
2284
- allocs: [{ key: "a1", at: week(29n), state: CONFIRMED }] }],
2285
- ["desp", { ...base, series: "table", label: "Despatch t",
2286
- nums: [
2287
- { at: Plan.at.time(week(28n)), value: some(128.0), text: none, tone: none },
2288
- { at: Plan.at.time(week(31n)), value: some(-96.0), text: none, tone: none },
2289
- ] }],
2290
- ["crewA", { ...base, series: "crew", label: "Crew A",
2291
- shifts: [{ key: "s1", from: week(27n), to: week(29n), label: "80h", state: CONFIRMED }] }],
2292
- ["ms", { ...base, series: "strm", label: "Milestones",
2293
- marks: [{ key: "k", at: Plan.at.time(week(29n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") }] }],
2294
- // A section MEMBER — the header itself takes no drops, but the
2295
- // span row under it receives like any other span row.
2296
- ["m11", { ...base, series: "gmach", label: "L3-M11",
2297
- jobs: [{ key: "b301", label: "RUN · B-301", start: week(30n), end: week(34n), state: CONFIRMED }] }],
2298
- ])));
2299
- // ── The two policy tables the host owns ───────────────────────
2300
- // Which FAMILY of card each row will take. A drop cell names its
2301
- // row by the canonical TEXT of the row's id — the series that made
2302
- // it and the path of keys to it — so the table is keyed by that
2303
- // text. A row absent from this map takes nothing — which is how
2304
- // the inert kinds would behave even if they did register a cell.
2305
- const rowAccepts = $.const(new Map([
2306
- [East.print(Plan.ref("mach", "m03")), "job"],
2307
- [East.print(Plan.ref("mach", "m04")), "job"],
2308
- [East.print(Plan.ref("gmach", "m11")), "job"],
2309
- [East.print(Plan.ref("dock", "dock2")), "delivery"],
2310
- [East.print(Plan.ref("crew", "crewA")), "shift"],
2311
- [East.print(Plan.ref("strm", "ms")), "milestone"],
2312
- ]), DictType(StringType, StringType));
2313
- const CardRow = StructType({
2314
- key: StringType, name: StringType, family: StringType, note: StringType, icon: StringType,
2315
- });
2316
- const cards = $.const([
2317
- { key: "job-weld", name: "Weld cell", family: "job", note: "job · machines", icon: "gear" },
2318
- { key: "job-cure", name: "Cure oven", family: "job", note: "job · machines", icon: "fire" },
2319
- { key: "dlv-truck", name: "Truck 12", family: "delivery", note: "delivery · docks", icon: "truck" },
2320
- { key: "shf-night", name: "Night shift", family: "shift", note: "shift · crews", icon: "moon" },
2321
- { key: "mst-audit", name: "Audit gate", family: "milestone", note: "milestone · streams", icon: "flag" },
2322
- // Belongs to no family, so no row accepts it — the ⊘ stage
2323
- // everywhere, which is what a card with nowhere to go looks like.
2324
- { key: "pallet", name: "PALLET", family: "none", note: "fits nowhere", icon: "box" },
2325
- ], ArrayType(CardRow));
2326
- const cardFamily = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.family));
2327
- const cardName = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.name));
2328
- // ── The drop veto ────────────────────────────────────────────
2329
- // Consulted with the candidate event the pointer's CURRENT bucket
2330
- // would produce, so the ⊘ appears while dragging rather than after,
2331
- // and once more before the drop becomes a draft. Only `add` reaches
2332
- // a Plan from a library, and refusing the rest says so.
2333
- const canDrop = $.const(East.function([DragEventType], BooleanType, ($, event) => {
2334
- const no = $.const(false, BooleanType);
2335
- return event.match({
2336
- add: ($, add) => {
2337
- const row = $.let(add.into.row);
2338
- const card = $.let(add.from.key);
2339
- return rowAccepts.has(row)
2340
- .and(_$ => cardFamily.has(card))
2341
- .and(_$ => rowAccepts.get(row).equal(cardFamily.get(card)));
2342
- },
2343
- }, _$ => no);
2344
- }));
2345
- // ── The session ──────────────────────────────────────────────
2346
- // Every drop is a DRAFT of the entry it landed on: the canvas
2347
- // draws the new item at once, marked pending, and the history bar
2348
- // undoes, redoes, discards and applies it. Apply writes every draft
2349
- // through the live handle as one checked batch; `onPatch` hears each
2350
- // gesture as it is made.
2351
- const lastBind = $.let(State.bind([StringType], "ex.plan.lastdrop", "none yet"));
2352
- const onPatch = $.const(East.function([Plan.Types.PatchEvent(OpsRow)], NullType, ($, event) => {
2353
- $(lastBind.write(East.str `${event.origin.getTag()} · ${event.label}`));
2354
- }));
2355
- const last = $.let(lastBind.read());
2356
- const axis = $.const(Plan.axis({
2357
- window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
2358
- }));
2359
- return (_jsxs(VStack, { gap: "4", align: "stretch", children: [_jsx(Library, { id: "plan-library", data: cards, item: c => ({ key: c.key, label: c.name, sublabel: c.note, icon: c.icon }) }), _jsx(Plan, { axis: axis, data: ops,
2360
- // The DnD target role: `id` names this surface in every
2361
- // cell ref, `sources` says which palettes it will take
2362
- // from, and `canDrop` is the policy. A drop becomes a
2363
- // draft of the session — without `editing` no card
2364
- // lands, since nothing could hold it.
2365
- id: "ops-plan", sources: ["plan-library"], canDrop: canDrop, editing: { onUpdate: ops.write, onPatch }, series: [
2366
- // INERT — a chart plots a derived series, so there
2367
- // is nothing a card could become here.
2368
- Plan.series.chart(OpsRow, {
2369
- key: "util", title: "Utilisation",
2370
- match: r => r.series.equal("util"),
2371
- label: r => r.label, id: true, height: "spark",
2372
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
2373
- }),
2374
- // RECEIVES — runs are discrete scheduled objects. A
2375
- // dropped job joins the machine's `jobs`: a
2376
- // fortnight long, from the bucket the pointer named.
2377
- Plan.series.span(OpsRow, {
2378
- key: "mach", title: "Machine jobs",
2379
- match: r => r.series.equal("mach"),
2380
- label: r => r.label, id: true,
2381
- runs: r => r.jobs.map((_$, j) => Plan.run({
2382
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2383
- })),
2384
- edit: {
2385
- items: "jobs",
2386
- create: (drop, r) => ({
2387
- key: East.str `drop-${drop.from.key}-${East.print(r.jobs.size())}`,
2388
- label: cardName.get(drop.from.key),
2389
- start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
2390
- state: ADDED,
2391
- }),
2392
- },
2393
- }),
2394
- // INERT — an intensity field has no object to add to.
2395
- Plan.series.heat(OpsRow, {
2396
- key: "load", title: "Line load",
2397
- match: r => r.series.equal("load"),
2398
- label: r => r.label,
2399
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
2400
- }),
2401
- // RECEIVES — a dropped delivery becomes a tile in
2402
- // the bucket under the pointer.
2403
- Plan.series.buckets(OpsRow, {
2404
- key: "dock", title: "Dock allocations",
2405
- match: r => r.series.equal("dock"),
2406
- label: r => r.label,
2407
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
2408
- edit: {
2409
- items: "allocs",
2410
- create: (drop, r) => ({
2411
- key: East.str `drop-${drop.from.key}-${East.print(r.allocs.size())}`,
2412
- at: drop.at.unwrap("time"), state: ADDED,
2413
- }),
2414
- },
2415
- }),
2416
- // INERT — the cells are computed numbers.
2417
- Plan.series.table(OpsRow, {
2418
- key: "table", title: "Despatch tonnes",
2419
- match: r => r.series.equal("table"),
2420
- label: r => r.label,
2421
- cells: r => r.nums,
2422
- format: Format.Number({ maximumFractionDigits: 0n }),
2423
- }),
2424
- // RECEIVES — a dropped shift becomes a chip.
2425
- Plan.series.cards(OpsRow, {
2426
- key: "crew", title: "Crew shifts",
2427
- match: r => r.series.equal("crew"),
2428
- label: r => r.label,
2429
- chips: r => r.shifts.map((_$, s) => Plan.chip({
2430
- key: s.key, from: s.from, to: s.to, label: s.label, state: s.state,
2431
- })),
2432
- edit: {
2433
- items: "shifts",
2434
- create: (drop, r) => ({
2435
- key: East.str `drop-${drop.from.key}-${East.print(r.shifts.size())}`,
2436
- from: drop.at.unwrap("time"), to: drop.at.unwrap("time").addWeeks(2n),
2437
- label: cardName.get(drop.from.key), state: ADDED,
2438
- }),
2439
- },
2440
- }),
2441
- // RECEIVES — a dropped milestone becomes a mark at
2442
- // the instant, the one kind with no duration.
2443
- Plan.series.events(OpsRow, {
2444
- key: "strm", title: "Milestones",
2445
- match: r => r.series.equal("strm"),
2446
- label: r => r.label, id: true,
2447
- marks: r => r.marks,
2448
- edit: {
2449
- items: "marks",
2450
- create: (drop, r) => ({
2451
- key: East.str `drop-${drop.from.key}-${East.print(r.marks.size())}`,
2452
- at: drop.at, kind: variant("milestone", null), icon: none,
2453
- label: some(cardName.get(drop.from.key)),
2454
- }),
2455
- },
2456
- }),
2457
- // The HEADER is inert; the span row under it is not.
2458
- Plan.series.section(OpsRow, { key: "line3", title: "Line 3", meta: "span" }, [
2459
- Plan.series.span(OpsRow, {
2460
- key: "gmach", title: "Line 3 machine jobs",
2461
- match: r => r.series.equal("gmach"),
2462
- label: r => r.label, id: true,
2463
- runs: r => r.jobs.map((_$, j) => Plan.run({
2464
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2465
- })),
2466
- edit: {
2467
- items: "jobs",
2468
- create: (drop, r) => ({
2469
- key: East.str `drop-${drop.from.key}-${East.print(r.jobs.size())}`,
2470
- label: cardName.get(drop.from.key),
2471
- start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
2472
- state: ADDED,
2473
- }),
2474
- },
2475
- }),
2476
- ]),
2477
- ],
2478
- // Shorter than its rows: the last ones start below the
2479
- // fold, and a card held at the bottom edge scrolls there.
2480
- style: { height: "300px" } }), _jsx(Text.MonoLabel, { children: East.str `LAST GESTURE · ${last}` })] }));
2481
- } }))),
2482
- inputs: [],
2483
- });
2484
- // ============================================================================
2485
- // planFill — the bounded sizing isolate (#320 / #567 D1)
2486
- // ============================================================================
2487
- /** Fill (#320) — `height="fill"` resolves against the bounded Box and
2488
- * virtualizes 200 rows of mixed kinds under 8 line strips. The bound must land
2489
- * on the canvas WRAPPER: a percentage passed inward resolves against an
2490
- * auto-height parent, computes to `auto`, and silently unbinds — the frame
2491
- * reports bounded, renders its spacer, and never scrolls (#567 D1).
2492
- *
2493
- * The grouping is here for what it does to VIRTUALIZATION, not for the
2494
- * chrome. Collapsing a strip removes its 25 children from the virtualizer's
2495
- * item list, so `count` and the total size change while the scroll offset does
2496
- * not — the case where an estimate that disagrees with the rendered height
2497
- * shows up as drift or a jumping scrollbar. The strips also give the list
2498
- * another row height again, so the `estimateSize` path is exercised rather
2499
- * than a single constant. */
2500
- export const planFill = example({
2501
- keywords: ["Plan", "fill", "height", "maxHeight", "#320", "virtual", "virtualization", "bounded", "Box", "scroll", "sizing", "data", "series", "span", "chart", "heat", "spark", "sub", "two-line", "mixed", "variable", "estimateSize", "group", "groupToDicts", "grouping", "data step", "views", "interleave", "Plan.children", "collapse", "parent"],
2502
- description: "Fill sizing over variable row heights — 200 virtualized rows of mixed kinds under 8 line strips",
2503
- fn: East.function([], UIComponentType, ($) => {
2504
- // Monday of ISO week n, 2026 — window W27–W38 (half-open).
2505
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
2506
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
2507
- return w1.addWeeks(n.subtract(1n));
2508
- }));
2509
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
2510
- const UnitRow = StructType({
2511
- line: StringType, series: StringType,
2512
- sub: OptionType(StringType),
2513
- start: DateTimeType, end: DateTimeType, tonnes: FloatType,
2514
- points: ArrayType(MeasureRow),
2515
- cells: ArrayType(Plan.Types.HeatCell),
2516
- });
2517
- // 200 raw rows, generated in East — the row count is the point. The
2518
- // KEYS sort as written (`UNIT-1000` … `UNIT-1199`) rather than
2519
- // lexicographically (`UNIT-1`, `UNIT-10`, `UNIT-100`, …), and a line's
2520
- // rows keep that order.
2521
- //
2522
- // `line` is the grouping level: 8 strips of 25, so a single collapse
2523
- // takes an eighth of the list out of the virtualizer at once.
2524
- //
2525
- // Everything else here exists to make the row heights DISAGREE, which
2526
- // is the case a virtualizer gets wrong. `series` cycles span / chart /
2527
- // heat (32 / 32 / 28px), `sub` alternates so every other row floors at
2528
- // the 42px two-line gutter, and the strips add another height again —
2529
- // so consecutive estimates differ in both directions and no constant
2530
- // can stand in for `estimateSize`.
2531
- const units = $.const(East.Array.range(0n, 200n).toDict((_$, i) => East.str `UNIT-${East.print(i.add(1000n))}`, ($, i) => {
2532
- const m = $.let(i.modulo(10n), IntegerType);
2533
- return {
2534
- line: East.str `LINE ${East.print(i.modulo(8n).add(1n))}`,
2535
- // The kind cycles with the KEY, so every stretch of the canvas
2536
- // holds the same mix — clustering the tall rows at one end
2537
- // would leave most of the scroll a single height again.
2538
- series: m.equal(4n).ifElse(() => "heat", () => m.equal(5n).ifElse(() => "spark", () => m.equal(6n).ifElse(() => "chartM", () => m.equal(7n).ifElse(() => "chartL", () => m.equal(8n).ifElse(() => "chartXL", () => "span"))))),
2539
- // Alternating one-line / two-line gutters.
2540
- sub: i.modulo(2n).equal(0n).ifElse(() => some(East.str `cap ${East.print(i.modulo(9n).add(40n))} t`), () => $.const(none, OptionType(StringType))),
2541
- start: week(i.modulo(9n).add(27n)),
2542
- end: week(i.modulo(9n).add(30n)),
2543
- tonnes: i.toFloat().multiply(1.5).add(40.0),
2544
- points: East.Array.generate(6n, MeasureRow, (_$, j) => ({
2545
- week: week(j.multiply(2n).add(27n)),
2546
- pct: j.multiply(17n).add(i).remainder(60n).toFloat().add(40.0),
2547
- })),
2548
- cells: East.Array.generate(6n, Plan.Types.HeatCell, (_$, j) => ({
2549
- at: Plan.at.time(week(j.multiply(2n).add(27n))),
2550
- value: some(j.multiply(13n).add(i).remainder(100n).toFloat()),
2551
- label: none,
2552
- })),
2553
- };
2554
- }), DictType(StringType, UnitRow));
2555
- // Grouping is a DATA step (#822): one `groupToDicts` makes each line an
2556
- // entry holding its 25 units.
2557
- const lines = $.let(units.groupToDicts(($, u) => u.line, ($, _u, k) => k));
2558
- const LineGroup = DictType(StringType, UnitRow);
2559
- // ONE strip per line, its units stepped down into. A `views` series
2560
- // gives each unit the ONE row its kind's member draws, so within a
2561
- // strip the kinds interleave in the units' order — a block per kind
2562
- // would bank the tall rows together again.
2563
- const series = $.const([
2564
- Plan.series.group(LineGroup, {
2565
- key: "lines", title: "Lines",
2566
- label: (_g, line) => line,
2567
- children: Plan.children((g) => g, [
2568
- Plan.series.views(UnitRow, { key: "units", title: "Units" }, [
2569
- Plan.series.span(UnitRow, {
2570
- key: "unit-span", title: "Span",
2571
- match: r => r.series.equal("span"),
2572
- label: (_r, k) => k, id: true, sub: r => r.sub,
2573
- runs: (r, k) => [Plan.run({
2574
- key: k, start: r.start, end: r.end,
2575
- label: East.str `RUN · ${k}`,
2576
- quantity: Plan.quantity(r.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
2577
- state: variant("confirmed", null),
2578
- })],
2579
- }),
2580
- // Heat rows are 28px — shorter than everything around them.
2581
- Plan.series.heat(UnitRow, {
2582
- key: "unit-heat", title: "Heat",
2583
- match: r => r.series.equal("heat"),
2584
- label: (_r, k) => k, id: true, sub: r => r.sub,
2585
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
2586
- }),
2587
- // Four chart heights. `height` is a SERIES declaration,
2588
- // not a per-row accessor, so distinct heights mean
2589
- // distinct members — which is the point here: a spark,
2590
- // then three EXPANDED rows several times taller,
2591
- // scattered through the same unit order.
2592
- Plan.series.chart(UnitRow, {
2593
- key: "unit-spark", title: "Spark",
2594
- match: r => r.series.equal("spark"),
2595
- label: (_r, k) => k, id: true, sub: r => r.sub,
2596
- height: "spark", expandable: true,
2597
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
2598
- }),
2599
- Plan.series.chart(UnitRow, {
2600
- key: "unit-chart-m", title: "Chart · medium",
2601
- match: r => r.series.equal("chartM"),
2602
- label: (_r, k) => k, id: true, sub: r => r.sub,
2603
- height: Plan.fixed("72px"),
2604
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
2605
- }),
2606
- Plan.series.chart(UnitRow, {
2607
- key: "unit-chart-l", title: "Chart · large",
2608
- match: r => r.series.equal("chartL"),
2609
- label: (_r, k) => k, id: true, sub: r => r.sub,
2610
- height: "expanded",
2611
- layers: r => [Chart.Area(r.points, { x: p => p.week, y: p => p.pct })],
2612
- }),
2613
- Plan.series.chart(UnitRow, {
2614
- key: "unit-chart-xl", title: "Chart · x-large",
2615
- match: r => r.series.equal("chartXL"),
2616
- label: (_r, k) => k, id: true, sub: r => r.sub,
2617
- height: "expanded", expandedHeight: "140px",
2618
- layers: r => [Chart.Column(r.points, { x: p => p.week, y: p => p.pct })],
2619
- }),
2620
- ]),
2621
- ]),
2622
- }),
2623
- ], ArrayType(Plan.Types.Series(LineGroup)));
2624
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
2625
- return (_jsx(Box, { height: "240px", children: _jsx(Plan, { axis: axis, data: lines, series: series, style: { height: "fill" } }) }));
2626
- }),
2627
- inputs: [],
2628
- });
2629
- // ============================================================================
2630
- // planReview — the review chrome, and what a verdict is FOR (#569)
2631
- // ============================================================================
2632
- /**
2633
- * Review as drafts (#880): a verdict is a FIELD of the record the reviewer
2634
- * decides about — `approval`, an `ApprovalStateType` — and the series whose
2635
- * rows are reviewed names it (`review: { verdict: "approval" }`). Approve or
2636
- * Reject on a row, and Approve all / Reject all at the foot, draft the entries
2637
- * with the field changed: the canvas derives the drafted rows again at once —
2638
- * buttons, bar, dot and run — marked pending, the history bar undoes, redoes
2639
- * and discards them, and Apply writes them back through the live handle as one
2640
- * checked batch. The foot's summary is the HOST's, over what it holds: the
2641
- * saved state, which moves only when Apply lands.
2642
- */
2643
- export const planReview = example({
2644
- keywords: [
2645
- "Plan", "review", "approval", "approve", "reject", "verdict", "decision", "ApprovalStateType",
2646
- "deriveApproval", "flagged", "chrome", "batch", "foot", "Approve all", "Reject all", "onRerun",
2647
- "editing", "onUpdate", "draft", "pending", "Apply", "undo", "redo", "discard", "Reactive", "State", "bind",
2648
- "live handle", "derived", "accessor", "data", "row id", "#880",
2649
- ],
2650
- description: "Review as drafts — a verdict drafts the entry's `approval` field, the canvas re-derives buttons, bar and dot from the draft, and Apply writes the batch back through the live handle",
2651
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
2652
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
2653
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
2654
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
2655
- return w1.addWeeks(n.subtract(1n));
2656
- }));
2657
- // The verdict IS a field of the job — what the reviewer decides
2658
- // about it — so a verdict drafts the job with `approval` changed,
2659
- // and Apply writes the job back. Nothing is held anywhere else.
2660
- const JobRow = StructType({
2661
- start: DateTimeType, end: DateTimeType, tonnes: FloatType, flagged: BooleanType,
2662
- approval: ApprovalStateType,
2663
- });
2664
- // A LIVE handle: the canvas reads it, and Apply writes it.
2665
- const jobs = $.let(State.bind([DictType(StringType, JobRow)], "plan_review_jobs", new Map([
2666
- ["L1-M03", { start: week(28n), end: week(31n), tonnes: 96.0, flagged: true, approval: variant("pending", null) }],
2667
- ["L1-M04", { start: week(29n), end: week(33n), tonnes: 112.0, flagged: true, approval: variant("approved", null) }],
2668
- ["L1-M07", { start: week(30n), end: week(34n), tonnes: 64.0, flagged: true, approval: variant("rejected", null) }],
2669
- ["L2-M11", { start: week(27n), end: week(30n), tonnes: 88.0, flagged: false, approval: variant("approved", null) }],
2670
- ])));
2671
- // Rerun is not a verdict — it asks the host for a fresh proposal,
2672
- // so it stays a callback and writes the SOURCE: here every job
2673
- // returns to what its flag derives ("clean rests pre-approved,
2674
- // flagged awaits an explicit call"). Drafts made before it sit over
2675
- // a source that moved, and say so.
2676
- const onRerun = $.const(East.function([], NullType, ($) => {
2677
- const fresh = $.let(jobs.read().map((_$, j) => ({
2678
- start: j.start, end: j.end, tonnes: j.tonnes, flagged: j.flagged,
2679
- approval: deriveApproval(j.flagged).unwrap("some"),
2680
- })));
2681
- $(jobs.write(fresh));
2682
- }));
2683
- const series = $.const([
2684
- Plan.series.span(JobRow, {
2685
- key: "jobs", title: "Jobs",
2686
- label: (_r, k) => k, id: true,
2687
- value: r => some(East.str `${East.Float.printFixed(r.tonnes, 0n)} t`),
2688
- // The verdict's FIELD: the decision buttons show it, and a
2689
- // verdict drafts the job with it changed.
2690
- review: { verdict: "approval" },
2691
- // ...and the SAME field drives the bar and the dot, because
2692
- // appearance is derived like everything else. Click Approve
2693
- // and the draft repaints — the chrome never touched it.
2694
- status: r => r.approval.hasTag("rejected").ifElse(() => some(variant("danger", null)), () => r.flagged.and(_$ => r.approval.hasTag("pending")).ifElse(() => some(variant("warning", null)), () => none)),
2695
- runs: (r, k) => [Plan.run({
2696
- key: k, start: r.start, end: r.end,
2697
- label: East.str `RUN · ${k}`,
2698
- quantity: Plan.quantity(r.tonnes, { unit: "t", format: Format.Number({ maximumFractionDigits: 0n }) }),
2699
- state: r.approval.hasTag("approved").ifElse(() => variant("confirmed", null), () => r.approval.hasTag("rejected").ifElse(() => variant("rejected", null), () => variant("proposed", variant("recommended", null)))),
2700
- })],
2701
- }),
2702
- ], ArrayType(Plan.Types.Series(JobRow)));
2703
- const axis = $.const(Plan.axis({
2704
- window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
2705
- }));
2706
- // The host's summary reads what it holds — the saved jobs.
2707
- const saved = $.let(jobs.read());
2708
- const pending = $.let(saved.filter((_$, j) => j.approval.hasTag("pending")).size());
2709
- const rejected = $.let(saved.filter((_$, j) => j.approval.hasTag("rejected")).size());
2710
- return (_jsx(Plan, { axis: axis, data: jobs, series: series, review: {
2711
- summary: _jsx(Text, { children: East.str `SAVED · ${East.Float.printFixed(pending.toFloat(), 0n)} PENDING · ${East.Float.printFixed(rejected.toFloat(), 0n)} REJECTED` }),
2712
- onRerun,
2713
- }, editing: { onUpdate: jobs.write } }));
2714
- } }))),
2715
- inputs: [],
2716
- });
2717
- // ============================================================================
2718
- // planEditing — every change a draft, one checked Apply (#880, #825)
2719
- // ============================================================================
2720
- /**
2721
- * The Plan's editing session (#880) — the Sheet's, over the canvas's entries.
2722
- *
2723
- * The source holds LINES, each holding its machines. A machine is reviewed
2724
- * (its `approval` field), receives dropped jobs (its `jobs` list) and has its
2725
- * jobs moved and resized, and every gesture lands on the MACHINE rows, one
2726
- * level down — yet each drafts the LINE, the source's top-level entry, which
2727
- * the whole subtree rides in:
2728
- *
2729
- * - Approve / Reject on a machine, and Approve all / Reject all at the foot,
2730
- * draft its `approval` (`review: { verdict: "approval" }` on the machines).
2731
- * - A job card dropped on a machine drafts a new job in its `jobs` at the
2732
- * bucket's instant (`edit: { items: "jobs", create }`).
2733
- * - A job's run moves along its machine or onto another machine, and resizes
2734
- * by either end (#825): the series names the job's key and the instant
2735
- * fields a move writes (`key`, `start`, `end`). It moves in whole weeks, or
2736
- * in days with Shift held. A move onto a machine of the other line takes
2737
- * the job out of one line and puts it into the other, as one gesture over
2738
- * both. From the keyboard, Space on a focused run picks it up: ←/→ move it
2739
- * a week, Shift+←/→ its end, Alt+←/→ its start, ↑/↓ carry it to another
2740
- * machine, and Space drops it.
2741
- *
2742
- * Every gesture is one transaction, drawn at once with the pending mark; the
2743
- * toolbar's history bar undoes, redoes and discards it (so do ⌘Z and ⌘⇧Z).
2744
- * `ready` is the author's check over a drafted line: a machine holding more
2745
- * than four jobs is refused, by name, and Apply waits until it is fixed — M11
2746
- * already holds four, so a job moved onto it holds Apply until one leaves.
2747
- * Apply writes the batch back through the live handle (`onUpdate`), and
2748
- * `onPatch` hears each gesture as it is made.
2749
- */
2750
- export const planEditing = example({
2751
- keywords: [
2752
- "Plan", "editing", "session", "draft", "drafts", "transaction", "onUpdate", "onPatch", "ready",
2753
- "Readiness", "Editing.Types.Readiness", "invalid", "review", "verdict", "approval", "ApprovalStateType",
2754
- "Approve all", "Reject all", "edit", "items", "create", "drop", "Library", "DnD", "id", "sources",
2755
- "undo", "redo", "discard", "Apply", "history bar", "pending", "nested", "children", "Plan.children",
2756
- "top-level entry", "live handle", "Plan.Types.PatchEvent", "Reactive", "State", "bind", "#880",
2757
- "move", "resize", "drag", "run", "key", "start", "end", "Shift", "snap", "keyboard", "Space",
2758
- "cross-row", "Plan.Types.Move", "#825",
2759
- ],
2760
- description: "The Plan's editing session — verdicts, dropped jobs and moved or resized runs on nested machine rows draft their line (both lines when a job changes line), the history bar undoes and applies them, and `ready` refuses a crowded machine",
2761
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
2762
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
2763
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
2764
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
2765
- return w1.addWeeks(n.subtract(1n));
2766
- }));
2767
- const Job = StructType({
2768
- key: StringType, label: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
2769
- });
2770
- const Machine = StructType({ approval: ApprovalStateType, jobs: ArrayType(Job) });
2771
- const Line = StructType({ name: StringType, machines: DictType(StringType, Machine) });
2772
- const CONFIRMED = variant("confirmed", null);
2773
- const RECOMMENDED = variant("proposed", variant("recommended", null));
2774
- // What a drop creates is a proposal the host has not committed.
2775
- const ADDED = variant("proposed", variant("added", null));
2776
- // A LIVE handle over the lines: the canvas reads it, and Apply
2777
- // writes it. M11 already holds four jobs, so one more is refused.
2778
- const lines = $.let(State.bind([DictType(StringType, Line)], "ex.plan.editing.lines", new Map([
2779
- ["L1", { name: "Line 1", machines: new Map([
2780
- ["M03", { approval: variant("pending", null), jobs: [
2781
- { key: "b214", label: "B-214", start: week(28n), end: week(31n), state: RECOMMENDED },
2782
- ] }],
2783
- ["M04", { approval: variant("approved", null), jobs: [
2784
- { key: "b208", label: "B-208", start: week(27n), end: week(30n), state: CONFIRMED },
2785
- { key: "b219", label: "B-219", start: week(31n), end: week(34n), state: RECOMMENDED },
2786
- ] }],
2787
- ]) }],
2788
- ["L2", { name: "Line 2", machines: new Map([
2789
- ["M11", { approval: variant("pending", null), jobs: [
2790
- { key: "b241", label: "B-241", start: week(27n), end: week(29n), state: CONFIRMED },
2791
- { key: "b244", label: "B-244", start: week(29n), end: week(31n), state: RECOMMENDED },
2792
- { key: "b247", label: "B-247", start: week(31n), end: week(33n), state: RECOMMENDED },
2793
- { key: "b250", label: "B-250", start: week(33n), end: week(35n), state: RECOMMENDED },
2794
- ] }],
2795
- ]) }],
2796
- ])));
2797
- // The palette the jobs come from.
2798
- const CardRow = StructType({ key: StringType, name: StringType, note: StringType, icon: StringType });
2799
- const cards = $.const([
2800
- { key: "weld", name: "Weld run", note: "two weeks", icon: "gear" },
2801
- { key: "cure", name: "Cure run", note: "two weeks", icon: "fire" },
2802
- ], ArrayType(CardRow));
2803
- const cardName = $.const(cards.toDict((_$, c) => c.key, (_$, c) => c.name));
2804
- // The author's check over one drafted LINE — every check of the
2805
- // batch runs in one call. A refusal names the machine.
2806
- const ready = $.const(East.function([Line, StringType], Editing.Types.Readiness, ($, line, _key) => {
2807
- const crowded = $.let(line.machines.filter((_$, m) => m.jobs.size().greater(4n)));
2808
- const result = $.let(variant("ready", null), Editing.Types.Readiness);
2809
- $.if(crowded.size().greater(0n), ($) => {
2810
- $.assign(result, variant("invalid", crowded.toArray((_$, m, k) => ({
2811
- field: "jobs", message: East.str `${k} holds ${East.print(m.jobs.size())} jobs — at most 4`,
2812
- }))));
2813
- });
2814
- return result;
2815
- }));
2816
- // Every gesture, as it is made — a verdict, a drop, an undo.
2817
- const lastBind = $.let(State.bind([StringType], "ex.plan.editing.last", "none yet"));
2818
- const onPatch = $.const(East.function([Plan.Types.PatchEvent(Line)], NullType, ($, event) => {
2819
- $(lastBind.write(East.str `${event.origin.getTag()} · ${event.label}`));
2820
- }));
2821
- const last = $.let(lastBind.read());
2822
- // The host's summary reads what it holds — the saved lines.
2823
- const saved = $.let(lines.read());
2824
- const pending = $.let(saved.toArray((_$, l) => l.machines.filter((_$, m) => m.approval.hasTag("pending")).size()).sum());
2825
- const axis = $.const(Plan.axis({
2826
- window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n),
2827
- }));
2828
- return (_jsxs(VStack, { gap: "4", align: "stretch", children: [_jsx(Library, { id: "plan-editing-jobs", data: cards, item: c => ({ key: c.key, label: c.name, sublabel: c.note, icon: c.icon }) }), _jsx(Plan, { axis: axis, data: lines, id: "plan-editing", sources: ["plan-editing-jobs"], series: [
2829
- // One row per line, its machines stepped down into
2830
- // through a plain field — which is what lets a
2831
- // gesture on a machine write back into its line.
2832
- Plan.series.span(Line, {
2833
- key: "lines", title: "Lines",
2834
- label: l => l.name,
2835
- runs: _l => [],
2836
- rollup: "union",
2837
- children: Plan.children(l => l.machines, [
2838
- Plan.series.span(Machine, {
2839
- key: "machines", title: "Machines",
2840
- label: (_m, k) => k, id: true,
2841
- review: { verdict: "approval" },
2842
- status: m => m.approval.hasTag("rejected").ifElse(() => some(variant("danger", null)), () => none),
2843
- runs: m => m.jobs.map((_$, j) => Plan.run({
2844
- key: j.key, start: j.start, end: j.end,
2845
- label: East.str `RUN · ${j.label}`, state: j.state,
2846
- })),
2847
- edit: {
2848
- items: "jobs",
2849
- // A run moves and resizes: the job is
2850
- // found by `key` — the run's own key —
2851
- // and a move writes `start` and `end`.
2852
- // Keys stay unique on a machine: a drop
2853
- // or a move that would repeat one is
2854
- // refused, so the new job's key names
2855
- // its card, bucket and place.
2856
- key: "key", start: "start", end: "end",
2857
- create: (drop, m) => ({
2858
- key: East.str `${drop.from.key}-${East.print(drop.at.unwrap("time"))}-${East.print(m.jobs.size())}`,
2859
- label: cardName.get(drop.from.key),
2860
- start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
2861
- state: ADDED,
2862
- }),
2863
- },
2864
- }),
2865
- ]),
2866
- }),
2867
- ], review: {
2868
- summary: _jsx(Text, { children: East.str `SAVED · ${East.print(pending)} PENDING` }),
2869
- }, editing: { onUpdate: lines.write, onPatch, ready }, style: { height: "360px" } }), _jsx(Text.MonoLabel, { children: East.str `LAST GESTURE · ${last}` })] }));
2870
- } }))),
2871
- inputs: [],
2872
- });
2873
- // ============================================================================
2874
- // planUiState — the interaction state, held by the host (#824)
2875
- // ============================================================================
2876
- /**
2877
- * A bound `ui` state (#824): the canvas's selection, the rows folded or opened
2878
- * against what they declare, the expanded charts, and a row to bring into
2879
- * view — held by the HOST, in `State.bind` at `Plan.Types.UiState`, seeded by
2880
- * `Plan.uiState(…)`. The canvas reads it and writes the user's actions back;
2881
- * anything else may write it too. Here a picker beside the canvas brings a
2882
- * machine into view (`focus` — the canvas opens the line it sits in, scrolls
2883
- * to it and spends the request) and selects it, three buttons fold, open and
2884
- * expand from outside, and a readout says what the state holds — including
2885
- * whatever the user clicked on the canvas itself.
2886
- */
2887
- export const planUiState = example({
2888
- keywords: [
2889
- "Plan", "ui", "UiState", "Plan.uiState", "state", "bound", "State", "bind", "controlled",
2890
- "selected", "selection", "collapsed", "expanded", "fold", "open", "charts", "focus",
2891
- "scroll to row", "deep link", "external", "write back", "Reactive", "Select", "Button", "#824",
2892
- ],
2893
- description: "A bound ui state — the host selects a machine and brings it into view, folds and opens lines and expands a chart from outside, and reads back what the user did on the canvas",
2894
- fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
2895
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
2896
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
2897
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
2898
- return w1.addWeeks(n.subtract(1n));
2899
- }));
2900
- const JobRow = StructType({
2901
- key: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType,
2902
- });
2903
- const MachineRow = StructType({ line: StringType, jobs: ArrayType(JobRow) });
2904
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
2905
- const machines = $.const(new Map([
2906
- ["L1-M03", { line: "Line 1", jobs: [{ key: "b214", start: week(28n), end: week(31n), state: variant("in-progress", null) }] }],
2907
- ["L1-M04", { line: "Line 1", jobs: [{ key: "b208", start: week(27n), end: week(30n), state: variant("actual", null) }] }],
2908
- ["L2-M11", { line: "Line 2", jobs: [{ key: "b241", start: week(29n), end: week(33n), state: variant("confirmed", null) }] }],
2909
- ["L2-M12", { line: "Line 2", jobs: [{ key: "b198", start: week(30n), end: week(34n), state: variant("proposed", variant("recommended", null)) }] }],
2910
- ["L3-M21", { line: "Line 3", jobs: [{ key: "b301", start: week(31n), end: week(35n), state: variant("confirmed", null) }] }],
2911
- ["L3-M22", { line: "Line 3", jobs: [{ key: "b302", start: week(33n), end: week(37n), state: variant("proposed", variant("recommended", null)) }] }],
2912
- ]), DictType(StringType, MachineRow));
2913
- const lines = $.let(machines.groupToDicts(($, m) => m.line, ($, _m, k) => k));
2914
- const LineGroup = DictType(StringType, MachineRow);
2915
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({
2916
- week: week(i.add(27n)), pct: i.multiply(13n).remainder(9n).toFloat().add(88.0),
2917
- })));
2918
- const series = $.const([
2919
- Plan.series.rows(LineGroup, { key: "kpi", title: "Coverage" }, [
2920
- Plan.chart({
2921
- key: "coverage", label: "Coverage", id: true, height: "spark", expandable: true,
2922
- layers: [Chart.Line(coverage, { x: p => p.week, y: p => p.pct })],
2923
- }),
2924
- ]),
2925
- Plan.series.group(LineGroup, {
2926
- key: "lines", title: "Lines",
2927
- label: (_g, line) => line,
2928
- children: Plan.children((g) => g, [
2929
- Plan.series.span(MachineRow, {
2930
- key: "machines", title: "Machines",
2931
- label: (_m, k) => k, id: true,
2932
- runs: m => m.jobs.map((_$, j) => Plan.run({
2933
- key: j.key, start: j.start, end: j.end, label: East.str `RUN · ${j.key}`, state: j.state,
2934
- })),
2935
- }),
2936
- ]),
2937
- }),
2938
- ], ArrayType(Plan.Types.Series(LineGroup)));
2939
- // The STATE — Line 3 starts folded. Every row is named by its id:
2940
- // a line is `Plan.ref("lines", line)`, a machine
2941
- // `Plan.ref("machines", line, machine)`.
2942
- const ui = $.let(State.bind([Plan.Types.UiState], "ex.plan.ui", Plan.uiState({ collapsed: [Plan.ref("lines", "Line 3")] })));
2943
- const now = $.let(ui.read());
2944
- const lineIds = $.const([Plan.ref("lines", "Line 1"), Plan.ref("lines", "Line 2"), Plan.ref("lines", "Line 3")], ArrayType(Plan.Types.RowId));
2945
- const kpi = $.const(Plan.ref("kpi", "coverage"));
2946
- // Bring a machine into view: select it, and REQUEST its focus — the
2947
- // canvas opens its line if it is folded, scrolls to it, and clears
2948
- // the request once it has.
2949
- const goTo = $.const(East.function([StringType], NullType, ($, key) => {
2950
- const s = $.let(ui.read());
2951
- const id = $.let(Plan.ref("machines", machines.get(key).line, key));
2952
- $(ui.write(East.value({
2953
- selected: some(id), collapsed: s.collapsed, expanded: s.expanded, charts: s.charts, focus: some(id),
2954
- }, Plan.Types.UiState)));
2955
- }));
2956
- const foldAll = $.const(East.function([], NullType, ($) => {
2957
- const s = $.let(ui.read());
2958
- $(ui.write(East.value({
2959
- selected: s.selected, collapsed: lineIds, expanded: [], charts: s.charts, focus: s.focus,
2960
- }, Plan.Types.UiState)));
2961
- }));
2962
- const openAll = $.const(East.function([], NullType, ($) => {
2963
- const s = $.let(ui.read());
2964
- $(ui.write(East.value({
2965
- selected: s.selected, collapsed: [], expanded: lineIds, charts: s.charts, focus: s.focus,
2966
- }, Plan.Types.UiState)));
2967
- }));
2968
- const noCharts = $.const([], ArrayType(Plan.Types.RowId));
2969
- const kpiChart = $.const([kpi], ArrayType(Plan.Types.RowId));
2970
- const chart = $.const(East.function([], NullType, ($) => {
2971
- const s = $.let(ui.read());
2972
- const charts = $.let(s.charts.size().greater(0n).ifElse(() => noCharts, () => kpiChart));
2973
- $(ui.write(East.value({
2974
- selected: s.selected, collapsed: s.collapsed, expanded: s.expanded, charts, focus: s.focus,
2975
- }, Plan.Types.UiState)));
2976
- }));
2977
- const keys = $.const(["L1-M03", "L1-M04", "L2-M11", "L2-M12", "L3-M21", "L3-M22"], ArrayType(StringType));
2978
- const picked = $.let(now.selected.match({
2979
- some: (_$, id) => East.print(id),
2980
- none: (_$) => "nothing",
2981
- }), StringType);
2982
- const axis = $.const(Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }));
2983
- return (_jsxs(VStack, { gap: "2", align: "stretch", children: [_jsxs(HStack, { gap: "2", children: [_jsx(Select, { value: "L3-M22", onChange: goTo, size: "sm", items: keys.map((_$, k) => Select.Item(k, East.str `Go to ${k}`)) }), _jsx(Button, { size: "xs", onClick: foldAll, children: "Fold lines" }), _jsx(Button, { size: "xs", onClick: openAll, children: "Open lines" }), _jsx(Button, { size: "xs", onClick: chart, children: "Coverage chart" })] }), _jsx(Plan, { axis: axis, data: lines, series: series, ui: ui, style: { height: "300px" } }), _jsx(Text.MonoLabel, { children: East.str `SELECTED · ${picked} · ${East.print(now.collapsed.size())} FOLDED · ${East.print(now.expanded.size())} OPENED` })] }));
2984
- } }))),
2985
- inputs: [],
2986
- });
2987
- /**
2988
- * R2 expand-in-place — the one example that shows the whole gesture.
2989
- *
2990
- * Expand is two halves and needs BOTH: a per-row `expand` declaration (data,
2991
- * so it flows through an accessor like every other envelope field) and the
2992
- * ROOT's `expandRender` resolver, which builds the mounted body from the row's
2993
- * id. Neither alone shows the control.
2994
- *
2995
- * The rows here are deliberately heterogeneous — span, chart, heat, table,
2996
- * events — because focusing one is what makes the other five collapse, and
2997
- * each row KIND collapses differently (#591): geometry shrinks, values
2998
- * re-encode as a tone strip, shapes keep their silhouette.
2999
- */
3000
- export const planExpand = example({
3001
- keywords: ["Plan", "expand", "expandRender", "expandGutter", "axis", "keep", "dim", "off", "focus", "R2", "collapse", "context strip", "row", "row id", "data", "series", "chart", "heat", "table", "events", "span", "raw"],
3002
- description: "Expand-in-place — a row declares `expand`, the root renders plot and gutter, and unfocused rows collapse to strips",
3003
- fn: East.function([], UIComponentType, ($) => {
3004
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
3005
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
3006
- return w1.addWeeks(n.subtract(1n));
3007
- }));
3008
- // ONE raw source; `series` picks the series and `expand` is per-row
3009
- // DATA — presence is what grows the ⤢ control on that row.
3010
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
3011
- const OpsRow = StructType({
3012
- series: StringType,
3013
- label: StringType,
3014
- expand: OptionType(Plan.Types.Expand),
3015
- jobs: ArrayType(StructType({
3016
- key: StringType, label: StringType,
3017
- start: DateTimeType, end: DateTimeType, state: EventStateType,
3018
- })),
3019
- points: ArrayType(MeasureRow),
3020
- cells: ArrayType(Plan.Types.HeatCell),
3021
- nums: ArrayType(Plan.Types.TableCell),
3022
- marks: ArrayType(Plan.Types.EventMark),
3023
- });
3024
- const noJobs = $.const([], ArrayType(StructType({
3025
- key: StringType, label: StringType,
3026
- start: DateTimeType, end: DateTimeType, state: EventStateType,
3027
- })));
3028
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
3029
- const noNums = $.const([], ArrayType(Plan.Types.TableCell));
3030
- const noMarks = $.const([], ArrayType(Plan.Types.EventMark));
3031
- const noPoints = $.const([], ArrayType(MeasureRow));
3032
- const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
3033
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
3034
- const ops = $.const(new Map([
3035
- // A CHART row — the one kind whose marks are a VALUE scale. Its
3036
- // plot, its gutter ticks and its ref-label gate answer to the band
3037
- // the marks keep at the top, not to the grown row (#591).
3038
- ["COVERAGE", { series: "chart", label: "Coverage",
3039
- expand: some({ height: some("150px"), axis: variant("keep", null) }),
3040
- jobs: noJobs, points: coverage, cells: noCells, nums: noNums, marks: noMarks }],
3041
- // axis: keep — the grid and now-line run THROUGH the render.
3042
- ["L1-M03", { series: "span", label: "L1-M03",
3043
- expand: some({ height: some("168px"), axis: variant("keep", null) }),
3044
- jobs: [
3045
- { key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
3046
- { key: "qc", label: "QC", start: week(30n), end: week(32n), state: variant("confirmed", null) },
3047
- { key: "b231", label: "RUN · B-231", start: week(33n), end: week(38n), state: variant("proposed", variant("recommended", null)) },
3048
- ], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
3049
- // axis: dim — washed to 40% behind a dense render.
3050
- ["L1-M04", { series: "span", label: "L1-M04",
3051
- expand: some({ height: some("140px"), axis: variant("dim", null) }),
3052
- jobs: [
3053
- { key: "b214", label: "RUN · B-214", start: week(28n), end: week(33n), state: variant("in-progress", null) },
3054
- ], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
3055
- // No declaration — no control. The contrast is the point: one row
3056
- // that cannot be expanded beside five that can.
3057
- ["L1-M07", { series: "span", label: "L1-M07", expand: none,
3058
- jobs: [
3059
- { key: "hld", label: "HLD · B-197", start: week(27n), end: week(31n), state: variant("actual", null) },
3060
- ], points: noPoints, cells: noCells, nums: noNums, marks: noMarks }],
3061
- // The kinds that COLLAPSE differently — heat keeps its ramp, the
3062
- // table re-encodes its numerals, the marks keep their silhouettes.
3063
- ["LOAD", { series: "heat", label: "Line load",
3064
- expand: some({ height: some("132px"), axis: variant("keep", null) }),
3065
- jobs: noJobs, points: noPoints, nums: noNums, marks: noMarks,
3066
- cells: [
3067
- { at: Plan.at.time(week(27n)), value: some(46.0), label: some("46") },
3068
- { at: Plan.at.time(week(28n)), value: some(58.0), label: some("58") },
3069
- { at: Plan.at.time(week(29n)), value: some(66.0), label: some("66") },
3070
- { at: Plan.at.time(week(30n)), value: some(72.0), label: some("72") },
3071
- { at: Plan.at.time(week(31n)), value: some(84.0), label: some("84") },
3072
- { at: Plan.at.time(week(32n)), value: some(90.0), label: some("90") },
3073
- { at: Plan.at.time(week(33n)), value: some(96.0), label: some("96") },
3074
- { at: Plan.at.time(week(34n)), value: none, label: none },
3075
- { at: Plan.at.time(week(35n)), value: some(92.0), label: some("92") },
3076
- ] }],
3077
- // axis: off — the render draws its own canvas, so the shared lines
3078
- // are suppressed INSIDE this row only (the ruler never moves).
3079
- ["DESPATCH", { series: "table", label: "Despatch t",
3080
- expand: some({ height: some("120px"), axis: variant("off", null) }),
3081
- jobs: noJobs, points: noPoints, cells: noCells, marks: noMarks,
3082
- nums: [
3083
- { at: Plan.at.time(week(27n)), value: some(128.0), text: none, tone: none },
3084
- { at: Plan.at.time(week(28n)), value: some(134.0), text: none, tone: none },
3085
- { at: Plan.at.time(week(29n)), value: some(119.0), text: none, tone: none },
3086
- { at: Plan.at.time(week(30n)), value: some(-96.0), text: none, tone: none },
3087
- { at: Plan.at.time(week(31n)), value: some(-88.0), text: none, tone: none },
3088
- { at: Plan.at.time(week(32n)), value: none, text: none, tone: none },
3089
- { at: Plan.at.time(week(33n)), value: some(151.0), text: none, tone: none },
3090
- { at: Plan.at.time(week(34n)), value: some(162.0), text: none, tone: none },
3091
- { at: Plan.at.time(week(35n)), value: some(144.0), text: none, tone: none },
3092
- ] }],
3093
- ["MILESTONES", { series: "events", label: "Milestones",
3094
- expand: some({ height: some("112px"), axis: variant("dim", null) }),
3095
- jobs: noJobs, points: noPoints, cells: noCells, nums: noNums,
3096
- marks: [
3097
- { key: "k", at: Plan.at.time(week(28n)), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
3098
- { key: "d", at: Plan.at.time(week(31n)), kind: variant("decision", { applied: true }), icon: none, label: none },
3099
- { key: "a", at: Plan.at.time(week(34n)), kind: variant("exception", null), icon: none, label: some("AUDIT") },
3100
- ] }],
3101
- ]), DictType(StringType, OpsRow));
3102
- // ONE resolver serves every declaring row, called with the row's id at
3103
- // interaction time. The canvas hands it the PLOT column with the
3104
- // shared grid + now-line drawn behind it — so a component that fills
3105
- // that column edge to edge shares the canvas's x-space and lines up
3106
- // with the buckets above. `Sparkline` is the axis-free chart, which is
3107
- // what makes the alignment visible; a `Chart` would draw its own axes
3108
- // and margins inside the column and align to those instead.
3109
- const util = $.let(East.Array.generate(12n, FloatType, (_$, i) => i.multiply(19n).remainder(48n).toFloat().add(50.0)));
3110
- const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Sparkline, { data: util, type: "area", color: "link", width: "100%", height: "100%" }))));
3111
- // The GUTTER half. An expanded row's gutter cell grows with the row,
3112
- // and what fills the space it opens up is the author's — the identity
3113
- // and measures that only earn their place once the row has the canvas.
3114
- // Same row id as `expandRender`, so it can differ per row.
3115
- // The old spec's drilled-row card, which is what the grown gutter is
3116
- // for: identity lines then a fill meter. The lines need no styling —
3117
- // the gutter body already carries the sub-line vocabulary — so the
3118
- // author writes content, not typography.
3119
- const GutterFacts = StructType({ a: StringType, b: StringType, fill: FloatType });
3120
- const expandGutter = $.const(East.function([Plan.Types.RowId], UIComponentType, ($, id) => {
3121
- const facts = $.const(new Map([
3122
- ["COVERAGE", { a: "TARGET 100 · MIN 92", b: "BREACH W34–W36 · 3 wk", fill: 0.94 }],
3123
- ["L1-M03", { a: "120 t · FILL", b: "B-208 · 88 t · 73%", fill: 0.73 }],
3124
- ["L1-M04", { a: "120 t · FILL", b: "B-214 · 89 t · 74%", fill: 0.74 }],
3125
- ["LOAD", { a: "MEAN 74 · PEAK 96", b: "BREACH W33 · 1 wk", fill: 0.96 }],
3126
- ["DESPATCH", { a: "NET 1 629 t", b: "2 SHORT WEEKS", fill: 0.55 }],
3127
- ["MILESTONES", { a: "5 MARKS", b: "1 EXCEPTION · W34", fill: 0.2 }],
3128
- ]), DictType(StringType, GutterFacts));
3129
- // A row's id is its series and the path of keys to it — here the
3130
- // entry's one key, which is what the facts are keyed by.
3131
- const f = $.let(facts.get(id.unwrap("entry").path.get(0n)));
3132
- return (_jsxs(Box, { children: [_jsx(Text, { children: f.a }), _jsx(Text, { children: f.b }), _jsx(Box, { width: "108px", children: _jsx(Progress, { value: f.fill.multiply(100.0), size: "xs", tone: "brand" }) })] }));
3133
- }));
3134
- return (_jsx(Plan, { axis: Plan.axis({ window: { min: week(27n), max: week(39n) }, resolution: "week", now: week(31n) }), data: ops, series: [
3135
- Plan.series.span(OpsRow, {
3136
- key: "machines", title: "Machines",
3137
- match: r => r.series.equal("span"),
3138
- label: r => r.label, id: true, expand: r => r.expand,
3139
- runs: r => r.jobs.map((_$, j) => Plan.run({
3140
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
3141
- })),
3142
- }),
3143
- Plan.series.chart(OpsRow, {
3144
- key: "coverage", title: "Coverage",
3145
- match: r => r.series.equal("chart"),
3146
- label: r => r.label, id: true, expand: r => r.expand,
3147
- height: "spark",
3148
- left: { domain: [80, 110], tickValues: [80, 100] },
3149
- layers: r => [
3150
- Plan.layer(Chart.Line(r.points, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
3151
- Chart.refLine({ y: 100, label: "TARGET 100" }),
3152
- ],
3153
- }),
3154
- Plan.series.heat(OpsRow, {
3155
- key: "load", title: "Line load",
3156
- match: r => r.series.equal("heat"),
3157
- label: r => r.label, expand: r => r.expand,
3158
- cells: r => Plan.heatCells(r.cells, { min: 40.0, max: 100.0 }),
3159
- }),
3160
- Plan.series.table(OpsRow, {
3161
- key: "despatch", title: "Despatch",
3162
- match: r => r.series.equal("table"),
3163
- label: r => r.label, expand: r => r.expand,
3164
- cells: r => r.nums,
3165
- }),
3166
- Plan.series.events(OpsRow, {
3167
- key: "milestones", title: "Milestones",
3168
- match: r => r.series.equal("events"),
3169
- label: r => r.label, id: true, expand: r => r.expand,
3170
- marks: r => r.marks,
3171
- }),
3172
- ], expandRender: expandRender, expandGutter: expandGutter, style: { height: "460px" } }));
3173
- }),
3174
- inputs: [],
3175
- });
3176
- // ============================================================================
3177
- // planNarrow — the §10 narrow layout: a phone-width box makes the Plan a
3178
- // review tool, not a canvas (#570)
3179
- // ============================================================================
3180
- /**
3181
- * Below 480px of CONTAINER width — not the viewport: this example is a 360px
3182
- * `<Box>` on a desktop page — the Plan reflows to the §10 layout: three tabs
3183
- * over ONE slice (Groups · Rows · Measures), the group grain as hottest-first
3184
- * strip cards, one group's rows as cards whose head is the row's gutter
3185
- * identity and whose body is its plot on the shared window, chart rows
3186
- * full-width at expanded density. A row declaring `expand` drills in place on
3187
- * a second tap; horizontal pan is two-finger. The DEFINITION is the desktop
3188
- * one — the same `data` + `series`, the same slice — only the box changed.
3189
- */
3190
- export const planNarrow = example({
3191
- keywords: ["Plan", "narrow", "mobile", "phone", "responsive", "compact", "container", "breakpoint", "tabs", "Groups", "Rows", "Measures", "cards", "strip", "hottest", "two-finger", "pan", "review", "cohort", "slice", "§10", "groupToDicts", "raw"],
3192
- description: "The narrow layout — a phone-width box turns the same canvas into a review tool: Groups · Rows · Measures tabs, hottest-first strip cards, rows as cards, charts at expanded density",
3193
- fn: East.function([], UIComponentType, (_$) => {
3194
- const HorizonRow = StructType({ key: StringType, at: DateTimeType, risk: StringType });
3195
- const cfg = Slice.config(HorizonRow, {
3196
- fields: { at: { label: "Despatched", format: { date: "MMM D" } }, risk: { label: "Risk", hints: ["late", "on-time"] } },
3197
- rangeFieldId: "at",
3198
- });
3199
- return (_jsx(Reactive, { children: $ => {
3200
- // Monday of ISO week n, 2026 — window W27–W38 (half-open), now W31.
3201
- const week = $.const(East.function([IntegerType], DateTimeType, ($, n) => {
3202
- const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
3203
- return w1.addWeeks(n.subtract(1n));
3204
- }));
3205
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
3206
- const JobRow = StructType({
3207
- key: StringType, label: StringType,
3208
- start: DateTimeType, end: DateTimeType, state: EventStateType,
3209
- });
3210
- // The raw rows: `series` picks the series, `line` the line a row
3211
- // belongs to, and every envelope field is per-row DATA.
3212
- const OpsRow = StructType({
3213
- series: StringType, line: StringType, label: StringType,
3214
- value: OptionType(StringType), status: OptionType(StatusValueType),
3215
- expand: OptionType(Plan.Types.Expand),
3216
- jobs: ArrayType(JobRow),
3217
- cells: ArrayType(Plan.Types.HeatCell),
3218
- });
3219
- const noJobs = $.const([], ArrayType(JobRow));
3220
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
3221
- const loadPcts = $.const([46.0, 52.0, 58.0, 61.0, 66.0, 72.0, 78.0, 84.0, 90.0, 96.0, 98.0, 92.0], ArrayType(FloatType));
3222
- // Line 2 runs hotter than Line 1 — the Groups tab sorts it first.
3223
- const loadA = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
3224
- at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i).multiply(0.85)),
3225
- label: some(East.Float.printFixed(loadPcts.get(i).multiply(0.85), 0n)),
3226
- })));
3227
- const loadB = $.let(East.Array.generate(12n, Plan.Types.HeatCell, (_$, i) => ({
3228
- at: Plan.at.time(week(i.add(27n))), value: some(loadPcts.get(i)),
3229
- label: some(East.Float.printFixed(loadPcts.get(i), 0n)),
3230
- })));
3231
- const covPcts = $.const([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2], ArrayType(FloatType));
3232
- const coverage = $.let(East.Array.generate(12n, MeasureRow, (_$, i) => ({ week: week(i.add(27n)), pct: covPcts.get(i) })));
3233
- const ops = $.const(new Map([
3234
- ["L1-M03", { series: "mach", line: "Line 1 · Fill", label: "L1-M03",
3235
- value: some("120 t"), status: some(variant("success", null)),
3236
- expand: some({ height: some("140px"), axis: variant("keep", null) }),
3237
- jobs: [
3238
- { key: "b208", label: "RUN · B-208", start: week(27n), end: week(30n), state: variant("actual", null) },
3239
- { key: "qc", label: "QC", start: week(30n), end: week(32n), state: variant("confirmed", null) },
3240
- { key: "b231", label: "RUN · B-231", start: week(33n), end: week(38n), state: variant("proposed", variant("recommended", null)) },
3241
- ], cells: noCells }],
3242
- ["L1-M04", { series: "mach", line: "Line 1 · Fill", label: "L1-M04",
3243
- value: some("120 t"), status: some(variant("warning", null)), expand: none,
3244
- jobs: [
3245
- { key: "b214", label: "RUN · B-214", start: week(28n), end: week(33n), state: variant("in-progress", null) },
3246
- ], cells: noCells }],
3247
- ["l1-load", { series: "load", line: "Line 1 · Fill", label: "Line load",
3248
- value: none, status: none, expand: none,
3249
- jobs: noJobs, cells: loadA }],
3250
- ["L2-M11", { series: "mach", line: "Line 2 · Assy", label: "L2-M11",
3251
- value: some("80 t"), status: none, expand: none,
3252
- jobs: [
3253
- { key: "b241", label: "RUN · B-241", start: week(29n), end: week(34n), state: variant("confirmed", null) },
3254
- ], cells: noCells }],
3255
- ["l2-load", { series: "load", line: "Line 2 · Assy", label: "Line load",
3256
- value: none, status: some(variant("warning", null)), expand: none,
3257
- jobs: noJobs, cells: loadB }],
3258
- ]), DictType(StringType, OpsRow));
3259
- // Grouping is a DATA step (#822): one `groupToDicts` makes each
3260
- // line an entry holding its rows — the strips nest exactly those.
3261
- const lines = $.let(ops.groupToDicts(($, r) => r.line, ($, _r, k) => k));
3262
- const LineGroup = DictType(StringType, OpsRow);
3263
- // The slice's own rows — despatch orders with a late-risk cohort
3264
- // seeded active, so the chips row has something to say.
3265
- const horizon = $.let(East.Array.generate(24n, HorizonRow, (_$, i) => ({
3266
- key: East.str `h${East.print(i.add(1n))}`,
3267
- at: week(i.divide(2n).add(27n)),
3268
- risk: i.remainder(3n).equal(0n).ifElse(() => "late", () => "on-time"),
3269
- })));
3270
- // A slice range is CLOSED — both ends inclusive — so the twelve
3271
- // weeks W27–W38 end the millisecond before W39.
3272
- const slice = $.let(Slice.bind([HorizonRow], "ex.plan.narrow", cfg, Slice.state({
3273
- range: some(variant("datetime", { from: week(27n), to: week(39n).addMilliseconds(-1n) })),
3274
- cohorts: [{ id: "late", name: "Late risk", filters: [variant("string", { fieldId: "risk", op: variant("eq", "late") })] }],
3275
- activeCohorts: new Set(["late"]),
3276
- }), horizon, none));
3277
- const series = $.const([
3278
- // The KPI no line holds — a hand-built chart row over the
3279
- // coverage points: it rides the Measures tab and an "Other
3280
- // rows" card.
3281
- Plan.series.rows(LineGroup, { key: "kpi", title: "Coverage" }, [
3282
- Plan.chart({
3283
- key: "coverage", label: "Coverage", id: true, value: "94.2%",
3284
- height: "spark",
3285
- left: { domain: [80, 110], tickValues: [80, 100] },
3286
- layers: [
3287
- Plan.layer(Chart.Line(coverage, { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
3288
- Chart.refLine({ y: 100, label: "TARGET 100" }),
3289
- ],
3290
- }),
3291
- ]),
3292
- // One strip per line, its rows stepped down into; its summary
3293
- // strip is the max of its heat rows — what "hottest first" reads.
3294
- Plan.series.group(LineGroup, {
3295
- key: "lines", title: "Lines",
3296
- label: (_g, line) => line,
3297
- summaryAggregate: "max",
3298
- children: Plan.children((g) => g, [
3299
- Plan.series.span(OpsRow, {
3300
- key: "mach", title: "Machines",
3301
- match: r => r.series.equal("mach"),
3302
- label: r => r.label, id: true,
3303
- value: r => r.value, status: r => r.status, expand: r => r.expand,
3304
- runs: r => r.jobs.map((_$, j) => Plan.run({
3305
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
3306
- })),
3307
- }),
3308
- Plan.series.heat(OpsRow, {
3309
- key: "load", title: "Line load",
3310
- match: r => r.series.equal("load"),
3311
- label: r => r.label, status: r => r.status,
3312
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 95 }),
3313
- }),
3314
- ]),
3315
- }),
3316
- ], ArrayType(Plan.Types.Series(LineGroup)));
3317
- const util = $.let(East.Array.generate(12n, FloatType, (_$, i) => i.multiply(19n).remainder(48n).toFloat().add(50.0)));
3318
- const expandRender = $.const(East.function([Plan.Types.RowId], UIComponentType, (_$, _id) => (_jsx(Sparkline, { data: util, type: "area", color: "link", width: "100%", height: "100%" }))));
3319
- const axis = $.const(Plan.axis({
3320
- window: { min: week(27n), max: week(39n) },
3321
- resolution: "week", resolutions: ["month", "week", "day"], now: week(31n),
3322
- }));
3323
- // The 360px box is the whole point: the reflow is a property of
3324
- // the CONTAINER, so a phone, a splitter pane and this box agree.
3325
- return (_jsx(Box, { width: "360px", children: _jsx(Plan, { axis: axis, data: lines, series: series, slice: { slice, affordances: ["cohort", "filter", "range", "resolution", "summary"] }, expandRender: expandRender, footer: [
3326
- { text: "6 ROWS · 2 LINES" },
3327
- { text: "RUN 412 · W27–W38", end: true },
3328
- ], style: { height: "560px" } }) }));
3329
- } }));
3330
- }),
3331
- inputs: [],
3332
- });
3333
- // ============================================================================
3334
- // planNumberAxis — the `number` axis (#631): day 1..8 with AM/PM lanes
3335
- // ============================================================================
3336
- /**
3337
- * The `number` axis — the retired Planner's `plannerPoint` canvas on the
3338
- * Plan: eight days at step 1, AM/PM lanes in the bucket rows, `now` at day
3339
- * 5. Every kind positions on the one numeric scale, and every instant in the
3340
- * raw data is a plain number: a `FloatType` field wraps to the `number` arm
3341
- * through the element builders (`Plan.run({ start: j.start })`), a chart
3342
- * layer's numeric x accessor lands its columns on the same arm, `Plan.tableCells`
3343
- * reads a numeric `at`, and the heat cells stored as RECORDS spell it out with
3344
- * `Plan.at.number`. The slice's range field is an integer day, so the horizon
3345
- * brush and the range chip ride the slice's `integer` arm exactly as they ride
3346
- * `datetime` on a time axis — a closed range, so the eight days `[1, 9)` read
3347
- * `1–8`; there is no resolution segment — `step` is the declaration.
3348
- */
3349
- export const planNumberAxis = example({
3350
- keywords: [
3351
- "Plan", "axis", "number", "numeric", "step", "Plan.axis.number", "Plan.at", "instant",
3352
- "day", "AM", "PM", "lanes", "buckets", "Planner", "plannerPoint", "brush", "integer", "range", "inclusive",
3353
- "format", "now", "typed axis", "#631", "raw",
3354
- ],
3355
- description: "The number axis — day 1..8 at step 1 with AM/PM lanes (the retired Planner's plannerPoint), every row kind on the numeric scale, the horizon brush over the slice's integer range, read inclusive",
3356
- fn: East.function([], UIComponentType, (_$) => {
3357
- const HorizonRow = StructType({ key: StringType, day: IntegerType, line: StringType });
3358
- const cfg = Slice.config(HorizonRow, {
3359
- fields: { day: { label: "Day" }, line: { label: "Line" } },
3360
- rangeFieldId: "day",
3361
- });
3362
- return (_jsx(Reactive, { children: $ => {
3363
- // RAW rows — every instant is a plain number (a day index).
3364
- const AllocRow = StructType({ key: StringType, day: FloatType, lane: StringType, state: EventStateType });
3365
- const JobRow = StructType({ key: StringType, label: StringType, start: FloatType, end: FloatType, state: EventStateType });
3366
- const ShiftRow = StructType({ key: StringType, from: FloatType, to: FloatType, hours: FloatType, state: EventStateType });
3367
- const MarkRow = StructType({ key: StringType, day: FloatType, label: StringType });
3368
- const PointRow = StructType({ day: FloatType, t: FloatType });
3369
- const RawCell = StructType({ at: FloatType, value: OptionType(FloatType) });
3370
- const OpsRow = StructType({
3371
- series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
3372
- allocations: ArrayType(AllocRow), jobs: ArrayType(JobRow), shifts: ArrayType(ShiftRow),
3373
- marks: ArrayType(MarkRow), points: ArrayType(PointRow), tonnes: ArrayType(RawCell),
3374
- cells: ArrayType(Plan.Types.HeatCell),
3375
- });
3376
- const noAllocs = $.const([], ArrayType(AllocRow));
3377
- const noJobs = $.const([], ArrayType(JobRow));
3378
- const noShifts = $.const([], ArrayType(ShiftRow));
3379
- const noMarks = $.const([], ArrayType(MarkRow));
3380
- const noPoints = $.const([], ArrayType(PointRow));
3381
- const noTonnes = $.const([], ArrayType(RawCell));
3382
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
3383
- const CONFIRMED = variant("confirmed", null);
3384
- const ACTUAL = variant("actual", null);
3385
- const RUNNING = variant("in-progress", null);
3386
- const PROPOSED = variant("proposed", variant("recommended", null));
3387
- // Per-day series over days 1..8 — derived, never hand-written.
3388
- const points = $.let(East.Array.generate(8n, PointRow, (_$, i) => ({ day: i.toFloat().add(1.0), t: i.multiply(17n).remainder(40n).toFloat().add(60.0) })));
3389
- const tonnes = $.let(East.Array.generate(8n, RawCell, (_$, i) => ({ at: i.toFloat().add(1.0), value: some(i.multiply(23n).remainder(70n).toFloat().add(80.0)) })));
3390
- // Heat cells as STORED records — the instant spelled explicitly.
3391
- const cells = $.let(East.Array.generate(8n, Plan.Types.HeatCell, ($, i) => {
3392
- const load = $.let(i.multiply(29n).remainder(55n).toFloat().add(40.0), FloatType);
3393
- return { at: Plan.at.number(i.toFloat().add(1.0)), value: some(load), label: some(East.Float.printFixed(load, 0n)) };
3394
- }));
3395
- const base = {
3396
- value: none, sub: none, allocations: noAllocs, jobs: noJobs, shifts: noShifts,
3397
- marks: noMarks, points: noPoints, tonnes: noTonnes, cells: noCells,
3398
- };
3399
- // The keys are the entries' identities; the series list below is
3400
- // the layout.
3401
- const ops = $.const(new Map([
3402
- ["dock2", { ...base, series: "dock", label: "Dock 2", sub: some("load/day"), allocations: [
3403
- { key: "a1", day: 1.0, lane: "am", state: CONFIRMED },
3404
- { key: "a2", day: 1.0, lane: "pm", state: CONFIRMED },
3405
- { key: "a3", day: 2.0, lane: "am", state: CONFIRMED },
3406
- { key: "a4", day: 3.0, lane: "pm", state: PROPOSED },
3407
- { key: "a5", day: 5.0, lane: "am", state: PROPOSED },
3408
- { key: "a6", day: 6.0, lane: "pm", state: CONFIRMED },
3409
- { key: "a7", day: 8.0, lane: "am", state: PROPOSED },
3410
- ] }],
3411
- ["dock5", { ...base, series: "dock", label: "Dock 5", sub: some("load/day"), allocations: [
3412
- { key: "b1", day: 2.0, lane: "pm", state: CONFIRMED },
3413
- { key: "b2", day: 4.0, lane: "am", state: CONFIRMED },
3414
- { key: "b3", day: 4.0, lane: "pm", state: PROPOSED },
3415
- { key: "b4", day: 7.0, lane: "am", state: PROPOSED },
3416
- ] }],
3417
- ["m03", { ...base, series: "mach", label: "L1-M03", value: some("120 t"), jobs: [
3418
- { key: "set", label: "SET", start: 1.0, end: 2.0, state: ACTUAL },
3419
- { key: "b214", label: "RUN · B-214", start: 2.0, end: 5.0, state: RUNNING },
3420
- { key: "b221", label: "RUN · B-221", start: 6.0, end: 8.0, state: PROPOSED },
3421
- ] }],
3422
- ["m04", { ...base, series: "mach", label: "L1-M04", value: some("80 t"), jobs: [
3423
- { key: "b208", label: "RUN · B-208", start: 1.0, end: 4.0, state: ACTUAL },
3424
- { key: "qc", label: "QC", start: 4.0, end: 5.0, state: CONFIRMED },
3425
- { key: "b231", label: "RUN · B-231", start: 5.0, end: 9.0, state: PROPOSED },
3426
- ] }],
3427
- ["load", { ...base, series: "load", label: "Line load", cells }],
3428
- ["out", { ...base, series: "out", label: "Output · t", value: some("612 t"), points }],
3429
- ["desp", { ...base, series: "table", label: "Despatch t", tonnes }],
3430
- ["crewA", { ...base, series: "crew", label: "Crew A", shifts: [
3431
- { key: "s1", from: 1.0, to: 3.0, hours: 24.0, state: CONFIRMED },
3432
- { key: "s2", from: 3.0, to: 6.0, hours: 36.0, state: CONFIRMED },
3433
- { key: "s3", from: 6.0, to: 8.0, hours: 24.0, state: PROPOSED },
3434
- ] }],
3435
- ["ms", { ...base, series: "ms", label: "Milestones", marks: [
3436
- { key: "kick", day: 2.0, label: "KICKOFF" },
3437
- { key: "rel", day: 6.0, label: "REL" },
3438
- ] }],
3439
- ]), DictType(StringType, OpsRow));
3440
- const series = $.const([
3441
- Plan.series.section(OpsRow, { key: "docks-in", title: "Docks · In", meta: "2 rows" }, [
3442
- Plan.series.buckets(OpsRow, {
3443
- key: "dock", title: "Docks",
3444
- match: r => r.series.equal("dock"),
3445
- label: r => r.label, sub: r => r.sub,
3446
- // The Planner's AM/PM lanes — sub-slots of each day column.
3447
- lanes: _r => [Plan.lane({ key: "am", label: "AM" }), Plan.lane({ key: "pm", label: "PM" })],
3448
- // `a.day` is a FloatType field — the builder wraps it to the number arm.
3449
- events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.day, lane: a.lane, state: a.state })),
3450
- }),
3451
- ]),
3452
- Plan.series.span(OpsRow, {
3453
- key: "mach", title: "Machines",
3454
- match: r => r.series.equal("mach"),
3455
- label: r => r.label, id: true, value: r => r.value,
3456
- runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
3457
- }),
3458
- Plan.series.heat(OpsRow, {
3459
- key: "load", title: "Line load",
3460
- match: r => r.series.equal("load"),
3461
- label: r => r.label,
3462
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
3463
- }),
3464
- Plan.series.chart(OpsRow, {
3465
- key: "out", title: "Output",
3466
- match: r => r.series.equal("out"),
3467
- label: r => r.label, id: true, value: r => r.value, height: "expanded",
3468
- // A numeric x accessor lands the columns on the number arm.
3469
- layers: r => [Chart.Column(r.points, { x: p => p.day, y: p => p.t })],
3470
- }),
3471
- Plan.series.table(OpsRow, {
3472
- key: "table", title: "Despatch",
3473
- match: r => r.series.equal("table"),
3474
- label: r => r.label,
3475
- // A `{ at: FloatType, value }` record wraps by its field type.
3476
- cells: r => Plan.tableCells(r.tonnes),
3477
- format: Format.Number({ maximumFractionDigits: 0n }),
3478
- }),
3479
- Plan.series.cards(OpsRow, {
3480
- key: "crew", title: "Crews",
3481
- match: r => r.series.equal("crew"),
3482
- label: r => r.label,
3483
- chips: r => r.shifts.map((_$, s) => Plan.chip({
3484
- key: s.key, from: s.from, to: s.to,
3485
- label: East.str `${East.Float.printFixed(s.hours, 0n)}h`, state: s.state,
3486
- })),
3487
- }),
3488
- Plan.series.events(OpsRow, {
3489
- key: "ms", title: "Milestones",
3490
- match: r => r.series.equal("ms"),
3491
- label: r => r.label, id: true,
3492
- marks: r => r.marks.map((_$, m) => Plan.mark({ key: m.key, at: m.day, kind: "milestone", label: m.label })),
3493
- }),
3494
- ], ArrayType(Plan.Types.Series(OpsRow)));
3495
- // The slice's horizon — twelve days of orders behind an eight-day
3496
- // window. Its range field is an INTEGER day, so the brush, the
3497
- // range chip and the window keys write the slice's `integer` arm:
3498
- // a closed range, both ends inclusive — the eight days `[1, 9)`
3499
- // are `1–8`.
3500
- const horizon = $.let(East.Array.generate(24n, HorizonRow, (_$, i) => ({
3501
- key: East.str `o${East.print(i.add(1n))}`,
3502
- day: i.divide(2n).add(1n),
3503
- line: i.remainder(2n).equal(0n).ifElse(() => "Line 1", () => "Line 2"),
3504
- })));
3505
- const slice = $.let(Slice.bind([HorizonRow], "ex.plan.number", cfg, Slice.state({
3506
- range: some(variant("integer", { from: 1n, to: 8n })),
3507
- }), horizon, none));
3508
- // The declaration: `[1, 9)` ÷ 1 = eight day columns, the divider at 5.
3509
- const axis = $.const(Plan.axis.number({
3510
- window: { min: 1, max: 9 }, step: 1, now: 5, format: Chart.format.number(),
3511
- }));
3512
- return (_jsx(Plan, { axis: axis, data: ops, series: series, slice: { slice, affordances: ["filter", "range", "brush", "summary"] }, footer: [{ text: "8 STEPS · NOW 5" }] }));
3513
- } }));
3514
- }),
3515
- inputs: [],
3516
- });
3517
- // ============================================================================
3518
- // planOrdinalAxis — the `ordinal` axis (#631): workflow phases
3519
- // ============================================================================
3520
- /**
3521
- * The `ordinal` axis — a workflow of six phases, each a bucket, in declared
3522
- * order. Instants are the phase VALUES: a run's `start` / `end` are phase
3523
- * names (an interval covers `[start, end]` in phase order — on an ordinal
3524
- * axis the end names the LAST bucket covered, since values are buckets, not
3525
- * edges), a tile sits in a phase, a chart's string x accessor lands its
3526
- * columns on the phase arm, `Plan.tableCells` reads a string `at`, and
3527
- * `Plan.at.ordinal` spells one out where a record is written as data. There
3528
- * is no slice range for an ordinal axis — the list IS the window, so the
3529
- * horizon brush does not mount and the window keys idle; `now` names a phase.
3530
- */
3531
- export const planOrdinalAxis = example({
3532
- keywords: [
3533
- "Plan", "axis", "ordinal", "phase", "phases", "workflow", "stage", "Plan.axis.ordinal",
3534
- "Plan.at", "instant", "values", "list", "span", "buckets", "heat", "chart", "table",
3535
- "cards", "events", "Planner", "typed axis", "#631", "raw",
3536
- ],
3537
- description: "The ordinal axis — six workflow phases as the buckets, in declared order; orders span phases, tiles and cells sit in them, a string x accessor lands a chart on them",
3538
- fn: East.function([], UIComponentType, ($) => {
3539
- const PHASES = $.const(["INTAKE", "PREP", "BUILD", "QC", "PACK", "SHIP"], ArrayType(StringType));
3540
- // RAW rows — every instant is a phase NAME.
3541
- const JobRow = StructType({ key: StringType, label: StringType, start: StringType, end: StringType, state: EventStateType });
3542
- const AllocRow = StructType({ key: StringType, phase: StringType, state: EventStateType });
3543
- const ShiftRow = StructType({ key: StringType, from: StringType, to: StringType, label: StringType, state: EventStateType });
3544
- const MarkRow = StructType({ key: StringType, phase: StringType, label: StringType, exception: BooleanType });
3545
- const PointRow = StructType({ phase: StringType, n: FloatType });
3546
- const RawCell = StructType({ at: StringType, value: OptionType(FloatType) });
3547
- const OrderRow = StructType({
3548
- series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
3549
- jobs: ArrayType(JobRow), allocations: ArrayType(AllocRow), shifts: ArrayType(ShiftRow),
3550
- marks: ArrayType(MarkRow), points: ArrayType(PointRow), counts: ArrayType(RawCell),
3551
- cells: ArrayType(Plan.Types.HeatCell),
3552
- });
3553
- const noJobs = $.const([], ArrayType(JobRow));
3554
- const noAllocs = $.const([], ArrayType(AllocRow));
3555
- const noShifts = $.const([], ArrayType(ShiftRow));
3556
- const noMarks = $.const([], ArrayType(MarkRow));
3557
- const noPoints = $.const([], ArrayType(PointRow));
3558
- const noCounts = $.const([], ArrayType(RawCell));
3559
- const noCells = $.const([], ArrayType(Plan.Types.HeatCell));
3560
- const CONFIRMED = variant("confirmed", null);
3561
- const ACTUAL = variant("actual", null);
3562
- const RUNNING = variant("in-progress", null);
3563
- const PROPOSED = variant("proposed", variant("recommended", null));
3564
- // Per-phase series derived over the declared list — the index picks the value.
3565
- const points = $.let(East.Array.generate(6n, PointRow, (_$, i) => ({ phase: PHASES.get(i), n: i.multiply(7n).remainder(20n).toFloat().add(4.0) })));
3566
- const counts = $.let(East.Array.generate(6n, RawCell, (_$, i) => ({
3567
- at: PHASES.get(i),
3568
- value: i.equal(4n).ifElse(() => none, () => some(i.multiply(11n).remainder(30n).toFloat().add(12.0))),
3569
- })));
3570
- // Heat cells as STORED records — the phase spelled with `Plan.at.ordinal`.
3571
- const cells = $.let(East.Array.generate(6n, Plan.Types.HeatCell, ($, i) => {
3572
- const load = $.let(i.multiply(31n).remainder(60n).toFloat().add(35.0), FloatType);
3573
- return { at: Plan.at.ordinal(PHASES.get(i)), value: some(load), label: some(East.Float.printFixed(load, 0n)) };
3574
- }));
3575
- const base = {
3576
- value: none, sub: none, jobs: noJobs, allocations: noAllocs, shifts: noShifts,
3577
- marks: noMarks, points: noPoints, counts: noCounts, cells: noCells,
3578
- };
3579
- const orders = $.const(new Map([
3580
- ["or-1188", { ...base, series: "order", label: "OR-1188", value: some("96 t"), jobs: [
3581
- { key: "prep", label: "PREP", start: "INTAKE", end: "PREP", state: ACTUAL },
3582
- { key: "build", label: "BUILD · B-214", start: "BUILD", end: "QC", state: RUNNING },
3583
- { key: "ship", label: "PACK + SHIP", start: "PACK", end: "SHIP", state: PROPOSED },
3584
- ] }],
3585
- ["or-1204", { ...base, series: "order", label: "OR-1204", value: some("54 t"), jobs: [
3586
- { key: "intake", label: "INTAKE", start: "INTAKE", end: "INTAKE", state: ACTUAL },
3587
- { key: "build", label: "BUILD · B-221", start: "PREP", end: "PACK", state: PROPOSED },
3588
- ] }],
3589
- ["bench", { ...base, series: "bench", label: "Bench 2", sub: some("slots"), allocations: [
3590
- { key: "a1", phase: "PREP", state: CONFIRMED }, { key: "a2", phase: "BUILD", state: CONFIRMED },
3591
- { key: "a3", phase: "BUILD", state: PROPOSED }, { key: "a4", phase: "PACK", state: PROPOSED },
3592
- ] }],
3593
- ["load", { ...base, series: "load", label: "Phase load", cells }],
3594
- ["wip", { ...base, series: "wip", label: "WIP · orders", value: some("31"), points }],
3595
- ["count", { ...base, series: "count", label: "Orders in phase", counts }],
3596
- ["crew", { ...base, series: "crew", label: "Crew B", shifts: [
3597
- { key: "s1", from: "INTAKE", to: "PREP", label: "prep crew", state: CONFIRMED },
3598
- { key: "s2", from: "BUILD", to: "SHIP", label: "+ finish crew", state: PROPOSED },
3599
- ] }],
3600
- ["gates", { ...base, series: "gates", label: "Gates", marks: [
3601
- { key: "g1", phase: "QC", label: "HOLD", exception: true },
3602
- { key: "g2", phase: "SHIP", label: "RELEASE", exception: false },
3603
- ] }],
3604
- ]), DictType(StringType, OrderRow));
3605
- const EXCEPTION = $.const(variant("exception", null), Plan.Types.EventMarkKind);
3606
- const MILESTONE = $.const(variant("milestone", null), Plan.Types.EventMarkKind);
3607
- const series = $.const([
3608
- Plan.series.section(OrderRow, { key: "order-block", title: "Orders", meta: "2 rows" }, [
3609
- Plan.series.span(OrderRow, {
3610
- key: "order", title: "Orders",
3611
- match: r => r.series.equal("order"),
3612
- label: r => r.label, id: true, value: r => r.value,
3613
- // `j.start` / `j.end` are StringType fields — the builder wraps them to the ordinal arm.
3614
- runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
3615
- }),
3616
- ]),
3617
- Plan.series.buckets(OrderRow, {
3618
- key: "bench", title: "Benches",
3619
- match: r => r.series.equal("bench"),
3620
- label: r => r.label, sub: r => r.sub,
3621
- events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.phase, state: a.state })),
3622
- }),
3623
- Plan.series.heat(OrderRow, {
3624
- key: "load", title: "Phase load",
3625
- match: r => r.series.equal("load"),
3626
- label: r => r.label,
3627
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
3628
- }),
3629
- Plan.series.chart(OrderRow, {
3630
- key: "wip", title: "WIP",
3631
- match: r => r.series.equal("wip"),
3632
- label: r => r.label, id: true, value: r => r.value, height: "expanded",
3633
- // A string x accessor lands the columns on the ordinal arm.
3634
- layers: r => [Chart.Column(r.points, { x: p => p.phase, y: p => p.n })],
3635
- }),
3636
- Plan.series.table(OrderRow, {
3637
- key: "count", title: "Counts",
3638
- match: r => r.series.equal("count"),
3639
- label: r => r.label,
3640
- cells: r => Plan.tableCells(r.counts),
3641
- format: Format.Number({ maximumFractionDigits: 0n }),
3642
- }),
3643
- Plan.series.cards(OrderRow, {
3644
- key: "crew", title: "Crews",
3645
- match: r => r.series.equal("crew"),
3646
- label: r => r.label,
3647
- chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
3648
- }),
3649
- Plan.series.events(OrderRow, {
3650
- key: "gates", title: "Gates",
3651
- match: r => r.series.equal("gates"),
3652
- label: r => r.label, id: true,
3653
- marks: r => r.marks.map((_$, m) => Plan.mark({
3654
- key: m.key, at: m.phase, label: m.label,
3655
- kind: m.exception.ifElse(() => EXCEPTION, () => MILESTONE),
3656
- })),
3657
- }),
3658
- ], ArrayType(Plan.Types.Series(OrderRow)));
3659
- // The declaration: the list IS the axis — one bucket per phase, `now` at BUILD.
3660
- const axis = $.const(Plan.axis.ordinal({ values: PHASES, now: "BUILD" }));
3661
- return (_jsx(Plan, { axis: axis, data: orders, series: series, footer: [{ text: "6 PHASES · NOW BUILD" }] }));
3662
- }),
3663
- inputs: [],
3664
- });
3665
- //# sourceMappingURL=plan.examples.js.map