@elaraai/e3-ui 1.0.98 → 1.0.99

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 (381) hide show
  1. package/README.md +0 -2
  2. package/dist/src/allocation/adapters.d.ts +1 -1
  3. package/dist/src/allocation/adapters.js +1 -1
  4. package/dist/src/allocation/index.d.ts +46 -90
  5. package/dist/src/allocation/index.d.ts.map +1 -1
  6. package/dist/src/allocation/index.js +5 -5
  7. package/dist/src/allocation/index.js.map +1 -1
  8. package/dist/src/bind/data-indexed.d.ts +273 -51
  9. package/dist/src/bind/data-indexed.d.ts.map +1 -1
  10. package/dist/src/bind/data-indexed.js +233 -56
  11. package/dist/src/bind/data-indexed.js.map +1 -1
  12. package/dist/src/bind/data.d.ts +48 -9
  13. package/dist/src/bind/data.d.ts.map +1 -1
  14. package/dist/src/bind/data.js +48 -3
  15. package/dist/src/bind/data.js.map +1 -1
  16. package/dist/src/bind/record.d.ts +61 -24
  17. package/dist/src/bind/record.d.ts.map +1 -1
  18. package/dist/src/bind/record.js +17 -57
  19. package/dist/src/bind/record.js.map +1 -1
  20. package/dist/src/calendar/index.d.ts +55 -95
  21. package/dist/src/calendar/index.d.ts.map +1 -1
  22. package/dist/src/calendar/index.js +10 -6
  23. package/dist/src/calendar/index.js.map +1 -1
  24. package/dist/src/flowchart/index.d.ts +35 -31
  25. package/dist/src/flowchart/index.d.ts.map +1 -1
  26. package/dist/src/flowchart/index.js +35 -31
  27. package/dist/src/flowchart/index.js.map +1 -1
  28. package/dist/src/flowchart/library.d.ts +108 -84
  29. package/dist/src/flowchart/library.d.ts.map +1 -1
  30. package/dist/src/flowchart/library.js +108 -84
  31. package/dist/src/flowchart/library.js.map +1 -1
  32. package/dist/src/flowchart/payload.d.ts +39 -107
  33. package/dist/src/flowchart/payload.d.ts.map +1 -1
  34. package/dist/src/flowchart/payload.js +3 -3
  35. package/dist/src/flowchart/payload.js.map +1 -1
  36. package/dist/src/flowchart/values.d.ts +35 -31
  37. package/dist/src/flowchart/values.d.ts.map +1 -1
  38. package/dist/src/flowchart/values.js +35 -31
  39. package/dist/src/flowchart/values.js.map +1 -1
  40. package/dist/src/flowchart/window.d.ts +0 -12
  41. package/dist/src/flowchart/window.d.ts.map +1 -1
  42. package/dist/src/flowchart/window.js +3 -2
  43. package/dist/src/flowchart/window.js.map +1 -1
  44. package/dist/src/index.d.ts +2 -2
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +1 -1
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/internal.d.ts +7 -3
  49. package/dist/src/internal.d.ts.map +1 -1
  50. package/dist/src/internal.js +12 -3
  51. package/dist/src/internal.js.map +1 -1
  52. package/dist/src/plan/assemble.d.ts +8 -0
  53. package/dist/src/plan/assemble.d.ts.map +1 -1
  54. package/dist/src/plan/assemble.js +1 -1
  55. package/dist/src/plan/assemble.js.map +1 -1
  56. package/dist/src/plan/builders.d.ts +1518 -2
  57. package/dist/src/plan/builders.d.ts.map +1 -1
  58. package/dist/src/plan/builders.js +248 -114
  59. package/dist/src/plan/builders.js.map +1 -1
  60. package/dist/src/plan/event-rows.d.ts +9 -14
  61. package/dist/src/plan/event-rows.d.ts.map +1 -1
  62. package/dist/src/plan/event-rows.js +6 -46
  63. package/dist/src/plan/event-rows.js.map +1 -1
  64. package/dist/src/plan/factories.d.ts +72 -4
  65. package/dist/src/plan/factories.d.ts.map +1 -1
  66. package/dist/src/plan/factories.js +147 -1
  67. package/dist/src/plan/factories.js.map +1 -1
  68. package/dist/src/plan/index.d.ts +147 -125
  69. package/dist/src/plan/index.d.ts.map +1 -1
  70. package/dist/src/plan/index.js +135 -122
  71. package/dist/src/plan/index.js.map +1 -1
  72. package/dist/src/plan/ir.d.ts +1105 -44
  73. package/dist/src/plan/ir.d.ts.map +1 -1
  74. package/dist/src/plan/ir.js +5 -0
  75. package/dist/src/plan/ir.js.map +1 -1
  76. package/dist/src/plan/library.d.ts +65 -42
  77. package/dist/src/plan/library.d.ts.map +1 -1
  78. package/dist/src/plan/library.js +65 -42
  79. package/dist/src/plan/library.js.map +1 -1
  80. package/dist/src/plan/nested-checks.d.ts +146 -0
  81. package/dist/src/plan/nested-checks.d.ts.map +1 -0
  82. package/dist/src/plan/nested-checks.js +468 -0
  83. package/dist/src/plan/nested-checks.js.map +1 -0
  84. package/dist/src/plan/nested-patch.d.ts +35 -0
  85. package/dist/src/plan/nested-patch.d.ts.map +1 -0
  86. package/dist/src/plan/nested-patch.js +143 -0
  87. package/dist/src/plan/nested-patch.js.map +1 -0
  88. package/dist/src/plan/nested-read.d.ts +3250 -0
  89. package/dist/src/plan/nested-read.d.ts.map +1 -0
  90. package/dist/src/plan/nested-read.js +163 -0
  91. package/dist/src/plan/nested-read.js.map +1 -0
  92. package/dist/src/plan/nested.d.ts +280 -0
  93. package/dist/src/plan/nested.d.ts.map +1 -0
  94. package/dist/src/plan/nested.js +6 -0
  95. package/dist/src/plan/nested.js.map +1 -0
  96. package/dist/src/plan/ordered-source.d.ts +1030 -1
  97. package/dist/src/plan/ordered-source.d.ts.map +1 -1
  98. package/dist/src/plan/ordered-source.js +48 -6
  99. package/dist/src/plan/ordered-source.js.map +1 -1
  100. package/dist/src/plan/over.d.ts +111 -75
  101. package/dist/src/plan/over.d.ts.map +1 -1
  102. package/dist/src/plan/over.js +111 -75
  103. package/dist/src/plan/over.js.map +1 -1
  104. package/dist/src/plan/pick.d.ts +15 -3
  105. package/dist/src/plan/pick.d.ts.map +1 -1
  106. package/dist/src/plan/pick.js +75 -36
  107. package/dist/src/plan/pick.js.map +1 -1
  108. package/dist/src/plan/plan.d.ts +2719 -558
  109. package/dist/src/plan/plan.d.ts.map +1 -1
  110. package/dist/src/plan/plan.js +14 -2
  111. package/dist/src/plan/plan.js.map +1 -1
  112. package/dist/src/plan/reads.d.ts +421 -0
  113. package/dist/src/plan/reads.d.ts.map +1 -0
  114. package/dist/src/plan/reads.js +143 -0
  115. package/dist/src/plan/reads.js.map +1 -0
  116. package/dist/src/plan/refs.d.ts +111 -75
  117. package/dist/src/plan/refs.d.ts.map +1 -1
  118. package/dist/src/plan/refs.js +111 -75
  119. package/dist/src/plan/refs.js.map +1 -1
  120. package/dist/src/plan/root.d.ts +10 -5
  121. package/dist/src/plan/root.d.ts.map +1 -1
  122. package/dist/src/plan/root.js +76 -13
  123. package/dist/src/plan/root.js.map +1 -1
  124. package/dist/src/plan/series.d.ts +5140 -4331
  125. package/dist/src/plan/series.d.ts.map +1 -1
  126. package/dist/src/plan/series.js +1026 -264
  127. package/dist/src/plan/series.js.map +1 -1
  128. package/dist/src/plan/shade.d.ts +981 -0
  129. package/dist/src/plan/shade.d.ts.map +1 -0
  130. package/dist/src/plan/shade.js +60 -0
  131. package/dist/src/plan/shade.js.map +1 -0
  132. package/dist/src/plan/types.d.ts +178 -18
  133. package/dist/src/plan/types.d.ts.map +1 -1
  134. package/dist/src/plan/types.js +69 -19
  135. package/dist/src/plan/types.js.map +1 -1
  136. package/dist/src/query/builder.d.ts +3 -3
  137. package/dist/src/query/builder.d.ts.map +1 -1
  138. package/dist/src/query/builder.js +1 -1
  139. package/dist/src/query/library.d.ts +3 -3
  140. package/dist/src/query/library.d.ts.map +1 -1
  141. package/dist/src/query/library.js +1 -1
  142. package/dist/src/query/queries.d.ts +2 -2
  143. package/dist/src/query/queries.js +1 -1
  144. package/dist/src/query/saved.d.ts +8 -4
  145. package/dist/src/query/saved.d.ts.map +1 -1
  146. package/dist/src/query/saved.js +8 -4
  147. package/dist/src/query/saved.js.map +1 -1
  148. package/dist/src/query/types.d.ts +8 -3
  149. package/dist/src/query/types.d.ts.map +1 -1
  150. package/dist/src/query/types.js +8 -3
  151. package/dist/src/query/types.js.map +1 -1
  152. package/dist/src/roster/index.d.ts +47 -91
  153. package/dist/src/roster/index.d.ts.map +1 -1
  154. package/dist/src/roster/index.js +6 -6
  155. package/dist/src/roster/index.js.map +1 -1
  156. package/dist/src/roster/targets.d.ts +8 -4
  157. package/dist/src/roster/targets.d.ts.map +1 -1
  158. package/dist/src/roster/targets.js +9 -5
  159. package/dist/src/roster/targets.js.map +1 -1
  160. package/dist/src/schedule/cell-checks.js +3 -3
  161. package/dist/src/schedule/cell-checks.js.map +1 -1
  162. package/dist/src/schedule/days.d.ts +1 -1
  163. package/dist/src/schedule/days.js +1 -1
  164. package/dist/src/schedule/events.d.ts +10 -6
  165. package/dist/src/schedule/events.d.ts.map +1 -1
  166. package/dist/src/schedule/events.js +252 -228
  167. package/dist/src/schedule/events.js.map +1 -1
  168. package/dist/src/schedule/patch.d.ts +1 -1
  169. package/dist/src/schedule/patch.js +1 -1
  170. package/dist/src/schedule/periods.d.ts +8 -4
  171. package/dist/src/schedule/periods.d.ts.map +1 -1
  172. package/dist/src/schedule/periods.js +1 -1
  173. package/dist/src/schedule/periods.js.map +1 -1
  174. package/dist/src/schedule/record-window.d.ts +38 -58
  175. package/dist/src/schedule/record-window.d.ts.map +1 -1
  176. package/dist/src/schedule/record-window.js +15 -16
  177. package/dist/src/schedule/record-window.js.map +1 -1
  178. package/dist/src/schedule/resources.d.ts +1 -1
  179. package/dist/src/schedule/session.d.ts +1 -1
  180. package/dist/src/schedule/session.js +1 -1
  181. package/dist/src/schedule/types.d.ts +29 -1
  182. package/dist/src/schedule/types.d.ts.map +1 -1
  183. package/dist/src/schedule/types.js +16 -1
  184. package/dist/src/schedule/types.js.map +1 -1
  185. package/dist/src/schematic/index.d.ts +19 -9
  186. package/dist/src/schematic/index.d.ts.map +1 -1
  187. package/dist/src/schematic/index.js +19 -9
  188. package/dist/src/schematic/index.js.map +1 -1
  189. package/dist/src/schematic/kinds.d.ts +4 -4
  190. package/dist/src/schematic/kinds.js +9 -9
  191. package/dist/src/schematic/kinds.js.map +1 -1
  192. package/dist/src/schematic/payload.d.ts +38 -88
  193. package/dist/src/schematic/payload.d.ts.map +1 -1
  194. package/dist/src/schematic/payload.js +4 -4
  195. package/dist/src/schematic/payload.js.map +1 -1
  196. package/dist/src/schematic/window.d.ts +8 -7
  197. package/dist/src/schematic/window.d.ts.map +1 -1
  198. package/dist/src/schematic/window.js +22 -19
  199. package/dist/src/schematic/window.js.map +1 -1
  200. package/dist/src/sheet/bridge.d.ts +19 -0
  201. package/dist/src/sheet/bridge.d.ts.map +1 -1
  202. package/dist/src/sheet/bridge.js +28 -21
  203. package/dist/src/sheet/bridge.js.map +1 -1
  204. package/dist/src/sheet/index.d.ts +66 -46
  205. package/dist/src/sheet/index.d.ts.map +1 -1
  206. package/dist/src/sheet/index.js +66 -46
  207. package/dist/src/sheet/index.js.map +1 -1
  208. package/dist/src/sheet/library.d.ts +13 -8
  209. package/dist/src/sheet/library.d.ts.map +1 -1
  210. package/dist/src/sheet/library.js +13 -8
  211. package/dist/src/sheet/library.js.map +1 -1
  212. package/dist/src/sheet/record.d.ts +1 -1
  213. package/dist/src/sheet/record.js +2 -2
  214. package/dist/src/sheet/record.js.map +1 -1
  215. package/dist/src/sheet/registers.d.ts +152 -128
  216. package/dist/src/sheet/registers.d.ts.map +1 -1
  217. package/dist/src/sheet/registers.js +152 -128
  218. package/dist/src/sheet/registers.js.map +1 -1
  219. package/dist/src/sheet/root.d.ts +4 -1
  220. package/dist/src/sheet/root.d.ts.map +1 -1
  221. package/dist/src/sheet/root.js +15 -3
  222. package/dist/src/sheet/root.js.map +1 -1
  223. package/dist/src/sheet/sheet.d.ts +39 -113
  224. package/dist/src/sheet/sheet.d.ts.map +1 -1
  225. package/dist/src/sheet/sheet.js +7 -5
  226. package/dist/src/sheet/sheet.js.map +1 -1
  227. package/dist/src/sheet/sub-rows.d.ts +106 -76
  228. package/dist/src/sheet/sub-rows.d.ts.map +1 -1
  229. package/dist/src/sheet/sub-rows.js +106 -76
  230. package/dist/src/sheet/sub-rows.js.map +1 -1
  231. package/dist/src/sheet/types.d.ts +19 -62
  232. package/dist/src/sheet/types.d.ts.map +1 -1
  233. package/dist/src/studio/builder.d.ts +3 -153
  234. package/dist/src/studio/builder.d.ts.map +1 -1
  235. package/dist/src/studio/builder.js +1 -1
  236. package/dist/src/studio/canvas.d.ts +5 -4
  237. package/dist/src/studio/canvas.d.ts.map +1 -1
  238. package/dist/src/studio/canvas.js +8 -7
  239. package/dist/src/studio/canvas.js.map +1 -1
  240. package/dist/src/studio/component.d.ts +30 -20
  241. package/dist/src/studio/component.d.ts.map +1 -1
  242. package/dist/src/studio/component.js +26 -61
  243. package/dist/src/studio/component.js.map +1 -1
  244. package/dist/src/studio/index.d.ts +2 -2
  245. package/dist/src/studio/index.d.ts.map +1 -1
  246. package/dist/src/studio/index.js +1 -1
  247. package/dist/src/studio/index.js.map +1 -1
  248. package/dist/src/studio/inspector.d.ts +10 -39
  249. package/dist/src/studio/inspector.d.ts.map +1 -1
  250. package/dist/src/studio/inspector.js +11 -31
  251. package/dist/src/studio/inspector.js.map +1 -1
  252. package/dist/src/studio/library.d.ts +3 -169
  253. package/dist/src/studio/library.d.ts.map +1 -1
  254. package/dist/src/studio/library.js +1 -1
  255. package/dist/src/studio/page.d.ts +0 -8
  256. package/dist/src/studio/page.d.ts.map +1 -1
  257. package/dist/src/studio/pages.d.ts +17 -591
  258. package/dist/src/studio/pages.d.ts.map +1 -1
  259. package/dist/src/studio/pages.js +17 -44
  260. package/dist/src/studio/pages.js.map +1 -1
  261. package/dist/src/studio/palette.d.ts +2 -7
  262. package/dist/src/studio/palette.d.ts.map +1 -1
  263. package/dist/src/studio/palette.js +4 -5
  264. package/dist/src/studio/palette.js.map +1 -1
  265. package/dist/src/studio/publish.d.ts +8 -18
  266. package/dist/src/studio/publish.d.ts.map +1 -1
  267. package/dist/src/studio/publish.js +10 -36
  268. package/dist/src/studio/publish.js.map +1 -1
  269. package/dist/src/utils/derive.d.ts +12 -12
  270. package/dist/src/utils/derive.d.ts.map +1 -1
  271. package/dist/src/utils/derive.js +5 -2
  272. package/dist/src/utils/derive.js.map +1 -1
  273. package/dist/src/utils/ordered-source.d.ts.map +1 -1
  274. package/dist/src/utils/ordered-source.js +14 -5
  275. package/dist/src/utils/ordered-source.js.map +1 -1
  276. package/dist/test/allocation/allocation.examples.d.ts +1900 -1598
  277. package/dist/test/allocation/allocation.examples.d.ts.map +1 -1
  278. package/dist/test/allocation/allocation.examples.js +179 -89
  279. package/dist/test/allocation/allocation.examples.js.map +1 -1
  280. package/dist/test/allocation/optimise.examples.d.ts.map +1 -1
  281. package/dist/test/allocation/optimise.examples.js +7 -7
  282. package/dist/test/allocation/optimise.examples.js.map +1 -1
  283. package/dist/test/bind/data/data.examples.d.ts +1057 -903
  284. package/dist/test/bind/data/data.examples.d.ts.map +1 -1
  285. package/dist/test/bind/data/data.examples.js +260 -204
  286. package/dist/test/bind/data/data.examples.js.map +1 -1
  287. package/dist/test/bind/record/record.examples.d.ts +457 -426
  288. package/dist/test/bind/record/record.examples.d.ts.map +1 -1
  289. package/dist/test/bind/record/record.examples.js +41 -21
  290. package/dist/test/bind/record/record.examples.js.map +1 -1
  291. package/dist/test/calendar/calendar-records.examples.d.ts +162 -170
  292. package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -1
  293. package/dist/test/calendar/calendar-records.examples.js +20 -12
  294. package/dist/test/calendar/calendar-records.examples.js.map +1 -1
  295. package/dist/test/calendar/calendar-windows.examples.d.ts.map +1 -1
  296. package/dist/test/calendar/calendar-windows.examples.js +12 -12
  297. package/dist/test/calendar/calendar-windows.examples.js.map +1 -1
  298. package/dist/test/calendar/calendar.examples.d.ts +1172 -901
  299. package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
  300. package/dist/test/calendar/calendar.examples.js +211 -124
  301. package/dist/test/calendar/calendar.examples.js.map +1 -1
  302. package/dist/test/calendar/optimise.examples.js +2 -2
  303. package/dist/test/calendar/optimise.examples.js.map +1 -1
  304. package/dist/test/examples-package.d.ts +6 -9
  305. package/dist/test/examples-package.d.ts.map +1 -1
  306. package/dist/test/examples-package.js +11 -32
  307. package/dist/test/examples-package.js.map +1 -1
  308. package/dist/test/flowchart/flowchart.examples.d.ts +3841 -2820
  309. package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
  310. package/dist/test/flowchart/flowchart.examples.js +211 -121
  311. package/dist/test/flowchart/flowchart.examples.js.map +1 -1
  312. package/dist/test/plan/optimise.examples.js +2 -2
  313. package/dist/test/plan/optimise.examples.js.map +1 -1
  314. package/dist/test/plan/plan-events.examples.d.ts +4055 -3263
  315. package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
  316. package/dist/test/plan/plan-events.examples.js +492 -319
  317. package/dist/test/plan/plan-events.examples.js.map +1 -1
  318. package/dist/test/plan/plan-nested.examples.d.ts +1829 -0
  319. package/dist/test/plan/plan-nested.examples.d.ts.map +1 -0
  320. package/dist/test/plan/plan-nested.examples.js +321 -0
  321. package/dist/test/plan/plan-nested.examples.js.map +1 -0
  322. package/dist/test/plan/plan-records.examples.d.ts +328 -362
  323. package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
  324. package/dist/test/plan/plan-records.examples.js +81 -57
  325. package/dist/test/plan/plan-records.examples.js.map +1 -1
  326. package/dist/test/plan/plan-windows.examples.d.ts +1756 -1398
  327. package/dist/test/plan/plan-windows.examples.d.ts.map +1 -1
  328. package/dist/test/plan/plan-windows.examples.js +161 -128
  329. package/dist/test/plan/plan-windows.examples.js.map +1 -1
  330. package/dist/test/plan/plan.examples.d.ts +2238 -5712
  331. package/dist/test/plan/plan.examples.d.ts.map +1 -1
  332. package/dist/test/plan/plan.examples.js +839 -968
  333. package/dist/test/plan/plan.examples.js.map +1 -1
  334. package/dist/test/query/query.examples.d.ts +5221 -3005
  335. package/dist/test/query/query.examples.d.ts.map +1 -1
  336. package/dist/test/query/query.examples.js +169 -118
  337. package/dist/test/query/query.examples.js.map +1 -1
  338. package/dist/test/roster/optimise.examples.d.ts.map +1 -1
  339. package/dist/test/roster/optimise.examples.js +7 -7
  340. package/dist/test/roster/optimise.examples.js.map +1 -1
  341. package/dist/test/roster/roster-records.examples.d.ts +364 -392
  342. package/dist/test/roster/roster-records.examples.d.ts.map +1 -1
  343. package/dist/test/roster/roster-records.examples.js +40 -26
  344. package/dist/test/roster/roster-records.examples.js.map +1 -1
  345. package/dist/test/roster/roster.examples.d.ts +4605 -4308
  346. package/dist/test/roster/roster.examples.d.ts.map +1 -1
  347. package/dist/test/roster/roster.examples.js +212 -113
  348. package/dist/test/roster/roster.examples.js.map +1 -1
  349. package/dist/test/schedule/schedule.examples.d.ts +2646 -2471
  350. package/dist/test/schedule/schedule.examples.d.ts.map +1 -1
  351. package/dist/test/schedule/schedule.examples.js +144 -88
  352. package/dist/test/schedule/schedule.examples.js.map +1 -1
  353. package/dist/test/schematic/schematic-records.examples.d.ts +286 -276
  354. package/dist/test/schematic/schematic-records.examples.d.ts.map +1 -1
  355. package/dist/test/schematic/schematic-records.examples.js +52 -38
  356. package/dist/test/schematic/schematic-records.examples.js.map +1 -1
  357. package/dist/test/schematic/schematic.examples.d.ts +1842 -1491
  358. package/dist/test/schematic/schematic.examples.d.ts.map +1 -1
  359. package/dist/test/schematic/schematic.examples.js +240 -134
  360. package/dist/test/schematic/schematic.examples.js.map +1 -1
  361. package/dist/test/sheet/optimise.examples.d.ts +102 -95
  362. package/dist/test/sheet/optimise.examples.d.ts.map +1 -1
  363. package/dist/test/sheet/optimise.examples.js +7 -3
  364. package/dist/test/sheet/optimise.examples.js.map +1 -1
  365. package/dist/test/sheet/sheet-records.examples.d.ts +175 -214
  366. package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -1
  367. package/dist/test/sheet/sheet-records.examples.js +29 -17
  368. package/dist/test/sheet/sheet-records.examples.js.map +1 -1
  369. package/dist/test/sheet/sheet.examples.d.ts +3642 -3353
  370. package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
  371. package/dist/test/sheet/sheet.examples.js +389 -282
  372. package/dist/test/sheet/sheet.examples.js.map +1 -1
  373. package/dist/test/studio/component.examples.js +2 -2
  374. package/dist/test/studio/component.examples.js.map +1 -1
  375. package/dist/test/studio/page.examples.js +6 -6
  376. package/dist/test/studio/page.examples.js.map +1 -1
  377. package/dist/test/studio/studio.examples.d.ts +2329 -873
  378. package/dist/test/studio/studio.examples.d.ts.map +1 -1
  379. package/dist/test/studio/studio.examples.js +155 -107
  380. package/dist/test/studio/studio.examples.js.map +1 -1
  381. package/package.json +15 -11
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "@elaraai/e3-ui/jsx-runtime";
4
4
  * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
5
5
  */
6
6
  /** @jsxImportSource @elaraai/e3-ui */
7
- import { ArrayType, BooleanType, DateTimeType, DictType, East, FloatType, IntegerType, NullType, OptionType, RecursiveType, StringType, StructType, VariantType, example, none, some, variant, } from "@elaraai/east";
7
+ import { ArrayType, BooleanType, DateTimeType, DictType, East, FloatType, IntegerType, NullType, OptionType, RecursiveType, SortedMap, StringType, StructType, VariantType, compareFor, example, none, some, variant, } from "@elaraai/east";
8
8
  import { DragEventType, Editing, EventStateType, State, StatusValueType, Style, UIComponentType } from "@elaraai/east-ui";
9
9
  import { Badge, Box, Button, Chart, Configurator, Format, HStack, Progress, Reactive, SegmentGroup, Select, Slice, Sparkline, Text, VStack } from "@elaraai/east-ui";
10
10
  import { Data, Plan, Record } from "@elaraai/e3-ui";
@@ -69,306 +69,309 @@ export const generateTargetHorizon = East.function([IntegerType], ArrayType(Targ
69
69
  * flagship's Deliveries tab lists, the rail's `deliveries` source narrowing
70
70
  * them by hall and risk, its range the canvas's window (#1344).
71
71
  */
72
- export const planTargetDeliveries = e3.record("plan_target_deliveries", DictType(StringType, TargetHorizonRow), East.compile(East.function([IntegerType], DictType(StringType, TargetHorizonRow), ($, count) => {
73
- const generate = $.const(generateTargetHorizon);
74
- return generate(count).toDict((_$2, delivery) => delivery.key, (_$2, delivery) => delivery);
75
- }), [])(36n));
76
- // The deliveries through their indexes, each ending at the delivery's instant, so every narrowing — a hall, a
77
- // risk, both — holds the window too.
78
- export const planTargetDeliveriesByAt = e3.recordIndex("plan_target_deliveries_by_at", planTargetDeliveries, { on: ["at"] });
79
- export const planTargetDeliveriesByRiskAt = e3.recordIndex("plan_target_deliveries_by_risk_at", planTargetDeliveries, { on: ["risk", "at"] });
80
- export const planTargetDeliveriesByHallAt = e3.recordIndex("plan_target_deliveries_by_hall_at", planTargetDeliveries, { on: ["hall", "at"] });
81
- export const planTargetDeliveriesByHallRiskAt = e3.recordIndex("plan_target_deliveries_by_hall_risk_at", planTargetDeliveries, { on: ["hall", "risk", "at"] });
82
- /** A job on a press — the RAW record an ops dataset stores: its phase and
83
- * optional job ticket, its window, optional sheets, lifecycle state and alert. */
72
+ export const planTargetDeliveries = e3.record("plan_target_deliveries", DictType(StringType, TargetHorizonRow), {
73
+ initial: () => East.compile(East.function([IntegerType], DictType(StringType, TargetHorizonRow), ($, count) => {
74
+ const generate = $.const(generateTargetHorizon);
75
+ return generate(count).toDict((_$2, delivery) => delivery.key, (_$2, delivery) => delivery);
76
+ }), [])(36n),
77
+ indexes: (index) => ({
78
+ // The deliveries through their indexes, each ending at the delivery's instant, so every narrowing — a hall, a
79
+ // risk, both — holds the window too.
80
+ plan_target_deliveries_by_at: index.on(["at"]),
81
+ plan_target_deliveries_by_risk_at: index.on(["risk", "at"]),
82
+ plan_target_deliveries_by_hall_at: index.on(["hall", "at"]),
83
+ plan_target_deliveries_by_hall_risk_at: index.on(["hall", "risk", "at"]),
84
+ }),
85
+ });
86
+ /** A job on a press, under its id — the RAW record an ops dataset stores: its
87
+ * phase and optional job ticket, its window, optional sheets, lifecycle state
88
+ * and alert. */
84
89
  export const TargetJob = StructType({
85
- key: StringType, phase: StringType, ticket: OptionType(StringType),
90
+ phase: StringType, ticket: OptionType(StringType),
86
91
  start: DateTimeType, end: DateTimeType,
87
92
  sheets: OptionType(FloatType), state: EventStateType,
88
93
  alert: OptionType(StatusValueType),
89
94
  });
90
- /** A crew's shift — its window, its hours and its lifecycle state. */
91
- export const TargetShift = StructType({
92
- key: StringType, from: DateTimeType, to: DateTimeType,
93
- hours: FloatType, state: EventStateType,
95
+ /** A crew's shift, under its id — its window, its hours and its lifecycle state. */
96
+ export const TargetShift = StructType({ from: DateTimeType, to: DateTimeType, hours: FloatType, state: EventStateType });
97
+ /** A van drop, under its id — its week and its lifecycle state. */
98
+ export const TargetAlloc = StructType({ at: DateTimeType, state: EventStateType });
99
+ /** A decision on a run's transition, under its id — when, and whether it is applied. */
100
+ export const TargetDecision = StructType({ at: DateTimeType, applied: BooleanType });
101
+ /** Sheets moving in or out of a press, at their instant — the caption. */
102
+ export const TargetPort = StructType({ label: StringType });
103
+ /** A van's capacity alert, at its week — how bad, and what it says. */
104
+ export const TargetAlert = StructType({ status: StatusValueType, message: StringType });
105
+ /** A tile's look channels, as a bucket row's tile holds them. */
106
+ const TileLook = Plan.Types.BucketEvent.fields;
107
+ /** A tile stored as it draws, under its id: its week, its lane and every channel of its look. */
108
+ export const TargetTile = StructType({
109
+ at: DateTimeType, lane: TileLook.lane, label: TileLook.label, icon: TileLook.icon, state: TileLook.state,
110
+ tone: TileLook.tone, color: TileLook.color, colorPalette: TileLook.colorPalette, stretch: TileLook.stretch,
111
+ content: TileLook.content, animation: TileLook.animation,
94
112
  });
95
- /** A van drop — its week and its lifecycle state. */
96
- export const TargetAlloc = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
113
+ /** A chip stored as it draws, under its id: its window, its text, its state and its icon. */
114
+ export const TargetChip = StructType({ from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType, icon: Plan.Types.Chip.fields.icon });
115
+ /** A mark on a stream, under its id: its instant, its kind, its icon and its caption. */
116
+ export const TargetMark = StructType({ at: DateTimeType, kind: Plan.Types.EventMarkKind, icon: Plan.Types.EventMark.fields.icon, label: OptionType(StringType) });
97
117
  /**
98
118
  * The ops row — every series' rows in ONE keyed source, discriminated by the
99
- * kind variant (the natural ops-dataset shape). The arms carry RAW fields: a
100
- * measure is its weekly readings from its first week, and presence — a
101
- * status, an expand declaration (`detail`) — is per-row Option data the
102
- * accessors pass through. A row that stands in a hall names it (`hall`), and a
103
- * data step groups those rows into their hall's entry (the `hall` arm, which
104
- * the dataset itself never stores). The row is recursive, so the hierarchy is
105
- * the data's own (#822): a hall holds its members, and a contract the presses
106
- * it books (`presses`), to whatever depth the data has. A press names the span
107
- * series that draws it (`series`): a hall's presses, the proposal flavours,
108
- * the detail line, a contract rolled up by union or by status, a delivery.
119
+ * kind variant (the natural ops-dataset shape). The kind carries a row's own
120
+ * facts; presence — a status, an expand declaration (`detail`) — is per-row
121
+ * Option data the accessors pass through. A row's COLLECTIONS sit at its top,
122
+ * each a Dict a series reads by name (#1436): the things people drag keyed by
123
+ * their ids (`jobs`, `decisions`, `drops`, `tiles`, `shifts`, `chips`,
124
+ * `marks`), a value a week keyed by the week (`ontime`, `load`, `util`,
125
+ * `delivered`, `sheets`), and the rows nested under it. A row that stands in a
126
+ * hall names it (`hall`), and a data step groups those rows into their hall's
127
+ * entry (the `hall` arm, which the dataset itself never stores), its rows its
128
+ * `members`. The row is recursive, so the hierarchy is the data's own (#822):
129
+ * a hall holds its members, and a contract the presses it books (`presses`),
130
+ * to whatever depth the data has. A press names the span series that draws it
131
+ * (`series`): a hall's presses, the proposal flavours, the detail line, a
132
+ * contract rolled up by union or by status, a delivery.
109
133
  */
110
134
  export const TargetOpsRow = RecursiveType((self) => StructType({
111
135
  hall: OptionType(StringType),
112
136
  kind: VariantType({
113
- kpi: StructType({ name: StringType, headline: StringType, pinned: BooleanType,
114
- from: DateTimeType, weekly: ArrayType(FloatType), detail: OptionType(Plan.Types.Expand) }),
137
+ kpi: StructType({ name: StringType, headline: StringType, pinned: BooleanType, detail: OptionType(Plan.Types.Expand) }),
115
138
  press: StructType({ series: StringType, rate: OptionType(FloatType), sub: OptionType(StringType),
116
- status: OptionType(StatusValueType), detail: OptionType(Plan.Types.Expand),
117
- jobs: ArrayType(TargetJob),
118
- decisions: ArrayType(Plan.Types.DecisionMark),
119
- ports: ArrayType(Plan.Types.Port),
120
- presses: DictType(StringType, self) }),
121
- load: StructType({ name: StringType, sub: StringType, from: DateTimeType,
122
- weekly: ArrayType(OptionType(FloatType)), detail: OptionType(Plan.Types.Expand) }),
139
+ status: OptionType(StatusValueType), detail: OptionType(Plan.Types.Expand) }),
140
+ load: StructType({ name: StringType, sub: StringType, detail: OptionType(Plan.Types.Expand) }),
123
141
  van: StructType({ name: StringType, region: StringType, sub: OptionType(StringType), detail: OptionType(Plan.Types.Expand),
124
- lanes: ArrayType(Plan.Types.Lane), drops: ArrayType(TargetAlloc),
125
- tiles: ArrayType(Plan.Types.BucketEvent), markers: ArrayType(Plan.Types.CellMarker) }),
142
+ lanes: ArrayType(Plan.Types.Lane) }),
126
143
  crew: StructType({ name: StringType, pool: BooleanType, sub: OptionType(StringType), value: OptionType(StringType),
127
- detail: OptionType(Plan.Types.Expand), shifts: ArrayType(TargetShift), chips: ArrayType(Plan.Types.Chip) }),
128
- stream: StructType({ name: StringType, cadence: OptionType(StringType), detail: OptionType(Plan.Types.Expand),
129
- marks: ArrayType(Plan.Types.EventMark) }),
130
- tally: StructType({ name: StringType, detail: OptionType(Plan.Types.Expand), cells: ArrayType(Plan.Types.TableCell) }),
131
- finisher: StructType({ name: StringType, jobs: ArrayType(TargetJob), util: ArrayType(FloatType),
132
- sheets: ArrayType(Plan.Types.TableCell) }),
133
- hall: StructType({ members: DictType(StringType, self) }),
144
+ detail: OptionType(Plan.Types.Expand) }),
145
+ stream: StructType({ name: StringType, cadence: OptionType(StringType), detail: OptionType(Plan.Types.Expand) }),
146
+ tally: StructType({ name: StringType, detail: OptionType(Plan.Types.Expand) }),
147
+ finisher: StructType({ name: StringType }),
148
+ hall: NullType,
134
149
  }),
150
+ jobs: DictType(StringType, TargetJob),
151
+ decisions: DictType(StringType, TargetDecision),
152
+ ports: DictType(DateTimeType, TargetPort),
153
+ drops: DictType(StringType, TargetAlloc),
154
+ tiles: DictType(StringType, TargetTile),
155
+ alerts: DictType(DateTimeType, TargetAlert),
156
+ shifts: DictType(StringType, TargetShift),
157
+ chips: DictType(StringType, TargetChip),
158
+ marks: DictType(StringType, TargetMark),
159
+ ontime: DictType(DateTimeType, FloatType),
160
+ load: DictType(DateTimeType, OptionType(FloatType)),
161
+ util: DictType(DateTimeType, FloatType),
162
+ delivered: DictType(DateTimeType, OptionType(FloatType)),
163
+ sheets: DictType(DateTimeType, FloatType),
164
+ presses: DictType(StringType, self),
165
+ members: DictType(StringType, self),
135
166
  }));
167
+ /** Every collection an ops row holds, empty: a row names the few it fills. */
168
+ const TARGET_EMPTY = {
169
+ jobs: new Map(), decisions: new Map(), ports: new Map(), drops: new Map(), tiles: new Map(), alerts: new Map(),
170
+ shifts: new Map(), chips: new Map(), marks: new Map(), ontime: new Map(), load: new Map(), util: new Map(),
171
+ delivered: new Map(), sheets: new Map(), presses: new Map(), members: new Map(),
172
+ };
173
+ /** Values keyed by their instants — East keeps a Dict keyed by DateTime as a `SortedMap` in its order. */
174
+ const atInstants = (entries) => new SortedMap(entries, compareFor(DateTimeType));
175
+ /** A value a week from W27 (Monday 29 June 2026), each keyed by its week's instant. */
176
+ const fromW27 = (weekly) => atInstants(weekly.map((v, i) => [new Date(Date.UTC(2026, 5, 29 + 7 * i)), v]));
136
177
  /**
137
178
  * The ONE ops source — every series' rows in one KEYED collection, RAW: no
138
- * display strings the accessors can derive, no built elements; the instants an
139
- * element record stores ride the axis's `time` arm. A key is its entry's
140
- * identity, never its place: a row's id is the series that made it and the
141
- * path of keys to it (`Plan.ref("presses", "Hall 1", "H1-P03")`), which is
142
- * what `links`, `popover`, `ui` and `onSelect` speak — the layout is the
143
- * series list. Weeks W27–W38.
179
+ * display strings the accessors can derive, no built elements. A key is its
180
+ * entry's identity, never its place: a row's id is the series that made it
181
+ * and the path of keys to it (`Plan.ref("presses", "Hall 1", "H1-P03")`),
182
+ * which is what `links`, `popover`, `ui` and `onSelect` speak — the layout is
183
+ * the series list. Weeks W27–W38.
144
184
  */
145
185
  export const planTargetOps = e3.input("plan_target_ops", DictType(StringType, TargetOpsRow), variant("value", new Map([
146
186
  // ── The pinned KPI ──
147
- ["ontime", { hall: none, kind: variant("kpi", { name: "On-time", headline: "94.2%", pinned: true,
148
- from: new Date("2026-06-29T00:00:00Z"), weekly: [96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2],
149
- detail: some({ height: some("150px"), axis: variant("keep", null) }) }) }],
150
- // ── Deliveries — Van 1's drops are raw, its tiles derived; Van 2's are
151
- // stored tile records in its AM/PM lanes. Van 1's Aug 24 cell holds two
152
- // drops, more than its room: a `+n` holds the rest. ──
153
- ["van1", { hall: none, kind: variant("van", { name: "Van 1", region: "local", sub: some("drops/wk"), detail: none, lanes: [],
154
- drops: [
155
- { key: "a0", at: new Date("2026-06-29T00:00:00Z"), state: variant("confirmed", null) },
156
- { key: "a1", at: new Date("2026-07-06T00:00:00Z"), state: variant("confirmed", null) },
157
- { key: "a2", at: new Date("2026-07-13T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
158
- { key: "a3", at: new Date("2026-07-20T00:00:00Z"), state: variant("confirmed", null) },
159
- { key: "a4", at: new Date("2026-07-27T00:00:00Z"), state: variant("confirmed", null) },
160
- { key: "a5", at: new Date("2026-08-03T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
161
- { key: "a6", at: new Date("2026-08-10T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
162
- { key: "a7", at: new Date("2026-08-24T00:00:00Z"), state: variant("confirmed", null) },
163
- { key: "a8", at: new Date("2026-08-24T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
164
- ],
165
- tiles: [],
166
- markers: [
167
- { at: variant("time", new Date("2026-07-13T00:00:00Z")), lane: none, status: variant("warning", null), message: "capacity 90%" },
168
- { at: variant("time", new Date("2026-08-24T00:00:00Z")), lane: none, status: variant("warning", null), message: "capacity breach — 2 drops" },
169
- ] }) }],
170
- ["van2", { hall: none, kind: variant("van", { name: "Van 2", region: "regional", sub: some("day · am/pm"),
187
+ ["ontime", { ...TARGET_EMPTY, hall: none, kind: variant("kpi", { name: "On-time", headline: "94.2%", pinned: true,
188
+ detail: some({ height: some("150px"), axis: variant("keep", null) }) }),
189
+ ontime: fromW27([96.1, 96.4, 96.8, 97.0, 96.2, 95.1, 93.4, 91.0, 88.9, 91.4, 93.8, 94.2]) }],
190
+ // ── Deliveries — Van 1's drops are raw, drawn as resting tiles; Van 2's
191
+ // are tiles stored as they draw, in its AM/PM lanes. Van 1's Aug 24 cell
192
+ // holds two drops, more than its room: a `+n` holds the rest. ──
193
+ ["van1", { ...TARGET_EMPTY, hall: none, kind: variant("van", { name: "Van 1", region: "local", sub: some("drops/wk"), detail: none, lanes: [] }),
194
+ drops: new Map([
195
+ ["a0", { at: new Date("2026-06-29T00:00:00Z"), state: variant("confirmed", null) }],
196
+ ["a1", { at: new Date("2026-07-06T00:00:00Z"), state: variant("confirmed", null) }],
197
+ ["a2", { at: new Date("2026-07-13T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
198
+ ["a3", { at: new Date("2026-07-20T00:00:00Z"), state: variant("confirmed", null) }],
199
+ ["a4", { at: new Date("2026-07-27T00:00:00Z"), state: variant("confirmed", null) }],
200
+ ["a5", { at: new Date("2026-08-03T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
201
+ ["a6", { at: new Date("2026-08-10T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
202
+ ["a7", { at: new Date("2026-08-24T00:00:00Z"), state: variant("confirmed", null) }],
203
+ ["a8", { at: new Date("2026-08-24T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
204
+ ]),
205
+ alerts: atInstants([
206
+ [new Date("2026-07-13T00:00:00Z"), { status: variant("warning", null), message: "capacity 90%" }],
207
+ [new Date("2026-08-24T00:00:00Z"), { status: variant("warning", null), message: "capacity breach — 2 drops" }],
208
+ ]) }],
209
+ ["van2", { ...TARGET_EMPTY, hall: none, kind: variant("van", { name: "Van 2", region: "regional", sub: some("day · am/pm"),
171
210
  detail: some({ height: some("112px"), axis: variant("keep", null) }),
172
- lanes: [{ key: "am", label: some("AM") }, { key: "pm", label: some("PM") }],
173
- drops: [],
174
- tiles: [
175
- { key: "m1", at: variant("time", new Date("2026-06-29T00:00:00Z")), lane: some("am"), label: none, icon: none, state: variant("confirmed", null),
176
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
177
- { key: "m2", at: variant("time", new Date("2026-06-29T00:00:00Z")), lane: some("pm"), label: none, icon: none, state: variant("confirmed", null),
178
- tone: some(variant("warning", null)), color: none, colorPalette: none, stretch: none, content: none, animation: none },
179
- { key: "m3", at: variant("time", new Date("2026-07-06T00:00:00Z")), lane: some("am"), label: none, icon: none, state: variant("proposed", variant("recommended", null)),
180
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: some(variant("pulse", null)) },
181
- { key: "m4", at: variant("time", new Date("2026-07-13T00:00:00Z")), lane: none, label: some("MIXED"), icon: none, state: variant("confirmed", null),
211
+ lanes: [{ key: "am", label: some("AM") }, { key: "pm", label: some("PM") }] }),
212
+ tiles: new Map([
213
+ ["m1", { at: new Date("2026-06-29T00:00:00Z"), lane: some("am"), label: none, icon: none, state: variant("confirmed", null),
214
+ tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none }],
215
+ ["m2", { at: new Date("2026-06-29T00:00:00Z"), lane: some("pm"), label: none, icon: none, state: variant("confirmed", null),
216
+ tone: some(variant("warning", null)), color: none, colorPalette: none, stretch: none, content: none, animation: none }],
217
+ ["m3", { at: new Date("2026-07-06T00:00:00Z"), lane: some("am"), label: none, icon: none, state: variant("proposed", variant("recommended", null)),
218
+ tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: some(variant("pulse", null)) }],
219
+ ["m4", { at: new Date("2026-07-13T00:00:00Z"), lane: none, label: some("MIXED"), icon: none, state: variant("confirmed", null),
182
220
  tone: none, color: none, colorPalette: none, stretch: some(variant("horizontal", null)),
183
- content: some({ horizontal: some(variant("center", null)), vertical: none }), animation: none },
184
- { key: "m5", at: variant("time", new Date("2026-07-20T00:00:00Z")), lane: some("pm"), label: none,
221
+ content: some({ horizontal: some(variant("center", null)), vertical: none }), animation: none }],
222
+ ["m5", { at: new Date("2026-07-20T00:00:00Z"), lane: some("pm"), label: none,
185
223
  icon: some({ prefix: "fas", name: "truck", label: none, style: none }),
186
224
  state: variant("proposed", variant("recommended", null)),
187
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
188
- { key: "m6", at: variant("time", new Date("2026-07-27T00:00:00Z")), lane: some("am"), label: some("PROOF"), icon: none, state: variant("estimated", null),
189
- tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none },
190
- // The colour channels: `color` is a raw token override,
191
- // `colorPalette` recolours the whole lifecycle treatment.
192
- { key: "m7", at: variant("time", new Date("2026-08-03T00:00:00Z")), lane: some("am"), label: some("S-A"), icon: none, state: variant("confirmed", null),
193
- tone: none, color: some("teal.solid"), colorPalette: none, stretch: none, content: none, animation: none },
194
- { key: "m8", at: variant("time", new Date("2026-08-03T00:00:00Z")), lane: some("pm"), label: some("S-B"), icon: none, state: variant("confirmed", null),
195
- tone: none, color: none, colorPalette: some(variant("brand", null)), stretch: none, content: none, animation: none },
196
- ],
197
- markers: [{ at: variant("time", new Date("2026-07-13T00:00:00Z")), lane: none, status: variant("danger", null), message: "capacity breach" }] }) }],
225
+ tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none }],
226
+ ["m6", { at: new Date("2026-07-27T00:00:00Z"), lane: some("am"), label: some("PROOF"), icon: none, state: variant("estimated", null),
227
+ tone: none, color: none, colorPalette: none, stretch: none, content: none, animation: none }],
228
+ // The colour channels: `color` is a raw token override,
229
+ // `colorPalette` recolours the whole lifecycle treatment.
230
+ ["m7", { at: new Date("2026-08-03T00:00:00Z"), lane: some("am"), label: some("S-A"), icon: none, state: variant("confirmed", null),
231
+ tone: none, color: some("teal.solid"), colorPalette: none, stretch: none, content: none, animation: none }],
232
+ ["m8", { at: new Date("2026-08-03T00:00:00Z"), lane: some("pm"), label: some("S-B"), icon: none, state: variant("confirmed", null),
233
+ tone: none, color: none, colorPalette: some(variant("brand", null)), stretch: none, content: none, animation: none }],
234
+ ]),
235
+ alerts: atInstants([[new Date("2026-07-13T00:00:00Z"), { status: variant("danger", null), message: "capacity breach" }]]) }],
198
236
  // ── The halls' presses and loads: each names its hall, and the data
199
237
  // step groups them under it. ──
200
- ["H1-P03", { hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(12.0), sub: none,
201
- status: some(variant("success", null)), detail: none,
202
- jobs: [
203
- { key: "mr", phase: "MAKEREADY", ticket: none, start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-06T00:00:00Z"), sheets: none, state: variant("actual", null), alert: none },
204
- { key: "j4642", phase: "RUN", ticket: some("J-4642"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(96.0), state: variant("in-progress", null), alert: none },
205
- { key: "wash", phase: "WASH-UP", ticket: none, start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none },
206
- { key: "j4663", phase: "RUN", ticket: some("J-4663"), start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none },
207
- ],
208
- decisions: [{ key: "d1", at: variant("time", new Date("2026-08-03T00:00:00Z")), applied: false }],
209
- ports: [{ at: variant("time", new Date("2026-07-27T00:00:00Z")), label: some("−24 k sheets") }],
210
- presses: new Map() }) }],
211
- ["H1-P04", { hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(12.0), sub: none, status: none,
238
+ ["H1-P03", { ...TARGET_EMPTY, hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(12.0), sub: none,
239
+ status: some(variant("success", null)), detail: none }),
240
+ jobs: new Map([
241
+ ["mr", { phase: "MAKEREADY", ticket: none, start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-06T00:00:00Z"), sheets: none, state: variant("actual", null), alert: none }],
242
+ ["j4642", { phase: "RUN", ticket: some("J-4642"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(96.0), state: variant("in-progress", null), alert: none }],
243
+ ["wash", { phase: "WASH-UP", ticket: none, start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none }],
244
+ ["j4663", { phase: "RUN", ticket: some("J-4663"), start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none }],
245
+ ]),
246
+ decisions: new Map([["d1", { at: new Date("2026-08-03T00:00:00Z"), applied: false }]]),
247
+ ports: atInstants([[new Date("2026-07-27T00:00:00Z"), { label: "−24 k sheets" }]]) }],
248
+ ["H1-P04", { ...TARGET_EMPTY, hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(12.0), sub: none, status: none,
212
249
  // The expand declaration — a stored plain-data record; presence is a
213
250
  // per-row fact, and the ROOT's expandRender mounts the body.
214
- detail: some({ height: some("152px"), axis: variant("keep", null) }),
215
- jobs: [
216
- { key: "j4624", phase: "RUN", ticket: some("J-4624"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), sheets: some(112.0), state: variant("actual", null), alert: none },
217
- { key: "plates", phase: "PLATES", ticket: none, start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none },
218
- { key: "proof", phase: "PROOF", ticket: none, start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none },
219
- { key: "j4693", phase: "RUN", ticket: some("J-4693"), start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-10-05T00:00:00Z"), sheets: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none },
220
- ],
221
- decisions: [], ports: [], presses: new Map() }) }],
222
- ["H1-P05", { hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(8.0), sub: none,
223
- status: some(variant("warning", null)), detail: none,
224
- jobs: [
225
- { key: "j4591", phase: "PLATES", ticket: some("J-4591"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: none, state: variant("actual", null), alert: some(variant("warning", null)) },
226
- { key: "wash", phase: "WASH-UP", ticket: none, start: new Date("2026-08-17T00:00:00Z"), end: new Date("2026-08-31T00:00:00Z"), sheets: none, state: variant("proposed", variant("recommended", null)), alert: none },
227
- ],
228
- decisions: [], ports: [], presses: new Map() }) }],
251
+ detail: some({ height: some("152px"), axis: variant("keep", null) }) }),
252
+ jobs: new Map([
253
+ ["j4624", { phase: "RUN", ticket: some("J-4624"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), sheets: some(112.0), state: variant("actual", null), alert: none }],
254
+ ["plates", { phase: "PLATES", ticket: none, start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none }],
255
+ ["proof", { phase: "PROOF", ticket: none, start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: none, state: variant("confirmed", null), alert: none }],
256
+ ["j4693", { phase: "RUN", ticket: some("J-4693"), start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-10-05T00:00:00Z"), sheets: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none }],
257
+ ]) }],
258
+ ["H1-P05", { ...TARGET_EMPTY, hall: some("Hall 1"), kind: variant("press", { series: "presses", rate: some(8.0), sub: none,
259
+ status: some(variant("warning", null)), detail: none }),
260
+ jobs: new Map([
261
+ ["j4591", { phase: "PLATES", ticket: some("J-4591"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: none, state: variant("actual", null), alert: some(variant("warning", null)) }],
262
+ ["wash", { phase: "WASH-UP", ticket: none, start: new Date("2026-08-17T00:00:00Z"), end: new Date("2026-08-31T00:00:00Z"), sheets: none, state: variant("proposed", variant("recommended", null)), alert: none }],
263
+ ]) }],
229
264
  // A load reading nothing in the week of 17 August: its cell is no data.
230
- ["h1-load", { hall: some("Hall 1"), kind: variant("load", { name: "H1 load", sub: "%/wk", from: new Date("2026-06-29T00:00:00Z"),
231
- weekly: [some(46.0), some(58.0), some(66.0), some(72.0), some(84.0), some(90.0), some(96.0), none, some(92.0), some(88.0), some(81.0), some(77.0)],
232
- detail: some({ height: some("132px"), axis: variant("keep", null) }) }) }],
233
- ["H2-P11", { hall: some("Hall 2"), kind: variant("press", { series: "presses", rate: some(10.0), sub: none, status: none, detail: none,
234
- jobs: [{ key: "j4723", phase: "RUN", ticket: some("J-4723"), start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(92.0), state: variant("confirmed", null), alert: none }],
235
- decisions: [], ports: [], presses: new Map() }) }],
236
- ["H2-P12", { hall: some("Hall 2"), kind: variant("press", { series: "presses", rate: some(10.0), sub: none, status: none, detail: none,
237
- jobs: [{ key: "j4594", phase: "RUN", ticket: some("J-4594"), start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(64.0), state: variant("proposed", variant("recommended", null)), alert: none }],
238
- decisions: [], ports: [], presses: new Map() }) }],
239
- ["h2-load", { hall: some("Hall 2"), kind: variant("load", { name: "H2 load", sub: "%/wk", from: new Date("2026-06-29T00:00:00Z"),
240
- weekly: [some(46.0), some(52.0), some(58.0), some(61.0), some(66.0), some(72.0), some(78.0), some(84.0), some(90.0), some(96.0), some(98.0), some(92.0)],
241
- detail: none }) }],
242
- ["H3-P21", { hall: some("Hall 3"), kind: variant("press", { series: "presses", rate: some(6.0), sub: none, status: none, detail: none,
243
- jobs: [{ key: "j4903", phase: "RUN", ticket: some("J-4903"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(48.0), state: variant("confirmed", null), alert: none }],
244
- decisions: [], ports: [], presses: new Map() }) }],
245
- ["H3-P22", { hall: some("Hall 3"), kind: variant("press", { series: "presses", rate: some(6.0), sub: none, status: none, detail: none,
246
- jobs: [{ key: "j4906", phase: "RUN", ticket: some("J-4906"), start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-09-07T00:00:00Z"), sheets: some(54.0), state: variant("proposed", variant("recommended", null)), alert: none }],
247
- decisions: [], ports: [], presses: new Map() }) }],
248
- ["h3-load", { hall: some("Hall 3"), kind: variant("load", { name: "H3 load", sub: "%/wk", from: new Date("2026-06-29T00:00:00Z"),
249
- weekly: [some(38.0), some(42.0), some(47.0), some(51.0), some(55.0), some(61.0), some(66.0), some(70.0), some(75.0), some(80.0), some(83.0), some(78.0)],
250
- detail: none }) }],
265
+ ["h1-load", { ...TARGET_EMPTY, hall: some("Hall 1"), kind: variant("load", { name: "H1 load", sub: "%/wk",
266
+ detail: some({ height: some("132px"), axis: variant("keep", null) }) }),
267
+ load: fromW27([some(46.0), some(58.0), some(66.0), some(72.0), some(84.0), some(90.0), some(96.0), none, some(92.0), some(88.0), some(81.0), some(77.0)]) }],
268
+ ["H2-P11", { ...TARGET_EMPTY, hall: some("Hall 2"), kind: variant("press", { series: "presses", rate: some(10.0), sub: none, status: none, detail: none }),
269
+ jobs: new Map([["j4723", { phase: "RUN", ticket: some("J-4723"), start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(92.0), state: variant("confirmed", null), alert: none }]]) }],
270
+ ["H2-P12", { ...TARGET_EMPTY, hall: some("Hall 2"), kind: variant("press", { series: "presses", rate: some(10.0), sub: none, status: none, detail: none }),
271
+ jobs: new Map([["j4594", { phase: "RUN", ticket: some("J-4594"), start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(64.0), state: variant("proposed", variant("recommended", null)), alert: none }]]) }],
272
+ ["h2-load", { ...TARGET_EMPTY, hall: some("Hall 2"), kind: variant("load", { name: "H2 load", sub: "%/wk", detail: none }),
273
+ load: fromW27([some(46.0), some(52.0), some(58.0), some(61.0), some(66.0), some(72.0), some(78.0), some(84.0), some(90.0), some(96.0), some(98.0), some(92.0)]) }],
274
+ ["H3-P21", { ...TARGET_EMPTY, hall: some("Hall 3"), kind: variant("press", { series: "presses", rate: some(6.0), sub: none, status: none, detail: none }),
275
+ jobs: new Map([["j4903", { phase: "RUN", ticket: some("J-4903"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(48.0), state: variant("confirmed", null), alert: none }]]) }],
276
+ ["H3-P22", { ...TARGET_EMPTY, hall: some("Hall 3"), kind: variant("press", { series: "presses", rate: some(6.0), sub: none, status: none, detail: none }),
277
+ jobs: new Map([["j4906", { phase: "RUN", ticket: some("J-4906"), start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-09-07T00:00:00Z"), sheets: some(54.0), state: variant("proposed", variant("recommended", null)), alert: none }]]) }],
278
+ ["h3-load", { ...TARGET_EMPTY, hall: some("Hall 3"), kind: variant("load", { name: "H3 load", sub: "%/wk", detail: none }),
279
+ load: fromW27([some(38.0), some(42.0), some(47.0), some(51.0), some(55.0), some(61.0), some(66.0), some(70.0), some(75.0), some(80.0), some(83.0), some(78.0)]) }],
251
280
  // ── The span grammar: proposal flavours (a forecast ghost, a proposed
252
281
  // cut, a declined run) ──
253
- ["H1-P07", { hall: none, kind: variant("press", { series: "flavours", rate: some(8.0), sub: none, status: none, detail: none,
254
- jobs: [
255
- { key: "run", phase: "RUN", ticket: some("J-4580"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), sheets: some(64.0), state: variant("in-progress", null), alert: none },
256
- { key: "gho", phase: "FORECAST", ticket: none, start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: none, state: variant("estimated", null), alert: none },
257
- { key: "rem", phase: "CUT", ticket: none, start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: none, state: variant("proposed", variant("removed", null)), alert: none },
258
- { key: "rej", phase: "DECLINED", ticket: none, start: new Date("2026-08-31T00:00:00Z"), end: new Date("2026-09-14T00:00:00Z"), sheets: none, state: variant("rejected", null), alert: none },
259
- ],
260
- decisions: [], ports: [], presses: new Map() }) }],
282
+ ["H1-P07", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "flavours", rate: some(8.0), sub: none, status: none, detail: none }),
283
+ jobs: new Map([
284
+ ["run", { phase: "RUN", ticket: some("J-4580"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), sheets: some(64.0), state: variant("in-progress", null), alert: none }],
285
+ ["gho", { phase: "FORECAST", ticket: none, start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: none, state: variant("estimated", null), alert: none }],
286
+ ["rem", { phase: "CUT", ticket: none, start: new Date("2026-08-10T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: none, state: variant("proposed", variant("removed", null)), alert: none }],
287
+ ["rej", { phase: "DECLINED", ticket: none, start: new Date("2026-08-31T00:00:00Z"), end: new Date("2026-09-14T00:00:00Z"), sheets: none, state: variant("rejected", null), alert: none }],
288
+ ]) }],
261
289
  // …sheets, an applied decision and a port on a two-line gutter, its
262
290
  // expand declaration dimming the axis behind the render…
263
- ["H1-P09", { hall: none, kind: variant("press", { series: "detail", rate: none, sub: some("12k sheets/h"), status: none,
264
- detail: some({ height: some("152px"), axis: variant("dim", null) }),
265
- jobs: [
266
- { key: "a", phase: "RUN", ticket: some("J-4624"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(112.0), state: variant("actual", null), alert: none },
267
- { key: "b", phase: "RUN", ticket: some("J-4693"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none },
268
- ],
269
- decisions: [{ key: "d1", at: variant("time", new Date("2026-07-27T00:00:00Z")), applied: true }],
270
- ports: [{ at: variant("time", new Date("2026-07-27T00:00:00Z")), label: some("−24 k sheets") }],
271
- presses: new Map() }) }],
291
+ ["H1-P09", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "detail", rate: none, sub: some("12k sheets/h"), status: none,
292
+ detail: some({ height: some("152px"), axis: variant("dim", null) }) }),
293
+ jobs: new Map([
294
+ ["a", { phase: "RUN", ticket: some("J-4624"), start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(112.0), state: variant("actual", null), alert: none }],
295
+ ["b", { phase: "RUN", ticket: some("J-4693"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(104.0), state: variant("proposed", variant("recommended", null)), alert: none }],
296
+ ]),
297
+ decisions: new Map([["d1", { at: new Date("2026-07-27T00:00:00Z"), applied: true }]]),
298
+ ports: atInstants([[new Date("2026-07-27T00:00:00Z"), { label: "−24 k sheets" }]]) }],
272
299
  // …a contract and the presses it books — its row rolls their runs up
273
300
  // into union bands (renderer-derived)…
274
- ["Contract A", { hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none,
275
- jobs: [], decisions: [], ports: [],
276
- presses: new Map([
277
- ["H1-P03", { hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none,
278
- jobs: [
279
- { key: "j4642", phase: "RUN", ticket: some("J-4642"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(96.0), state: variant("actual", null), alert: none },
280
- { key: "j4663", phase: "RUN", ticket: some("J-4663"), start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none },
281
- ],
282
- decisions: [], ports: [], presses: new Map() }) }],
283
- ["H2-P11", { hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none,
284
- jobs: [{ key: "j4723", phase: "RUN", ticket: some("J-4723"), start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(92.0), state: variant("confirmed", null), alert: none }],
285
- decisions: [], ports: [], presses: new Map() }) }],
286
- ]) }) }],
301
+ ["Contract A", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none }),
302
+ presses: new Map([
303
+ ["H1-P03", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none }),
304
+ jobs: new Map([
305
+ ["j4642", { phase: "RUN", ticket: some("J-4642"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), sheets: some(96.0), state: variant("actual", null), alert: none }],
306
+ ["j4663", { phase: "RUN", ticket: some("J-4663"), start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none }],
307
+ ]) }],
308
+ ["H2-P11", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "rollup", rate: none, sub: none, status: none, detail: none }),
309
+ jobs: new Map([["j4723", { phase: "RUN", ticket: some("J-4723"), start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(92.0), state: variant("confirmed", null), alert: none }]]) }],
310
+ ]) }],
287
311
  // …a linked delivery whose run starts BEYOND the window — in links focus
288
312
  // its landing is a slot on the plot's edge…
289
- ["dlv", { hall: none, kind: variant("press", { series: "delivery", rate: none, sub: none, status: none, detail: none,
290
- jobs: [{ key: "d1", phase: "DELIVER", ticket: none, start: new Date("2026-09-21T00:00:00Z"), end: new Date("2026-10-12T00:00:00Z"), sheets: some(91.0), state: variant("proposed", variant("recommended", null)), alert: none }],
291
- decisions: [], ports: [], presses: new Map() }) }],
313
+ ["dlv", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "delivery", rate: none, sub: none, status: none, detail: none }),
314
+ jobs: new Map([["d1", { phase: "DELIVER", ticket: none, start: new Date("2026-09-21T00:00:00Z"), end: new Date("2026-10-12T00:00:00Z"), sheets: some(91.0), state: variant("proposed", variant("recommended", null)), alert: none }]]) }],
292
315
  // …and a contract rolled up by status.
293
- ["Contract B", { hall: none, kind: variant("press", { series: "contracts", rate: none, sub: none, status: none, detail: none,
294
- jobs: [], decisions: [], ports: [],
295
- presses: new Map([
296
- ["H2-P12", { hall: none, kind: variant("press", { series: "contracts", rate: none, sub: none, status: none, detail: none,
297
- jobs: [
298
- { key: "r1", phase: "RUN", ticket: some("J-4594"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: some(64.0), state: variant("actual", null), alert: none },
299
- { key: "r2", phase: "RUN", ticket: some("J-4606"), start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(40.0), state: variant("proposed", variant("recommended", null)), alert: none },
300
- ],
301
- decisions: [], ports: [], presses: new Map() }) }],
302
- ]) }) }],
316
+ ["Contract B", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "contracts", rate: none, sub: none, status: none, detail: none }),
317
+ presses: new Map([
318
+ ["H2-P12", { ...TARGET_EMPTY, hall: none, kind: variant("press", { series: "contracts", rate: none, sub: none, status: none, detail: none }),
319
+ jobs: new Map([
320
+ ["r1", { phase: "RUN", ticket: some("J-4594"), start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-08-03T00:00:00Z"), sheets: some(64.0), state: variant("actual", null), alert: none }],
321
+ ["r2", { phase: "RUN", ticket: some("J-4606"), start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), sheets: some(40.0), state: variant("proposed", variant("recommended", null)), alert: none }],
322
+ ]) }],
323
+ ]) }],
303
324
  // ── A second heat series of the same kind, its own entry ──
304
- ["quality", { hall: none, kind: variant("load", { name: "Quality index", sub: "%", from: new Date("2026-06-29T00:00:00Z"),
305
- weekly: [some(94.0), some(95.0), some(96.0), some(95.0), some(97.0), some(96.0), some(93.0), some(92.0), some(94.0), some(95.0), some(96.0), some(97.0)],
306
- detail: none }) }],
325
+ ["quality", { ...TARGET_EMPTY, hall: none, kind: variant("load", { name: "Quality index", sub: "%", detail: none }),
326
+ load: fromW27([some(94.0), some(95.0), some(96.0), some(95.0), some(97.0), some(96.0), some(93.0), some(92.0), some(94.0), some(95.0), some(96.0), some(97.0)]) }],
307
327
  // ── Crews — Crew A's chips derive from raw shifts; the relief pool's are
308
- // chip records stored as they draw. ──
309
- ["crewA", { hall: none, kind: variant("crew", { name: "Crew A", pool: false, sub: some("152h → 168h"), value: none,
310
- detail: some({ height: some("120px"), axis: variant("keep", null) }),
311
- shifts: [
312
- { key: "s1", from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), hours: 80.0, state: variant("confirmed", null) },
313
- { key: "s2", from: new Date("2026-07-13T00:00:00Z"), to: new Date("2026-07-27T00:00:00Z"), hours: 56.0, state: variant("proposed", variant("removed", null)) },
314
- { key: "s3", from: new Date("2026-07-27T00:00:00Z"), to: new Date("2026-08-10T00:00:00Z"), hours: 64.0, state: variant("proposed", variant("recommended", null)) },
315
- { key: "s4", from: new Date("2026-08-17T00:00:00Z"), to: new Date("2026-08-24T00:00:00Z"), hours: 48.0, state: variant("estimated", null) },
316
- { key: "s5", from: new Date("2026-08-31T00:00:00Z"), to: new Date("2026-09-14T00:00:00Z"), hours: 56.0, state: variant("proposed", variant("recommended", null)) },
317
- ],
318
- chips: [] }) }],
319
- ["crewB", { hall: none, kind: variant("crew", { name: "Crew B", pool: true, sub: none, value: some("128h"), detail: none, shifts: [],
320
- chips: [
321
- { key: "b1", from: variant("time", new Date("2026-07-06T00:00:00Z")), to: variant("time", new Date("2026-07-27T00:00:00Z")), label: "96h", state: variant("confirmed", null),
322
- icon: some({ prefix: "fas", name: "user-group", label: none, style: none }) },
323
- { key: "b2", from: variant("time", new Date("2026-08-10T00:00:00Z")), to: variant("time", new Date("2026-08-31T00:00:00Z")), label: "+32h", state: variant("proposed", variant("recommended", null)), icon: none },
324
- ] }) }],
328
+ // chips stored as they draw. ──
329
+ ["crewA", { ...TARGET_EMPTY, hall: none, kind: variant("crew", { name: "Crew A", pool: false, sub: some("152h → 168h"), value: none,
330
+ detail: some({ height: some("120px"), axis: variant("keep", null) }) }),
331
+ shifts: new Map([
332
+ ["s1", { from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), hours: 80.0, state: variant("confirmed", null) }],
333
+ ["s2", { from: new Date("2026-07-13T00:00:00Z"), to: new Date("2026-07-27T00:00:00Z"), hours: 56.0, state: variant("proposed", variant("removed", null)) }],
334
+ ["s3", { from: new Date("2026-07-27T00:00:00Z"), to: new Date("2026-08-10T00:00:00Z"), hours: 64.0, state: variant("proposed", variant("recommended", null)) }],
335
+ ["s4", { from: new Date("2026-08-17T00:00:00Z"), to: new Date("2026-08-24T00:00:00Z"), hours: 48.0, state: variant("estimated", null) }],
336
+ ["s5", { from: new Date("2026-08-31T00:00:00Z"), to: new Date("2026-09-14T00:00:00Z"), hours: 56.0, state: variant("proposed", variant("recommended", null)) }],
337
+ ]) }],
338
+ ["crewB", { ...TARGET_EMPTY, hall: none, kind: variant("crew", { name: "Crew B", pool: true, sub: none, value: some("128h"), detail: none }),
339
+ chips: new Map([
340
+ ["b1", { from: new Date("2026-07-06T00:00:00Z"), to: new Date("2026-07-27T00:00:00Z"), label: "96h", state: variant("confirmed", null),
341
+ icon: some({ prefix: "fas", name: "user-group", label: none, style: none }) }],
342
+ ["b2", { from: new Date("2026-08-10T00:00:00Z"), to: new Date("2026-08-31T00:00:00Z"), label: "+32h", state: variant("proposed", variant("recommended", null)), icon: none }],
343
+ ]) }],
325
344
  // ── Instant marks — every kind: milestones, decisions applied and
326
345
  // pending, an exception, a custom icon; the releases on a cadence. ──
327
- ["milestones", { hall: none, kind: variant("stream", { name: "Milestones", cadence: none,
328
- detail: some({ height: some("112px"), axis: variant("dim", null) }),
329
- marks: [
330
- { key: "kick", at: variant("time", new Date("2026-07-06T00:00:00Z")), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
331
- { key: "d1", at: variant("time", new Date("2026-07-27T00:00:00Z")), kind: variant("decision", { applied: true }), icon: none, label: none },
332
- { key: "rel", at: variant("time", new Date("2026-08-10T00:00:00Z")), kind: variant("milestone", null),
333
- icon: some({ prefix: "fas", name: "rocket", label: none, style: none }), label: some("REL 2.4") },
334
- { key: "audit", at: variant("time", new Date("2026-08-24T00:00:00Z")), kind: variant("exception", null), icon: none, label: some("AUDIT") },
335
- { key: "d2", at: variant("time", new Date("2026-09-07T00:00:00Z")), kind: variant("decision", { applied: false }), icon: none, label: some("×3") },
336
- ] }) }],
337
- ["releases", { hall: none, kind: variant("stream", { name: "Releases", cadence: some("6-wk cadence"), detail: none,
338
- marks: [
339
- { key: "r1", at: variant("time", new Date("2026-07-13T00:00:00Z")), kind: variant("milestone", null), icon: none, label: some("2.3") },
340
- { key: "r2", at: variant("time", new Date("2026-08-31T00:00:00Z")), kind: variant("milestone", null), icon: none, label: some("2.4") },
341
- ] }) }],
346
+ ["milestones", { ...TARGET_EMPTY, hall: none, kind: variant("stream", { name: "Milestones", cadence: none,
347
+ detail: some({ height: some("112px"), axis: variant("dim", null) }) }),
348
+ marks: new Map([
349
+ ["kick", { at: new Date("2026-07-06T00:00:00Z"), kind: variant("milestone", null), icon: none, label: some("KICKOFF") }],
350
+ ["d1", { at: new Date("2026-07-27T00:00:00Z"), kind: variant("decision", { applied: true }), icon: none, label: none }],
351
+ ["rel", { at: new Date("2026-08-10T00:00:00Z"), kind: variant("milestone", null),
352
+ icon: some({ prefix: "fas", name: "rocket", label: none, style: none }), label: some("REL 2.4") }],
353
+ ["audit", { at: new Date("2026-08-24T00:00:00Z"), kind: variant("exception", null), icon: none, label: some("AUDIT") }],
354
+ ["d2", { at: new Date("2026-09-07T00:00:00Z"), kind: variant("decision", { applied: false }), icon: none, label: some("×3") }],
355
+ ]) }],
356
+ ["releases", { ...TARGET_EMPTY, hall: none, kind: variant("stream", { name: "Releases", cadence: some("6-wk cadence"), detail: none }),
357
+ marks: new Map([
358
+ ["r1", { at: new Date("2026-07-13T00:00:00Z"), kind: variant("milestone", null), icon: none, label: some("2.3") }],
359
+ ["r2", { at: new Date("2026-08-31T00:00:00Z"), kind: variant("milestone", null), icon: none, label: some("2.4") }],
360
+ ]) }],
342
361
  // ── Delivered sheets, a week apiece — negatives, and a week with none;
343
362
  // its render draws its own canvas, so the shared lines leave its row. ──
344
- ["delivered", { hall: none, kind: variant("tally", { name: "Delivered · k",
345
- detail: some({ height: some("120px"), axis: variant("off", null) }),
346
- cells: [
347
- { at: variant("time", new Date("2026-06-29T00:00:00Z")), value: some(128.0), text: none, tone: none },
348
- { at: variant("time", new Date("2026-07-06T00:00:00Z")), value: some(134.0), text: none, tone: none },
349
- { at: variant("time", new Date("2026-07-13T00:00:00Z")), value: some(119.0), text: none, tone: none },
350
- { at: variant("time", new Date("2026-07-20T00:00:00Z")), value: some(-96.0), text: none, tone: none },
351
- { at: variant("time", new Date("2026-07-27T00:00:00Z")), value: some(-88.0), text: none, tone: none },
352
- { at: variant("time", new Date("2026-08-03T00:00:00Z")), value: none, text: none, tone: none },
353
- { at: variant("time", new Date("2026-08-10T00:00:00Z")), value: some(151.0), text: none, tone: none },
354
- { at: variant("time", new Date("2026-08-17T00:00:00Z")), value: some(162.0), text: none, tone: none },
355
- { at: variant("time", new Date("2026-08-24T00:00:00Z")), value: some(144.0), text: none, tone: none },
356
- ] }) }],
363
+ ["delivered", { ...TARGET_EMPTY, hall: none, kind: variant("tally", { name: "Delivered · k",
364
+ detail: some({ height: some("120px"), axis: variant("off", null) }) }),
365
+ delivered: fromW27([some(128.0), some(134.0), some(119.0), some(-96.0), some(-88.0), none, some(151.0), some(162.0), some(144.0)]) }],
357
366
  // ── The finishing line's machines — each one shown three ways ──
358
- ["FN-1", { hall: none, kind: variant("finisher", { name: "Folder FN-1",
359
- jobs: [{ key: "f1", phase: "FOLD", ticket: some("J-4642"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(96.0), state: variant("confirmed", null), alert: none }],
360
- util: [46.0, 52.0, 58.0, 66.0, 72.0, 78.0, 84.0, 80.0, 74.0, 68.0, 62.0, 58.0],
361
- sheets: [
362
- { at: variant("time", new Date("2026-07-27T00:00:00Z")), value: some(48.0), text: none, tone: none },
363
- { at: variant("time", new Date("2026-08-03T00:00:00Z")), value: some(-8.0), text: none, tone: none },
364
- ] }) }],
365
- ["FN-2", { hall: none, kind: variant("finisher", { name: "Cutter FN-2",
366
- jobs: [{ key: "f2", phase: "CUT", ticket: some("J-4663"), start: new Date("2026-08-24T00:00:00Z"), end: new Date("2026-09-07T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none }],
367
- util: [30.0, 34.0, 38.0, 42.0, 46.0, 50.0, 54.0, 58.0, 62.0, 66.0, 70.0, 74.0],
368
- sheets: [
369
- { at: variant("time", new Date("2026-08-24T00:00:00Z")), value: some(44.0), text: none, tone: none },
370
- { at: variant("time", new Date("2026-08-31T00:00:00Z")), value: some(44.0), text: none, tone: none },
371
- ] }) }],
367
+ ["FN-1", { ...TARGET_EMPTY, hall: none, kind: variant("finisher", { name: "Folder FN-1" }),
368
+ jobs: new Map([["f1", { phase: "FOLD", ticket: some("J-4642"), start: new Date("2026-07-27T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), sheets: some(96.0), state: variant("confirmed", null), alert: none }]]),
369
+ util: fromW27([46.0, 52.0, 58.0, 66.0, 72.0, 78.0, 84.0, 80.0, 74.0, 68.0, 62.0, 58.0]),
370
+ sheets: atInstants([[new Date("2026-07-27T00:00:00Z"), 48.0], [new Date("2026-08-03T00:00:00Z"), -8.0]]) }],
371
+ ["FN-2", { ...TARGET_EMPTY, hall: none, kind: variant("finisher", { name: "Cutter FN-2" }),
372
+ jobs: new Map([["f2", { phase: "CUT", ticket: some("J-4663"), start: new Date("2026-08-24T00:00:00Z"), end: new Date("2026-09-07T00:00:00Z"), sheets: some(88.0), state: variant("proposed", variant("recommended", null)), alert: none }]]),
373
+ util: fromW27([30.0, 34.0, 38.0, 42.0, 46.0, 50.0, 54.0, 58.0, 62.0, 66.0, 70.0, 74.0]),
374
+ sheets: atInstants([[new Date("2026-08-24T00:00:00Z"), 44.0], [new Date("2026-08-31T00:00:00Z"), 44.0]]) }],
372
375
  ])));
373
376
  /**
374
377
  * The data flagship: every row kind on one axis, from ONE raw ops source bound
@@ -423,7 +426,7 @@ export const planTargetState = example({
423
426
  "span", "run", "group", "groups", "strip", "section", "heat", "buckets", "cards", "chip", "events", "mark", "chart",
424
427
  "table", "views", "rows", "layers", "rollup", "union", "byStatus", "bands", "footer", "milestone",
425
428
  "decision", "exception", "pinned", "port", "hovercard", "popover", "hover",
426
- "slice", "Slice.bindRecords", "source", "Data.bindPaged", "recordIndex", "presets", "brush", "horizon", "toolbar", "affordances", "summary", "#1344",
429
+ "slice", "Slice.bindRecords", "source", "Data.bindPaged", "indexes", "presets", "brush", "horizon", "toolbar", "affordances", "summary", "#1344",
427
430
  "expand", "expandRender", "expandGutter", "keep", "dim", "off", "collapse", "context strip",
428
431
  "resolver", "data-driven", "accessor", "raw", "target state", "flagship",
429
432
  "layout", "row id", "Plan.ref", "Plan.sectionRef", "links", "link", "focus", "quantity", "Plan.quantity", "unit",
@@ -464,66 +467,57 @@ export const planTargetState = example({
464
467
  }));
465
468
  // ── The data step ──────────────────────────────────────────
466
469
  // Hierarchy is the DATA's (#822): one `groupToDicts` makes each
467
- // hall an entry holding the rows that stand in it, keyed as the
468
- // source keys them, and the halls join the rest of the source.
470
+ // hall an entry holding the rows that stand in it — its
471
+ // `members`, keyed as the source keys them — and the halls join
472
+ // the rest of the source.
469
473
  const raw = $.let(ops.read());
470
474
  const halls = $.let(raw.filter((_$2, r) => r.unwrap().hall.hasTag("some"))
471
475
  .groupToDicts((_$2, r) => r.unwrap().hall.unwrap("some"), (_$2, _r, k) => k));
472
476
  const rows = $.let(raw.filter((_$2, r) => r.unwrap().hall.hasTag("none"))
473
- .union(halls.map((_$2, members) => East.value({ hall: none, kind: variant("hall", { members }) }, TargetOpsRow))));
474
- // ── What the accessors share ───────────────────────────────
475
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
476
- // A measure's weekly readings, from its first week, as a chart's
477
- // points, and as heat cells printing their values — a week with
478
- // none read the no-data cell.
479
- const weeklyPoints = $.const(East.function([DateTimeType, ArrayType(FloatType)], ArrayType(MeasureRow), (_$2, from, weekly) => East.Array.generate(weekly.size(), MeasureRow, (_$3, i) => ({ week: from.addWeeks(i), pct: weekly.get(i) }))));
480
- const weeklyCells = $.const(East.function([DateTimeType, ArrayType(OptionType(FloatType))], ArrayType(Plan.Types.HeatCell), (_$2, from, weekly) => East.Array.generate(weekly.size(), Plan.Types.HeatCell, ($3, i) => {
481
- const value = $3.let(weekly.get(i), OptionType(FloatType));
482
- const label = $3.let(none, OptionType(StringType));
483
- $3.match(value, { some: ($4, v) => { $4.assign(label, some(East.Float.printFixed(v, 0n))); } });
484
- return { at: Plan.at.time(from.addWeeks(i)), value, label };
485
- })));
477
+ .union(halls.map((_$2, members) => East.value({ ...TARGET_EMPTY, hall: none, kind: variant("hall", null), members }, TargetOpsRow))));
478
+ // ── What the reads share ───────────────────────────────────
479
+ // Each series reads its row's collections BY NAME (#1436): the
480
+ // jobs as runs (`Plan.items`, an id and the times named), a week's
481
+ // values as cells and points (`Plan.values`, each at its week).
482
+ // `draw` builds only how an element looks.
486
483
  // A press's rate, a thousand sheets an hour, as its gutter value.
487
484
  const rateText = $.const(East.function([OptionType(FloatType)], OptionType(StringType), (_$2, rate) => rate.match({
488
485
  some: (_$3, k) => East.value(some(East.str `${East.Float.printFixed(k, 0n)}k/h`), OptionType(StringType)),
489
486
  none: (_$3) => East.value(none, OptionType(StringType)),
490
487
  })));
491
- // Raw jobs → runs: ONE bound mapping function — the bar label and
492
- // the quantity derive CLIENT-SIDE from the raw phase/ticket/sheets
493
- // fields. A quantity is ONE value — the number, its unit and how it
494
- // prints (#824) — so the bar's caption and a rollup's sum can never
495
- // disagree. The run is written as a RECORD, so its instants are
496
- // spelled with `Plan.at.time` — the `Plan.run` builder would wrap a
497
- // DateTime field by itself.
488
+ // A job's look: the bar label and the quantity derive CLIENT-SIDE
489
+ // from its raw phase, ticket and sheets. A quantity is ONE value —
490
+ // the number, its unit and how it prints (#824) — so the bar's
491
+ // caption and a rollup's sum can never disagree.
498
492
  const sheetsFormat = $.const(Format.Number({ maximumFractionDigits: 0n }));
499
- const jobRuns = $.const(East.function([ArrayType(TargetJob)], ArrayType(Plan.Types.Run), (_$2, jobs) => jobs.map(($3, j) => {
500
- const noQuantity = $3.const(none, OptionType(Plan.Types.Quantity));
501
- const quantity = $3.let(j.sheets.match({
502
- some: (_$4, t) => East.value(some(Plan.quantity(t, { unit: "k sheets", format: sheetsFormat })), OptionType(Plan.Types.Quantity)),
503
- none: (_$4) => noQuantity,
504
- }), OptionType(Plan.Types.Quantity));
505
- const label = $3.let(j.ticket.match({
506
- some: (_$4, b) => East.str `${j.phase} · ${b}`,
507
- none: (_$4) => j.phase,
508
- }), StringType);
509
- const run = $3.let({
510
- key: j.key, start: Plan.at.time(j.start), end: Plan.at.time(j.end), label,
511
- quantity, state: j.state, status: j.alert,
512
- moved: none, icon: none,
513
- }, Plan.Types.Run);
514
- return run;
515
- })));
516
- // Raw shifts → chips: hours print as the chip label, an ADDED
493
+ const jobLook = (job) => ({
494
+ label: job.ticket.match({ some: (_$2, t) => East.str `${job.phase} · ${t}`, none: () => job.phase }),
495
+ state: job.state,
496
+ quantity: job.sheets.match({
497
+ some: (_$2, t) => East.value(some(Plan.quantity(t, { unit: "k sheets", format: sheetsFormat })), OptionType(Plan.Types.Quantity)),
498
+ none: () => East.value(none, OptionType(Plan.Types.Quantity)),
499
+ }),
500
+ status: job.alert,
501
+ });
502
+ // A shift's look: its hours print as the chip label, an ADDED
517
503
  // proposal wearing the `+` prefix — display derives from the
518
504
  // lifecycle, down to the proposal's flavour (a `removed` shift is
519
505
  // a proposal too, and `+` would read as its opposite).
520
- const shiftChips = $.const(East.function([ArrayType(TargetShift)], ArrayType(Plan.Types.Chip), (_$2, shifts) => shifts.map(($3, s) => {
521
- const hrs = $3.let(East.Float.printFixed(s.hours, 0n), StringType);
522
- const label = $3.let(s.state.match({
523
- proposed: (_$4, p) => p.hasTag("removed").ifElse(() => East.str `${hrs}h`, () => East.str `+${hrs}h`),
524
- }, _$4 => East.str `${hrs}h`), StringType);
525
- return Plan.chip({ key: s.key, from: s.from, to: s.to, label, state: s.state });
526
- })));
506
+ const shiftLook = (shift) => ({
507
+ label: shift.state.match({
508
+ proposed: (_$2, p) => p.hasTag("removed").ifElse(() => East.str `${East.Float.printFixed(shift.hours, 0n)}h`, () => East.str `+${East.Float.printFixed(shift.hours, 0n)}h`),
509
+ }, () => East.str `${East.Float.printFixed(shift.hours, 0n)}h`),
510
+ state: shift.state,
511
+ });
512
+ // A week's load: its value, printed as the cell's label — a week
513
+ // reading none is the no-data cell.
514
+ const loadLook = (pct) => ({
515
+ value: pct,
516
+ label: pct.match({
517
+ some: (_$2, v) => East.value(some(East.Float.printFixed(v, 0n)), OptionType(StringType)),
518
+ none: () => East.value(none, OptionType(StringType)),
519
+ }),
520
+ });
527
521
  const whole = $.const(Format.Number({ maximumFractionDigits: 0n }));
528
522
  // ── The series — the list IS the layout (#822): one block per
529
523
  // series, top to bottom, a section titling the blocks beneath
@@ -543,24 +537,24 @@ export const planTargetState = example({
543
537
  expand: r => r.kind.unwrap("kpi").detail,
544
538
  height: "spark", expandable: true,
545
539
  left: { domain: [80, 110], tickValues: [80, 100] },
546
- layers: r => [
547
- Plan.layer(Chart.Line(weeklyPoints(r.kind.unwrap("kpi").from, r.kind.unwrap("kpi").weekly), { x: p => p.week, y: p => p.pct }), { breach: { below: 92 } }),
540
+ layers: [
541
+ Plan.line(Plan.values("ontime", (pct) => ({ y: pct })), { breach: { below: 92 } }),
548
542
  Chart.refLine({ y: 100, label: "TARGET 100" }),
549
543
  ],
550
544
  }),
551
545
  Plan.series.section(TargetOpsRow, { key: "vans-local", title: "Deliveries · Local", meta: "1 row", value: "9 drops", status: "warning" }, [
552
- // Raw drops → resting tiles, in the accessor.
546
+ // Raw drops, each a resting tile at its week.
553
547
  Plan.series.buckets(TargetOpsRow, {
554
548
  key: "vans", title: "Vans",
555
549
  match: r => r.kind.hasTag("van").and(() => r.kind.unwrap("van").region.equal("local")),
556
550
  label: r => r.kind.unwrap("van").name,
557
551
  sub: r => r.kind.unwrap("van").sub,
558
- events: r => r.kind.unwrap("van").drops.map((_$2, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
559
- markers: r => r.kind.unwrap("van").markers,
552
+ events: Plan.items("drops", { at: "at" }, (drop) => ({ state: drop.state })),
553
+ markers: Plan.values("alerts", (alert) => ({ message: alert.message, status: alert.status })),
560
554
  }),
561
555
  ]),
562
556
  Plan.series.section(TargetOpsRow, { key: "vans-regional", title: "Deliveries · Regional", meta: "1 row" }, [
563
- // Stored tile records pass straight through, in their lanes.
557
+ // Tiles stored as they draw pass straight through, in their lanes.
564
558
  Plan.series.buckets(TargetOpsRow, {
565
559
  key: "regional", title: "Regional",
566
560
  match: r => r.kind.hasTag("van").and(() => r.kind.unwrap("van").region.equal("regional")),
@@ -568,13 +562,16 @@ export const planTargetState = example({
568
562
  sub: r => r.kind.unwrap("van").sub,
569
563
  expand: r => r.kind.unwrap("van").detail,
570
564
  lanes: r => r.kind.unwrap("van").lanes,
571
- events: r => r.kind.unwrap("van").tiles,
572
- markers: r => r.kind.unwrap("van").markers,
565
+ events: Plan.items("tiles", { at: "at" }, (tile) => ({
566
+ state: tile.state, lane: tile.lane, label: tile.label, icon: tile.icon, tone: tile.tone, color: tile.color,
567
+ colorPalette: tile.colorPalette, stretch: tile.stretch, content: tile.content, animation: tile.animation,
568
+ })),
569
+ markers: Plan.values("alerts", (alert) => ({ message: alert.message, status: alert.status })),
573
570
  }),
574
571
  ]),
575
572
  // One strip PER HALL — the data step's entries — its members
576
- // stepped down into (`Plan.children`) and laid out like a
577
- // top-level list: the hall's presses, then its load. Hall 2
573
+ // stepped down into by field (`Plan.children`) and laid out like
574
+ // a top-level list: the hall's presses, then its load. Hall 2
578
575
  // rests folded, as its DECLARED mean strip with its member
579
576
  // count; the host's state folds Hall 3.
580
577
  Plan.series.group(TargetOpsRow, {
@@ -583,7 +580,7 @@ export const planTargetState = example({
583
580
  label: (_r, hall) => hall,
584
581
  collapsed: (_r, hall) => hall.equal("Hall 2"),
585
582
  summaryAggregate: "mean",
586
- children: Plan.children(r => r.kind.unwrap("hall").members, [
583
+ children: Plan.children("members", [
587
584
  Plan.series.span(TargetOpsRow, {
588
585
  key: "presses", title: "Presses",
589
586
  match: r => r.kind.hasTag("press"),
@@ -591,9 +588,9 @@ export const planTargetState = example({
591
588
  value: r => rateText(r.kind.unwrap("press").rate),
592
589
  status: r => r.kind.unwrap("press").status,
593
590
  expand: r => r.kind.unwrap("press").detail,
594
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
595
- decisions: r => r.kind.unwrap("press").decisions,
596
- ports: r => r.kind.unwrap("press").ports,
591
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
592
+ decisions: Plan.items("decisions", { at: "at" }, (decision) => ({ applied: decision.applied })),
593
+ ports: Plan.values("ports", (port) => ({ label: some(port.label) })),
597
594
  }),
598
595
  Plan.series.heat(TargetOpsRow, {
599
596
  key: "hall-load", title: "Hall load",
@@ -601,7 +598,7 @@ export const planTargetState = example({
601
598
  label: r => r.kind.unwrap("load").name,
602
599
  sub: r => some(r.kind.unwrap("load").sub),
603
600
  expand: r => r.kind.unwrap("load").detail,
604
- cells: r => Plan.heatCells(weeklyCells(r.kind.unwrap("load").from, r.kind.unwrap("load").weekly), { min: 0, max: 100, warnAt: 95 }),
601
+ cells: Plan.heatCells(Plan.values("load", loadLook), { min: 0, max: 100, warnAt: 95 }),
605
602
  }),
606
603
  ]),
607
604
  }),
@@ -612,7 +609,7 @@ export const planTargetState = example({
612
609
  match: r => r.kind.hasTag("press").and(() => r.kind.unwrap("press").series.equal("flavours")),
613
610
  label: (_r, k) => k, id: true,
614
611
  value: r => rateText(r.kind.unwrap("press").rate),
615
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
612
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
616
613
  }),
617
614
  // …sheets, an applied decision and a port on a stacked two-line gutter…
618
615
  Plan.series.span(TargetOpsRow, {
@@ -621,25 +618,26 @@ export const planTargetState = example({
621
618
  label: (_r, k) => k, id: true, stacked: true,
622
619
  sub: r => r.kind.unwrap("press").sub,
623
620
  expand: r => r.kind.unwrap("press").detail,
624
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
625
- decisions: r => r.kind.unwrap("press").decisions,
626
- ports: r => r.kind.unwrap("press").ports,
621
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
622
+ decisions: Plan.items("decisions", { at: "at" }, (decision) => ({ applied: decision.applied })),
623
+ ports: Plan.values("ports", (port) => ({ label: some(port.label) })),
627
624
  }),
628
- // …a contract's presses nest under it (`children` — more of
629
- // this series, to any depth), and its row rolls their runs up…
625
+ // …a contract's presses nest under it (`children: "presses"` —
626
+ // more of this series, to any depth), and its row rolls their
627
+ // runs up…
630
628
  Plan.series.span(TargetOpsRow, {
631
629
  key: "rollup", title: "Rollup",
632
630
  match: r => r.kind.hasTag("press").and(() => r.kind.unwrap("press").series.equal("rollup")),
633
631
  label: (_r, k) => k, id: true,
634
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
635
- children: r => r.kind.unwrap("press").presses, rollup: "union",
632
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
633
+ children: "presses", rollup: "union",
636
634
  }),
637
635
  // …a delivery whose run lands past the window…
638
636
  Plan.series.span(TargetOpsRow, {
639
637
  key: "delivery", title: "Deliveries",
640
638
  match: r => r.kind.hasTag("press").and(() => r.kind.unwrap("press").series.equal("delivery")),
641
639
  label: (_r, k) => k, id: true,
642
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
640
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
643
641
  }),
644
642
  // …and a contract rolled up by status, under a section.
645
643
  Plan.series.section(TargetOpsRow, { key: "contract-block", title: "Contracts", meta: "1 row" }, [
@@ -647,8 +645,8 @@ export const planTargetState = example({
647
645
  key: "contracts", title: "Contracts",
648
646
  match: r => r.kind.hasTag("press").and(() => r.kind.unwrap("press").series.equal("contracts")),
649
647
  label: (_r, k) => k, id: true,
650
- runs: r => jobRuns(r.kind.unwrap("press").jobs),
651
- children: r => r.kind.unwrap("press").presses, rollup: "byStatus",
648
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
649
+ children: "presses", rollup: "byStatus",
652
650
  }),
653
651
  ]),
654
652
  // A section folded to start, resting as its strip — its
@@ -660,7 +658,7 @@ export const planTargetState = example({
660
658
  match: r => r.kind.hasTag("load"),
661
659
  label: r => r.kind.unwrap("load").name,
662
660
  sub: r => some(r.kind.unwrap("load").sub),
663
- cells: r => Plan.heatCells(weeklyCells(r.kind.unwrap("load").from, r.kind.unwrap("load").weekly), { min: 0, max: 100 }),
661
+ cells: Plan.heatCells(Plan.values("load", loadLook), { min: 0, max: 100 }),
664
662
  }),
665
663
  ]),
666
664
  // Chips derived from raw shifts on a two-line gutter…
@@ -670,25 +668,25 @@ export const planTargetState = example({
670
668
  label: r => r.kind.unwrap("crew").name, stacked: true,
671
669
  sub: r => r.kind.unwrap("crew").sub,
672
670
  expand: r => r.kind.unwrap("crew").detail,
673
- chips: r => shiftChips(r.kind.unwrap("crew").shifts),
671
+ chips: Plan.items("shifts", { from: "from", to: "to" }, shiftLook),
674
672
  }),
675
- // …and chip records stored as they draw, one with an icon.
673
+ // …and chips stored as they draw, one with an icon.
676
674
  Plan.series.section(TargetOpsRow, { key: "relief", title: "Relief pool", meta: "1 row" }, [
677
675
  Plan.series.cards(TargetOpsRow, {
678
676
  key: "pool", title: "Pool",
679
677
  match: r => r.kind.hasTag("crew").and(() => r.kind.unwrap("crew").pool),
680
678
  label: r => r.kind.unwrap("crew").name,
681
679
  value: r => r.kind.unwrap("crew").value,
682
- chips: r => r.kind.unwrap("crew").chips,
680
+ chips: Plan.items("chips", { from: "from", to: "to" }, (chip) => ({ label: chip.label, state: chip.state, icon: chip.icon })),
683
681
  }),
684
682
  ]),
685
683
  Plan.series.events(TargetOpsRow, {
686
684
  key: "milestones", title: "Milestones",
687
685
  match: r => r.kind.hasTag("stream").and(() => r.kind.unwrap("stream").cadence.hasTag("none")),
688
686
  label: r => r.kind.unwrap("stream").name, id: true,
689
- value: r => some(East.print(r.kind.unwrap("stream").marks.length())),
687
+ value: r => some(East.print(r.marks.size())),
690
688
  expand: r => r.kind.unwrap("stream").detail,
691
- marks: r => r.kind.unwrap("stream").marks,
689
+ marks: Plan.items("marks", { at: "at" }, (mark) => ({ kind: mark.kind, icon: mark.icon, label: mark.label })),
692
690
  }),
693
691
  Plan.series.section(TargetOpsRow, { key: "programs", title: "Programs", meta: "1 row" }, [
694
692
  Plan.series.events(TargetOpsRow, {
@@ -696,7 +694,7 @@ export const planTargetState = example({
696
694
  match: r => r.kind.hasTag("stream").and(() => r.kind.unwrap("stream").cadence.hasTag("some")),
697
695
  label: r => r.kind.unwrap("stream").name, id: true, stacked: true,
698
696
  sub: r => r.kind.unwrap("stream").cadence,
699
- marks: r => r.kind.unwrap("stream").marks,
697
+ marks: Plan.items("marks", { at: "at" }, (mark) => ({ kind: mark.kind, icon: mark.icon, label: mark.label })),
700
698
  }),
701
699
  ]),
702
700
  Plan.series.table(TargetOpsRow, {
@@ -704,7 +702,7 @@ export const planTargetState = example({
704
702
  match: r => r.kind.hasTag("tally"),
705
703
  label: r => r.kind.unwrap("tally").name,
706
704
  expand: r => r.kind.unwrap("tally").detail,
707
- cells: r => r.kind.unwrap("tally").cells,
705
+ cells: Plan.tableCells(Plan.values("delivered", (sheets) => ({ value: sheets }))),
708
706
  format: whole,
709
707
  }),
710
708
  // ONE machine, THREE views — a `views` series gives each
@@ -718,17 +716,17 @@ export const planTargetState = example({
718
716
  Plan.series.span(TargetOpsRow, {
719
717
  key: "finisher-jobs", title: "Finisher jobs",
720
718
  label: r => r.kind.unwrap("finisher").name, id: true,
721
- runs: r => jobRuns(r.kind.unwrap("finisher").jobs),
719
+ runs: Plan.items("jobs", { from: "start", to: "end" }, jobLook),
722
720
  }),
723
721
  Plan.series.chart(TargetOpsRow, {
724
722
  key: "finisher-util", title: "Finisher · utilisation",
725
723
  label: r => r.kind.unwrap("finisher").name, stacked: true, sub: _r => some("utilisation %"), height: "spark",
726
- layers: r => [Chart.Line(weeklyPoints(week(27n), r.kind.unwrap("finisher").util), { x: p => p.week, y: p => p.pct })],
724
+ layers: [Plan.line(Plan.values("util", (pct) => ({ y: pct })))],
727
725
  }),
728
726
  Plan.series.table(TargetOpsRow, {
729
727
  key: "finisher-sheets", title: "Finisher · sheets",
730
728
  label: r => r.kind.unwrap("finisher").name, stacked: true, sub: _r => some("sheets · plan Δ"),
731
- cells: r => r.kind.unwrap("finisher").sheets,
729
+ cells: Plan.tableCells(Plan.values("sheets", (sheets) => ({ value: some(sheets) }))),
732
730
  format: whole,
733
731
  }),
734
732
  ]),
@@ -764,7 +762,7 @@ export const planTargetState = example({
764
762
  // presets narrow the deliveries and keep the window.
765
763
  const slice = $.let(Slice.bindRecords("ex.plan.target", {
766
764
  deliveries: {
767
- source: Data.bindPaged(planTargetDeliveries, { indexes: [planTargetDeliveriesByAt, planTargetDeliveriesByRiskAt, planTargetDeliveriesByHallAt, planTargetDeliveriesByHallRiskAt] }),
765
+ source: Data.bindPaged(planTargetDeliveries, { indexes: ["plan_target_deliveries_by_at", "plan_target_deliveries_by_risk_at", "plan_target_deliveries_by_hall_at", "plan_target_deliveries_by_hall_risk_at"] }),
768
766
  label: "Deliveries", icon: { prefix: "fas", name: "truck" },
769
767
  fields: {
770
768
  hall: { label: "Hall", hints: ["Hall 1", "Hall 2"] },
@@ -938,37 +936,43 @@ export const planTargetState = example({
938
936
  // ============================================================================
939
937
  // planVariants — THE Plan configurator (#571): axis presets × style sweeps
940
938
  // ============================================================================
941
- /** A press job, its label already composed. */
939
+ /** A press job, under its id — its label already composed. */
942
940
  export const VariantsJob = StructType({
943
- key: StringType, label: StringType,
941
+ label: StringType,
944
942
  start: DateTimeType, end: DateTimeType, state: EventStateType,
945
943
  });
946
- /** One fortnightly reading of a measure. */
947
- export const VariantsMeasure = StructType({ week: DateTimeType, pct: FloatType });
948
- /** A van drop. */
949
- export const VariantsAlloc = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
950
- /** A crew shift, its label already composed. */
944
+ /** A van drop, under its id. */
945
+ export const VariantsAlloc = StructType({ at: DateTimeType, state: EventStateType });
946
+ /** A crew shift, under its id — its label already composed. */
951
947
  export const VariantsShift = StructType({
952
- key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
948
+ from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
953
949
  });
954
- /** One table reading — a bucket's instant and its value, if it has one. */
955
- export const VariantsReading = StructType({ at: DateTimeType, value: OptionType(FloatType) });
950
+ /** A milestone or an exception on a stream, under its id. */
951
+ export const VariantsMark = StructType({ at: DateTimeType, kind: Plan.Types.EventMarkKind, label: OptionType(StringType) });
956
952
  /**
957
953
  * ONE flat source: `series` names the series that claims the row, and every
958
- * channel a row's kind does not read stays empty.
954
+ * collection a row's kind does not read stays empty. The collections are Dicts
955
+ * each series reads by name (#1436): the things people drag by their ids, and
956
+ * a reading a fortnight keyed by its week.
959
957
  */
960
958
  export const VariantsOpsRow = StructType({
961
959
  series: StringType, label: StringType,
962
960
  sub: OptionType(StringType),
963
- jobs: ArrayType(VariantsJob),
964
- points: ArrayType(VariantsMeasure),
965
- cells: ArrayType(Plan.Types.HeatCell),
966
- allocs: ArrayType(VariantsAlloc),
967
- act: ArrayType(VariantsReading),
968
- plan: ArrayType(VariantsReading),
969
- shifts: ArrayType(VariantsShift),
970
- marks: ArrayType(Plan.Types.EventMark),
961
+ jobs: DictType(StringType, VariantsJob),
962
+ util: DictType(DateTimeType, FloatType),
963
+ load: DictType(DateTimeType, FloatType),
964
+ allocs: DictType(StringType, VariantsAlloc),
965
+ act: DictType(DateTimeType, FloatType),
966
+ plan: DictType(DateTimeType, FloatType),
967
+ shifts: DictType(StringType, VariantsShift),
968
+ marks: DictType(StringType, VariantsMark),
971
969
  });
970
+ /** Every collection a row holds, empty: a row names the few it fills. */
971
+ const VARIANTS_EMPTY = {
972
+ jobs: new Map(), util: new Map(), load: new Map(), allocs: new Map(), act: new Map(), plan: new Map(), shifts: new Map(), marks: new Map(),
973
+ };
974
+ /** A reading a fortnight from W27 (Monday 29 June 2026), each keyed by its week's instant. */
975
+ const fortnightsFromW27 = (readings) => atInstants(readings.map((v, i) => [new Date(Date.UTC(2026, 5, 29 + 14 * i)), v]));
972
976
  /**
973
977
  * Every row kind on purpose: the density sweep re-rhythms them all at once —
974
978
  * compact tightens the shared row and the bars in it, while a two-line gutter
@@ -976,71 +980,42 @@ export const VariantsOpsRow = StructType({
976
980
  * sweep re-widths every label. The readings are fortnightly from W27.
977
981
  */
978
982
  export const planVariantsOps = e3.input("plan_variants_ops", DictType(StringType, VariantsOpsRow), variant("value", new Map([
979
- ["util", { series: "util", label: "Util %", sub: none, jobs: [],
980
- points: [
981
- { week: new Date("2026-06-29T00:00:00Z"), pct: 46.0 }, { week: new Date("2026-07-13T00:00:00Z"), pct: 58.0 },
982
- { week: new Date("2026-07-27T00:00:00Z"), pct: 66.0 }, { week: new Date("2026-08-10T00:00:00Z"), pct: 72.0 },
983
- { week: new Date("2026-08-24T00:00:00Z"), pct: 84.0 }, { week: new Date("2026-09-07T00:00:00Z"), pct: 96.0 },
984
- ],
985
- cells: [], allocs: [], act: [], plan: [], shifts: [], marks: [] }],
986
- ["p03", { series: "press", label: "H1-P03", sub: some("12k sheets/h"),
987
- jobs: [
988
- { key: "j4642", label: "RUN · J-4642", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("in-progress", null) },
989
- { key: "j4663", label: "RUN · J-4663", start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
990
- ],
991
- points: [], cells: [], allocs: [], act: [], plan: [], shifts: [], marks: [] }],
992
- ["p04", { series: "press", label: "H1-P04", sub: none,
993
- jobs: [
994
- { key: "j4624", label: "RUN · J-4624", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), state: variant("actual", null) },
995
- ],
996
- points: [], cells: [], allocs: [], act: [], plan: [], shifts: [], marks: [] }],
997
- ["load", { series: "load", label: "H2 load", sub: some("%/wk"), jobs: [], points: [],
998
- cells: [
999
- { at: variant("time", new Date("2026-06-29T00:00:00Z")), value: some(46.0), label: some("46") },
1000
- { at: variant("time", new Date("2026-07-13T00:00:00Z")), value: some(58.0), label: some("58") },
1001
- { at: variant("time", new Date("2026-07-27T00:00:00Z")), value: some(66.0), label: some("66") },
1002
- { at: variant("time", new Date("2026-08-10T00:00:00Z")), value: some(72.0), label: some("72") },
1003
- { at: variant("time", new Date("2026-08-24T00:00:00Z")), value: some(84.0), label: some("84") },
1004
- { at: variant("time", new Date("2026-09-07T00:00:00Z")), value: some(96.0), label: some("96") },
1005
- ],
1006
- allocs: [], act: [], plan: [], shifts: [], marks: [] }],
1007
- ["van1", { series: "van", label: "Van 1", sub: none, jobs: [], points: [], cells: [],
1008
- allocs: [
1009
- { key: "a1", at: new Date("2026-07-06T00:00:00Z"), state: variant("confirmed", null) },
1010
- { key: "a2", at: new Date("2026-07-27T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
1011
- { key: "a3", at: new Date("2026-08-10T00:00:00Z"), state: variant("confirmed", null) },
1012
- ],
1013
- act: [], plan: [], shifts: [], marks: [] }],
983
+ ["util", { ...VARIANTS_EMPTY, series: "util", label: "Util %", sub: none, util: fortnightsFromW27([46.0, 58.0, 66.0, 72.0, 84.0, 96.0]) }],
984
+ ["p03", { ...VARIANTS_EMPTY, series: "press", label: "H1-P03", sub: some("12k sheets/h"),
985
+ jobs: new Map([
986
+ ["j4642", { label: "RUN · J-4642", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("in-progress", null) }],
987
+ ["j4663", { label: "RUN · J-4663", start: new Date("2026-08-03T00:00:00Z"), end: new Date("2026-08-24T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
988
+ ]) }],
989
+ ["p04", { ...VARIANTS_EMPTY, series: "press", label: "H1-P04", sub: none,
990
+ jobs: new Map([
991
+ ["j4624", { label: "RUN · J-4624", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), state: variant("actual", null) }],
992
+ ]) }],
993
+ ["load", { ...VARIANTS_EMPTY, series: "load", label: "H2 load", sub: some("%/wk"), load: fortnightsFromW27([46.0, 58.0, 66.0, 72.0, 84.0, 96.0]) }],
994
+ ["van1", { ...VARIANTS_EMPTY, series: "van", label: "Van 1", sub: none,
995
+ allocs: new Map([
996
+ ["a1", { at: new Date("2026-07-06T00:00:00Z"), state: variant("confirmed", null) }],
997
+ ["a2", { at: new Date("2026-07-27T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
998
+ ["a3", { at: new Date("2026-08-10T00:00:00Z"), state: variant("confirmed", null) }],
999
+ ]) }],
1014
1000
  // Delivered sheets and their signed Δ against plan — the table row's
1015
1001
  // two positions.
1016
- ["desp", { series: "desp", label: "Delivered · k", sub: none, jobs: [], points: [], cells: [], allocs: [],
1017
- act: [
1018
- { at: new Date("2026-06-29T00:00:00Z"), value: some(69.0) }, { at: new Date("2026-07-13T00:00:00Z"), value: some(87.0) },
1019
- { at: new Date("2026-07-27T00:00:00Z"), value: some(99.0) }, { at: new Date("2026-08-10T00:00:00Z"), value: some(108.0) },
1020
- { at: new Date("2026-08-24T00:00:00Z"), value: some(126.0) }, { at: new Date("2026-09-07T00:00:00Z"), value: some(144.0) },
1021
- ],
1022
- plan: [
1023
- { at: new Date("2026-06-29T00:00:00Z"), value: some(-24.0) }, { at: new Date("2026-07-13T00:00:00Z"), value: some(-12.0) },
1024
- { at: new Date("2026-07-27T00:00:00Z"), value: some(-4.0) }, { at: new Date("2026-08-10T00:00:00Z"), value: some(2.0) },
1025
- { at: new Date("2026-08-24T00:00:00Z"), value: some(14.0) }, { at: new Date("2026-09-07T00:00:00Z"), value: some(26.0) },
1026
- ],
1027
- shifts: [], marks: [] }],
1028
- ["crewA", { series: "crew", label: "Crew A", sub: none, jobs: [], points: [], cells: [], allocs: [], act: [], plan: [],
1029
- shifts: [
1030
- { key: "s1", from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), label: "80h", state: variant("confirmed", null) },
1031
- { key: "s2", from: new Date("2026-07-27T00:00:00Z"), to: new Date("2026-08-10T00:00:00Z"), label: "+64h", state: variant("proposed", variant("recommended", null)) },
1032
- ],
1033
- marks: [] }],
1034
- ["ms", { series: "ms", label: "Milestones", sub: none, jobs: [], points: [], cells: [], allocs: [], act: [], plan: [], shifts: [],
1035
- marks: [
1036
- { key: "k", at: variant("time", new Date("2026-07-13T00:00:00Z")), kind: variant("milestone", null), icon: none, label: some("KICKOFF") },
1037
- { key: "a", at: variant("time", new Date("2026-08-17T00:00:00Z")), kind: variant("exception", null), icon: none, label: some("AUDIT") },
1038
- ] }],
1039
- ["p11", { series: "gpress", label: "H3-P11", sub: none,
1040
- jobs: [
1041
- { key: "j4903", label: "RUN · J-4903", start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), state: variant("confirmed", null) },
1042
- ],
1043
- points: [], cells: [], allocs: [], act: [], plan: [], shifts: [], marks: [] }],
1002
+ ["desp", { ...VARIANTS_EMPTY, series: "desp", label: "Delivered · k", sub: none,
1003
+ act: fortnightsFromW27([69.0, 87.0, 99.0, 108.0, 126.0, 144.0]),
1004
+ plan: fortnightsFromW27([-24.0, -12.0, -4.0, 2.0, 14.0, 26.0]) }],
1005
+ ["crewA", { ...VARIANTS_EMPTY, series: "crew", label: "Crew A", sub: none,
1006
+ shifts: new Map([
1007
+ ["s1", { from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), label: "80h", state: variant("confirmed", null) }],
1008
+ ["s2", { from: new Date("2026-07-27T00:00:00Z"), to: new Date("2026-08-10T00:00:00Z"), label: "+64h", state: variant("proposed", variant("recommended", null)) }],
1009
+ ]) }],
1010
+ ["ms", { ...VARIANTS_EMPTY, series: "ms", label: "Milestones", sub: none,
1011
+ marks: new Map([
1012
+ ["k", { at: new Date("2026-07-13T00:00:00Z"), kind: variant("milestone", null), label: some("KICKOFF") }],
1013
+ ["a", { at: new Date("2026-08-17T00:00:00Z"), kind: variant("exception", null), label: some("AUDIT") }],
1014
+ ]) }],
1015
+ ["p11", { ...VARIANTS_EMPTY, series: "gpress", label: "H3-P11", sub: none,
1016
+ jobs: new Map([
1017
+ ["j4903", { label: "RUN · J-4903", start: new Date("2026-07-13T00:00:00Z"), end: new Date("2026-08-10T00:00:00Z"), state: variant("confirmed", null) }],
1018
+ ]) }],
1044
1019
  ])));
1045
1020
  /**
1046
1021
  * THE Plan configurator — the slot-2 variant-space surface the retired Gantt /
@@ -1085,39 +1060,37 @@ export const planVariants = example({
1085
1060
  key: "util", title: "Utilisation",
1086
1061
  match: r => r.series.equal("util"),
1087
1062
  label: r => r.label, id: true, height: "spark", expandable: true,
1088
- layers: r => [Chart.Line(r.points, { x: p => p.week, y: p => p.pct })],
1063
+ layers: [Plan.line(Plan.values("util", (pct) => ({ y: pct })))],
1089
1064
  }),
1090
1065
  Plan.series.span(VariantsOpsRow, {
1091
1066
  key: "press", title: "Press jobs",
1092
1067
  match: r => r.series.equal("press"),
1093
1068
  label: r => r.label, id: true, sub: r => r.sub,
1094
- runs: r => r.jobs.map((_$, j) => Plan.run({
1095
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
1096
- })),
1069
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
1097
1070
  }),
1098
1071
  Plan.series.heat(VariantsOpsRow, {
1099
1072
  key: "load", title: "Hall load",
1100
1073
  match: r => r.series.equal("load"),
1101
1074
  label: r => r.label, sub: r => r.sub,
1102
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100 }),
1075
+ cells: Plan.heatCells(Plan.values("load", (pct) => ({ value: pct, label: some(East.Float.printFixed(pct, 0n)) })), { min: 0, max: 100 }),
1103
1076
  }),
1104
1077
  Plan.series.buckets(VariantsOpsRow, {
1105
1078
  key: "van", title: "Van drops",
1106
1079
  match: r => r.series.equal("van"),
1107
1080
  label: r => r.label,
1108
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
1081
+ events: Plan.items("allocs", { at: "at" }, (alloc) => ({ state: alloc.state })),
1109
1082
  }),
1110
1083
  // The positions STACKED — the row grows a line per position.
1111
1084
  Plan.series.table(VariantsOpsRow, {
1112
1085
  key: "desp", title: "Deliveries",
1113
1086
  match: r => r.series.equal("desp"),
1114
1087
  label: r => r.label, split: "vertical",
1115
- series: r => [
1116
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(r.act) }),
1088
+ series: [
1089
+ Plan.tableSeries({ strong: true, cells: Plan.tableCells(Plan.values("act", (n) => ({ value: n }))) }),
1117
1090
  Plan.tableSeries({
1118
1091
  tone: "muted",
1119
1092
  format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1120
- cells: Plan.tableCells(r.plan),
1093
+ cells: Plan.tableCells(Plan.values("plan", (n) => ({ value: n }))),
1121
1094
  }),
1122
1095
  ],
1123
1096
  format: Format.Number({ maximumFractionDigits: 0n }),
@@ -1126,13 +1099,13 @@ export const planVariants = example({
1126
1099
  key: "crew", title: "Crew shifts",
1127
1100
  match: r => r.series.equal("crew"),
1128
1101
  label: r => r.label,
1129
- chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
1102
+ chips: Plan.items("shifts", { from: "from", to: "to" }, (shift) => ({ label: shift.label, state: shift.state })),
1130
1103
  }),
1131
1104
  Plan.series.events(VariantsOpsRow, {
1132
1105
  key: "ms", title: "Milestones",
1133
1106
  match: r => r.series.equal("ms"),
1134
1107
  label: r => r.label, id: true,
1135
- marks: r => r.marks,
1108
+ marks: Plan.items("marks", { at: "at" }, (mark) => ({ kind: mark.kind, label: mark.label })),
1136
1109
  }),
1137
1110
  // A section header, so the group-toggle callback has something to fire on.
1138
1111
  Plan.series.section(VariantsOpsRow, { key: "hall3", title: "Hall 3", meta: "1 row" }, [
@@ -1140,9 +1113,7 @@ export const planVariants = example({
1140
1113
  key: "gpress", title: "Grouped jobs",
1141
1114
  match: r => r.series.equal("gpress"),
1142
1115
  label: r => r.label, id: true,
1143
- runs: r => r.jobs.map((_$, j) => Plan.run({
1144
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
1145
- })),
1116
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
1146
1117
  }),
1147
1118
  ]),
1148
1119
  ], ArrayType(Plan.Types.Series(VariantsOpsRow)));
@@ -1217,36 +1188,51 @@ export const planVariants = example({
1217
1188
  // planMeasures — measure rows over weekly readings, folded to the resolution
1218
1189
  // the slice states (#824)
1219
1190
  // ============================================================================
1191
+ /** A band's bounds at a week: its low and high edges. */
1192
+ export const MeasureBand = StructType({ lo: FloatType, hi: FloatType });
1220
1193
  /**
1221
- * A measure — its readings a week apiece from W23, the window's first week, a
1222
- * week with none read nothing: `readings` the series its row draws, and
1223
- * `second` the set a row pairs with it — the stacked columns' second hall, the
1224
- * dual chart's on-time line, a table's Δ against plan; `lo` / `hi` a band's
1225
- * bounds; and a finishing line's capacity, stored in the element vocabulary
1226
- * itself (`segments`). A measure nests (`children`): a hall holds its presses,
1227
- * a top its contracts, a contract its orders — the hierarchy is the data's own
1228
- * (#822), to whatever depth it has.
1194
+ * A measure — its values a week apiece, each keyed by its week's instant (#1436),
1195
+ * each collection read by name: `readings`, the cells a heat or table row
1196
+ * draws, a week reading none its no-data cell; `second`, the position a table
1197
+ * row pairs with them (a Δ against plan, what left); `points`, the readings a
1198
+ * chart row draws, and `paired`, the series it pairs with them (the stacked
1199
+ * columns' second hall, the dual chart's on-time line); `band`, a band's
1200
+ * bounds; and `segments`, a finishing line's capacity compositions. A measure
1201
+ * nests (`children`): a hall holds its presses, a top its contracts, a contract
1202
+ * its orders — the hierarchy is the data's own (#822), to whatever depth it has.
1229
1203
  */
1230
1204
  export const Measure = RecursiveType((self) => StructType({
1231
1205
  series: StringType, label: StringType,
1232
1206
  sub: OptionType(StringType), value: OptionType(StringType),
1233
- readings: ArrayType(OptionType(FloatType)),
1234
- second: ArrayType(OptionType(FloatType)),
1235
- lo: ArrayType(FloatType), hi: ArrayType(FloatType),
1236
- segments: ArrayType(Plan.Types.SegmentCell),
1207
+ readings: DictType(DateTimeType, OptionType(FloatType)),
1208
+ second: DictType(DateTimeType, OptionType(FloatType)),
1209
+ points: DictType(DateTimeType, FloatType),
1210
+ paired: DictType(DateTimeType, FloatType),
1211
+ band: DictType(DateTimeType, MeasureBand),
1212
+ segments: DictType(DateTimeType, ArrayType(Plan.Types.Segment)),
1237
1213
  children: DictType(StringType, self),
1238
1214
  }));
1215
+ /** Every collection a measure holds, empty: a measure names the few it fills. */
1216
+ const MEASURE_EMPTY = {
1217
+ readings: new Map(), second: new Map(), points: new Map(), paired: new Map(), band: new Map(), segments: new Map(), children: new Map(),
1218
+ };
1219
+ /** A value a week from W23 (Monday 1 June 2026), each keyed by its week's instant. */
1220
+ const fromW23 = (weekly) => atInstants(weekly.map((v, i) => [new Date(Date.UTC(2026, 5, 1 + 7 * i)), v]));
1239
1221
  /** On-time %, a week apiece from W23: July's weeks run under the 92 breach. */
1240
- const MEASURE_ON_TIME = [some(96.1), some(96.4), some(96.8), some(97.0), some(96.2), some(91.0), some(89.4), some(88.9), some(90.6), some(93.8), some(94.0), some(94.2)];
1222
+ const MEASURE_ON_TIME = [96.1, 96.4, 96.8, 97.0, 96.2, 91.0, 89.4, 88.9, 90.6, 93.8, 94.0, 94.2];
1241
1223
  /** Sheets printed, thousands a week — Hall 1's, then Hall 2's. */
1242
- const MEASURE_PRINTED_H1 = [some(28.0), some(34.0), some(40.0), some(29.0), some(35.0), some(41.0), some(30.0), some(36.0), some(42.0), some(31.0), some(37.0), some(43.0)];
1243
- const MEASURE_PRINTED_H2 = [some(14.0), some(19.0), some(24.0), some(16.0), some(21.0), some(26.0), some(18.0), some(23.0), some(15.0), some(20.0), some(25.0), some(17.0)];
1224
+ const MEASURE_PRINTED_H1 = [28.0, 34.0, 40.0, 29.0, 35.0, 41.0, 30.0, 36.0, 42.0, 31.0, 37.0, 43.0];
1225
+ const MEASURE_PRINTED_H2 = [14.0, 19.0, 24.0, 16.0, 21.0, 26.0, 18.0, 23.0, 15.0, 20.0, 25.0, 17.0];
1244
1226
  /** An order's delivered sheets, thousands a week — the week of 3 August none. */
1245
1227
  const MEASURE_ACT = [some(40.0), some(47.0), some(54.0), some(61.0), some(68.0), some(75.0), some(82.0), some(89.0), some(96.0), none, some(110.0), some(117.0)];
1246
1228
  /** The Δ against plan beside them — some weeks none. */
1247
1229
  const MEASURE_DELTA = [some(-8.0), some(-6.5), some(-5.0), none, some(-2.0), some(-0.5), some(1.0), none, some(4.0), some(5.5), some(7.0), none];
1248
1230
  /** What left as the sheets came in. */
1249
1231
  const MEASURE_OUT = [some(-12.0), some(-15.0), some(-18.0), some(-21.0), some(-24.0), some(-27.0), some(-30.0), some(-33.0), some(-36.0), some(-39.0), some(-42.0), some(-45.0)];
1232
+ /** An order: its delivered sheets, and the position beside them. */
1233
+ const order = (series, label, second = []) => ({ ...MEASURE_EMPTY, series, label, sub: none, value: none, readings: fromW23(MEASURE_ACT), second: fromW23(second) });
1234
+ /** A parent measure: no readings of its own, its children's subtotal. */
1235
+ const parent = (series, label, children) => ({ ...MEASURE_EMPTY, series, label, sub: none, value: none, children: new Map(children) });
1250
1236
  /**
1251
1237
  * Twelve weeks of every measure, W23–W34 — from the first Monday of June, so
1252
1238
  * the first month's column starts with the first week; the weeks after now
@@ -1254,128 +1240,96 @@ const MEASURE_OUT = [some(-12.0), some(-15.0), some(-18.0), some(-21.0), some(-2
1254
1240
  */
1255
1241
  export const planMeasureReadings = e3.input("plan_measure_readings", DictType(StringType, Measure), variant("value", new Map([
1256
1242
  // ── Output ──
1257
- ["spark", { series: "spark", label: "On-time", sub: none, value: some("94.2%"),
1258
- readings: MEASURE_ON_TIME, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1243
+ ["spark", { ...MEASURE_EMPTY, series: "spark", label: "On-time", sub: none, value: some("94.2%"), points: fromW23(MEASURE_ON_TIME) }],
1259
1244
  // A running total: each week the sheets printed so far.
1260
- ["cum", { series: "cum", label: "Cumulative · k", sub: none, value: some("194 k sheets"),
1261
- readings: [some(40.0), some(54.0), some(68.0), some(82.0), some(96.0), some(110.0), some(124.0), some(138.0), some(152.0), some(166.0), some(180.0), some(194.0)],
1262
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1263
- ["stacked", { series: "stacked", label: "Printed · k", sub: some("k/wk"), value: none,
1264
- readings: MEASURE_PRINTED_H1, second: MEASURE_PRINTED_H2, lo: [], hi: [], segments: [], children: new Map() }],
1265
- ["ppm", { series: "ppm", label: "Defects · ppm", sub: none, value: some("161"),
1266
- readings: [some(120.0), some(157.0), some(134.0), some(171.0), some(148.0), some(125.0), some(162.0), some(139.0), some(176.0), some(153.0), some(130.0), some(167.0)],
1267
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1268
- ["refs", { series: "refs", label: "On-time + refs", sub: none, value: none,
1269
- readings: MEASURE_ON_TIME, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1245
+ ["cum", { ...MEASURE_EMPTY, series: "cum", label: "Cumulative · k", sub: none, value: some("194 k sheets"),
1246
+ points: fromW23([40.0, 54.0, 68.0, 82.0, 96.0, 110.0, 124.0, 138.0, 152.0, 166.0, 180.0, 194.0]) }],
1247
+ ["stacked", { ...MEASURE_EMPTY, series: "stacked", label: "Printed · k", sub: some("k/wk"), value: none,
1248
+ points: fromW23(MEASURE_PRINTED_H1), paired: fromW23(MEASURE_PRINTED_H2) }],
1249
+ ["ppm", { ...MEASURE_EMPTY, series: "ppm", label: "Defects · ppm", sub: none, value: some("161"),
1250
+ points: fromW23([120.0, 157.0, 134.0, 171.0, 148.0, 125.0, 162.0, 139.0, 176.0, 153.0, 130.0, 167.0]) }],
1251
+ ["refs", { ...MEASURE_EMPTY, series: "refs", label: "On-time + refs", sub: none, value: none, points: fromW23(MEASURE_ON_TIME) }],
1270
1252
  // Output columns on the left axis; the on-time line and its ±3 band on the right.
1271
- ["dual", { series: "dual", label: "Out + on-time", sub: none, value: none,
1272
- readings: MEASURE_PRINTED_H1, second: MEASURE_ON_TIME,
1273
- lo: [93.1, 93.4, 93.8, 94.0, 93.2, 88.0, 86.4, 85.9, 87.6, 90.8, 91.0, 91.2],
1274
- hi: [99.1, 99.4, 99.8, 100.0, 99.2, 94.0, 92.4, 91.9, 93.6, 96.8, 97.0, 97.2],
1275
- segments: [], children: new Map() }],
1253
+ ["dual", { ...MEASURE_EMPTY, series: "dual", label: "Out + on-time", sub: none, value: none,
1254
+ points: fromW23(MEASURE_PRINTED_H1), paired: fromW23(MEASURE_ON_TIME),
1255
+ band: fromW23([
1256
+ { lo: 93.1, hi: 99.1 }, { lo: 93.4, hi: 99.4 }, { lo: 93.8, hi: 99.8 }, { lo: 94.0, hi: 100.0 }, { lo: 93.2, hi: 99.2 }, { lo: 88.0, hi: 94.0 },
1257
+ { lo: 86.4, hi: 92.4 }, { lo: 85.9, hi: 91.9 }, { lo: 87.6, hi: 93.6 }, { lo: 90.8, hi: 96.8 }, { lo: 91.0, hi: 97.0 }, { lo: 91.2, hi: 97.2 },
1258
+ ]) }],
1276
1259
  // ── Load ──
1277
1260
  // A hall with no readings of its own: its row is its presses' per-bucket
1278
1261
  // mean. H1-P03 read nothing in the week of 29 June; H1-P04 nothing in
1279
1262
  // August, which folds to a month with no data.
1280
- ["hall1", { series: "depth", label: "Hall 1", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1281
- children: new Map([
1282
- ["p03h", { series: "depth", label: "H1-P03", sub: none, value: none, second: [], lo: [], hi: [], segments: [], children: new Map(),
1283
- readings: [some(46.0), some(52.0), some(58.0), some(61.0), none, some(72.0), some(78.0), some(84.0), some(90.0), some(96.0), some(98.0), some(92.0)] }],
1284
- ["p04h", { series: "depth", label: "H1-P04", sub: none, value: none, second: [], lo: [], hi: [], segments: [], children: new Map(),
1285
- readings: [some(44.0), some(50.0), some(55.0), some(60.0), some(63.0), some(70.0), some(74.0), some(80.0), some(86.0), none, none, none] }],
1286
- ]) }],
1287
- ["peak", { series: "peak", label: "Hall 2 peak %", sub: none, value: none,
1288
- readings: [some(45.0), some(62.0), some(79.0), some(46.0), some(63.0), some(80.0), some(47.0), some(64.0), some(81.0), some(48.0), some(65.0), some(82.0)],
1289
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1263
+ ["hall1", parent("depth", "Hall 1", [
1264
+ ["p03h", { ...MEASURE_EMPTY, series: "depth", label: "H1-P03", sub: none, value: none,
1265
+ readings: fromW23([some(46.0), some(52.0), some(58.0), some(61.0), none, some(72.0), some(78.0), some(84.0), some(90.0), some(96.0), some(98.0), some(92.0)]) }],
1266
+ ["p04h", { ...MEASURE_EMPTY, series: "depth", label: "H1-P04", sub: none, value: none,
1267
+ readings: fromW23([some(44.0), some(50.0), some(55.0), some(60.0), some(63.0), some(70.0), some(74.0), some(80.0), some(86.0), none, none, none]) }],
1268
+ ])],
1269
+ ["peak", { ...MEASURE_EMPTY, series: "peak", label: "Hall 2 peak %", sub: none, value: none,
1270
+ readings: fromW23([some(45.0), some(62.0), some(79.0), some(46.0), some(63.0), some(80.0), some(47.0), some(64.0), some(81.0), some(48.0), some(65.0), some(82.0)]) }],
1290
1271
  // Crew A's booked fraction of its hours, a week apiece.
1291
- ["booked", { series: "booked", label: "Crew A", sub: some("booked h"), value: none,
1292
- readings: [some(0.9), some(0.84), some(0.79), some(0.74), some(0.68), some(0.62), some(0.57), some(0.52), some(0.46), some(0.41), some(0.35), some(0.3)],
1293
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1272
+ ["booked", { ...MEASURE_EMPTY, series: "booked", label: "Crew A", sub: some("booked h"), value: none,
1273
+ points: fromW23([0.9, 0.84, 0.79, 0.74, 0.68, 0.62, 0.57, 0.52, 0.46, 0.41, 0.35, 0.3]) }],
1294
1274
  // ── Finishing ──
1295
- // Capacity compositions — plain `{ fill, weight, label }` records. June's
1296
- // and August's are their month's only one, so they keep their labels.
1297
- ["finishing", { series: "segments", label: "Finishing line", sub: some("capacity"), value: none,
1298
- readings: [], second: [], lo: [], hi: [], children: new Map(),
1299
- segments: [
1300
- { at: variant("time", new Date("2026-06-29T00:00:00Z")), segments: [
1275
+ // Capacity compositions — plain `{ fill, weight, label }` records, a
1276
+ // week's at its week. June's and August's are their month's only one, so
1277
+ // they keep their labels.
1278
+ ["finishing", { ...MEASURE_EMPTY, series: "segments", label: "Finishing line", sub: some("capacity"), value: none,
1279
+ segments: atInstants([
1280
+ [new Date("2026-06-29T00:00:00Z"), [
1301
1281
  { fill: variant("success", null), weight: 60.0, label: some("60%") },
1302
1282
  { fill: variant("warning", null), weight: 25.0, label: some("25%") },
1303
1283
  { fill: variant("slack", null), weight: 15.0, label: none },
1304
- ] },
1305
- { at: variant("time", new Date("2026-07-06T00:00:00Z")), segments: [
1284
+ ]],
1285
+ [new Date("2026-07-06T00:00:00Z"), [
1306
1286
  { fill: variant("success", null), weight: 70.0, label: some("70%") },
1307
1287
  { fill: variant("slack", null), weight: 30.0, label: none },
1308
- ] },
1309
- { at: variant("time", new Date("2026-07-13T00:00:00Z")), segments: [
1288
+ ]],
1289
+ [new Date("2026-07-13T00:00:00Z"), [
1310
1290
  { fill: variant("danger", null), weight: 40.0, label: some("40%") },
1311
1291
  { fill: variant("free", null), weight: 60.0, label: none },
1312
- ] },
1313
- { at: variant("time", new Date("2026-08-10T00:00:00Z")), segments: [
1292
+ ]],
1293
+ [new Date("2026-08-10T00:00:00Z"), [
1314
1294
  { fill: variant("success", null), weight: 55.0, label: some("55%") },
1315
1295
  { fill: variant("warning", null), weight: 30.0, label: some("30%") },
1316
1296
  { fill: variant("slack", null), weight: 15.0, label: none },
1317
- ] },
1318
- ] }],
1297
+ ]],
1298
+ ]) }],
1319
1299
  // ── Deliveries ──
1320
1300
  // Two levels of nesting — a top holds its contracts, a contract its
1321
1301
  // orders; every level with no readings of its own is a subtotal.
1322
- ["deliveries", { series: "orders", label: "Deliveries", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1323
- children: new Map([
1324
- ["contract-a", { series: "orders", label: "Contract A", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1325
- children: new Map([
1326
- ["j6188", { series: "orders", label: "J-6188", sub: none, value: none, readings: MEASURE_ACT, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1327
- ["j6204", { series: "orders", label: "J-6204", sub: none, value: none, readings: MEASURE_ACT, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1328
- ]) }],
1329
- ["contract-b", { series: "orders", label: "Contract B", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1330
- children: new Map([
1331
- ["j6219", { series: "orders", label: "J-6219", sub: none, value: none, readings: MEASURE_ACT, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1332
- ]) }],
1333
- ]) }],
1334
- ["reprints", { series: "orders", label: "Reprints", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1335
- children: new Map([
1336
- ["contract-b", { series: "orders", label: "Contract B", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1337
- children: new Map([
1338
- ["rp-0031", { series: "orders", label: "RP-0031", sub: none, value: none, readings: MEASURE_ACT, second: [], lo: [], hi: [], segments: [], children: new Map() }],
1339
- ]) }],
1340
- ]) }],
1302
+ ["deliveries", parent("orders", "Deliveries", [
1303
+ ["contract-a", parent("orders", "Contract A", [["j6188", order("orders", "J-6188")], ["j6204", order("orders", "J-6204")]])],
1304
+ ["contract-b", parent("orders", "Contract B", [["j6219", order("orders", "J-6219")]])],
1305
+ ])],
1306
+ ["reprints", parent("orders", "Reprints", [
1307
+ ["contract-b", parent("orders", "Contract B", [["rp-0031", order("orders", "RP-0031")]])],
1308
+ ])],
1341
1309
  // Footer emphasis, a negative and the em-dash: July read nothing at all.
1342
- ["net", { series: "net", label: "Net flow", sub: none, value: none,
1343
- readings: [some(22.0), some(-26.0), some(8.0), some(-4.0), some(12.0), none, none, none, none, some(-30.0), some(12.0), some(6.0)],
1344
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1310
+ ["net", { ...MEASURE_EMPTY, series: "net", label: "Net flow", sub: none, value: none,
1311
+ readings: fromW23([some(22.0), some(-26.0), some(8.0), some(-4.0), some(12.0), none, none, none, none, some(-30.0), some(12.0), some(6.0)]) }],
1345
1312
  // Multi-value rows — the actuals and the Δ per row. The SPLIT (how the
1346
1313
  // positions sit against each other) and the GUTTER (one line or two) are
1347
1314
  // independent choices, so all four combinations are here: the pair that
1348
1315
  // reads well depends on the numbers, not on the split.
1349
- ["actplan", { series: "actplan", label: "Act · Δ plan", sub: some("k/wk"), value: none,
1350
- readings: MEASURE_ACT, second: MEASURE_DELTA, lo: [], hi: [], segments: [], children: new Map() }],
1351
- ["inout", { series: "inout", label: "In / out", sub: none, value: none,
1352
- readings: MEASURE_ACT, second: MEASURE_OUT, lo: [], hi: [], segments: [], children: new Map() }],
1316
+ ["actplan", { ...order("actplan", "Act · Δ plan", MEASURE_DELTA), sub: some("k/wk") }],
1317
+ ["inout", order("inout", "In / out", MEASURE_OUT)],
1353
1318
  // Horizontal, on a ONE-line gutter — the pair reads as a single fact
1354
1319
  // ("booked beside free"), so a sub label would only repeat it.
1355
- ["sidebyside", { series: "sidebyside", label: "Booked · free", sub: none, value: none,
1356
- readings: MEASURE_ACT, second: MEASURE_OUT, lo: [], hi: [], segments: [], children: new Map() }],
1320
+ ["sidebyside", order("sidebyside", "Booked · free", MEASURE_OUT)],
1357
1321
  // Vertical, on a TWO-line gutter — the stack needs the unit spelled out,
1358
1322
  // because the positions are the same measure at two times.
1359
- ["overunder", { series: "overunder", label: "Act / plan", sub: some("k/wk"), value: none,
1360
- readings: MEASURE_ACT, second: MEASURE_DELTA, lo: [], hi: [], segments: [], children: new Map() }],
1323
+ ["overunder", { ...order("overunder", "Act / plan", MEASURE_DELTA), sub: some("k/wk") }],
1361
1324
  // NESTED and multi-value: the subtotal parent mirrors its members, an act
1362
1325
  // subtotal beside a Δ subtotal.
1363
- ["lots", { series: "lot", label: "Lots", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1364
- children: new Map([
1365
- ["lt-1", { series: "lot", label: "LT-2201", sub: none, value: none, readings: MEASURE_ACT, second: MEASURE_DELTA, lo: [], hi: [], segments: [], children: new Map() }],
1366
- ["lt-2", { series: "lot", label: "LT-2202", sub: none, value: none, readings: MEASURE_ACT, second: MEASURE_DELTA, lo: [], hi: [], segments: [], children: new Map() }],
1367
- ]) }],
1326
+ ["lots", parent("lot", "Lots", [["lt-1", order("lot", "LT-2201", MEASURE_DELTA)], ["lt-2", order("lot", "LT-2202", MEASURE_DELTA)]])],
1368
1327
  // The same, STACKED: members and their subtotal both put the two positions
1369
1328
  // on their own lines, so the parent has to grow too.
1370
- ["stacks", { series: "stack", label: "Stacks", sub: none, value: none, readings: [], second: [], lo: [], hi: [], segments: [],
1371
- children: new Map([
1372
- ["st-1", { series: "stack", label: "ST-3301", sub: none, value: none, readings: MEASURE_ACT, second: MEASURE_OUT, lo: [], hi: [], segments: [], children: new Map() }],
1373
- ["st-2", { series: "stack", label: "ST-3302", sub: none, value: none, readings: MEASURE_ACT, second: MEASURE_OUT, lo: [], hi: [], segments: [], children: new Map() }],
1374
- ]) }],
1329
+ ["stacks", parent("stack", "Stacks", [["st-1", order("stack", "ST-3301", MEASURE_OUT)], ["st-2", order("stack", "ST-3302", MEASURE_OUT)]])],
1375
1330
  // A balance: the paper in stock, thousands of sheets, each week's close.
1376
- ["stock", { series: "stock", label: "Paper stock · k", sub: none, value: none,
1377
- readings: [some(420.0), some(398.0), some(376.0), some(354.0), some(332.0), some(310.0), some(288.0), some(266.0), some(244.0), some(222.0), some(200.0), some(178.0)],
1378
- second: [], lo: [], hi: [], segments: [], children: new Map() }],
1331
+ ["stock", { ...MEASURE_EMPTY, series: "stock", label: "Paper stock · k", sub: none, value: none,
1332
+ readings: fromW23([some(420.0), some(398.0), some(376.0), some(354.0), some(332.0), some(310.0), some(288.0), some(266.0), some(244.0), some(222.0), some(200.0), some(178.0)]) }],
1379
1333
  ])));
1380
1334
  /** A week the readings cover: its first instant, and its name. */
1381
1335
  export const MeasureWeek = StructType({ week: DateTimeType, label: StringType });
@@ -1384,22 +1338,26 @@ export const MeasureWeek = StructType({ week: DateTimeType, label: StringType })
1384
1338
  * the library's Weeks tab lists, the rail's `weeks` source holding the
1385
1339
  * canvas's window in its range (#1344).
1386
1340
  */
1387
- export const planMeasureWeeks = e3.record("plan_measure_weeks", DictType(StringType, MeasureWeek), new Map([
1388
- ["W23", { week: new Date("2026-06-01T00:00:00Z"), label: "W23 · 1 Jun" }],
1389
- ["W24", { week: new Date("2026-06-08T00:00:00Z"), label: "W24 · 8 Jun" }],
1390
- ["W25", { week: new Date("2026-06-15T00:00:00Z"), label: "W25 · 15 Jun" }],
1391
- ["W26", { week: new Date("2026-06-22T00:00:00Z"), label: "W26 · 22 Jun" }],
1392
- ["W27", { week: new Date("2026-06-29T00:00:00Z"), label: "W27 · 29 Jun" }],
1393
- ["W28", { week: new Date("2026-07-06T00:00:00Z"), label: "W28 · 6 Jul" }],
1394
- ["W29", { week: new Date("2026-07-13T00:00:00Z"), label: "W29 · 13 Jul" }],
1395
- ["W30", { week: new Date("2026-07-20T00:00:00Z"), label: "W30 · 20 Jul" }],
1396
- ["W31", { week: new Date("2026-07-27T00:00:00Z"), label: "W31 · 27 Jul" }],
1397
- ["W32", { week: new Date("2026-08-03T00:00:00Z"), label: "W32 · 3 Aug" }],
1398
- ["W33", { week: new Date("2026-08-10T00:00:00Z"), label: "W33 · 10 Aug" }],
1399
- ["W34", { week: new Date("2026-08-17T00:00:00Z"), label: "W34 · 17 Aug" }],
1400
- ]));
1401
- /** The weeks by their first instant: what the window narrows them through. */
1402
- export const planMeasureWeeksByWeek = e3.recordIndex("plan_measure_weeks_by_week", planMeasureWeeks, { on: ["week"] });
1341
+ export const planMeasureWeeks = e3.record("plan_measure_weeks", DictType(StringType, MeasureWeek), {
1342
+ initial: new Map([
1343
+ ["W23", { week: new Date("2026-06-01T00:00:00Z"), label: "W23 · 1 Jun" }],
1344
+ ["W24", { week: new Date("2026-06-08T00:00:00Z"), label: "W24 · 8 Jun" }],
1345
+ ["W25", { week: new Date("2026-06-15T00:00:00Z"), label: "W25 · 15 Jun" }],
1346
+ ["W26", { week: new Date("2026-06-22T00:00:00Z"), label: "W26 · 22 Jun" }],
1347
+ ["W27", { week: new Date("2026-06-29T00:00:00Z"), label: "W27 · 29 Jun" }],
1348
+ ["W28", { week: new Date("2026-07-06T00:00:00Z"), label: "W28 · 6 Jul" }],
1349
+ ["W29", { week: new Date("2026-07-13T00:00:00Z"), label: "W29 · 13 Jul" }],
1350
+ ["W30", { week: new Date("2026-07-20T00:00:00Z"), label: "W30 · 20 Jul" }],
1351
+ ["W31", { week: new Date("2026-07-27T00:00:00Z"), label: "W31 · 27 Jul" }],
1352
+ ["W32", { week: new Date("2026-08-03T00:00:00Z"), label: "W32 · 3 Aug" }],
1353
+ ["W33", { week: new Date("2026-08-10T00:00:00Z"), label: "W33 · 10 Aug" }],
1354
+ ["W34", { week: new Date("2026-08-17T00:00:00Z"), label: "W34 · 17 Aug" }],
1355
+ ]),
1356
+ indexes: (index) => ({
1357
+ // The weeks by their first instant: what the window narrows them through.
1358
+ plan_measure_weeks_by_week: index.on(["week"]),
1359
+ }),
1360
+ });
1403
1361
  /**
1404
1362
  * Measure rows — the chart, heat and table grammars over weekly readings, and
1405
1363
  * the temporal fold (#824) that shows them at whatever resolution the slice
@@ -1442,7 +1400,7 @@ export const planMeasures = example({
1442
1400
  "em-dash", "neg", "tableSeries", "split", "horizontal", "vertical", "multi-value", "multi-cell", "two-line",
1443
1401
  "strong", "muted", "rollup", "mirror", "position", "fold", "temporal fold", "resolution", "week", "month",
1444
1402
  "rebucket", "bucket", "max", "last", "count", "default", "override", "layer", "Plan.layer", "column",
1445
- "line", "Slice", "Slice.bindRecords", "source", "Data.bindPaged", "recordIndex", "toolbar", "range", "brush", "section", "match", "gutter",
1403
+ "line", "Slice", "Slice.bindRecords", "source", "Data.bindPaged", "indexes", "toolbar", "range", "brush", "section", "match", "gutter",
1446
1404
  "raw", "readings", "Reactive", "Data.bind", "bound", "e3.input", "e3.record", "library", "Plan.library.tab", "#824", "#1258", "#1344",
1447
1405
  ],
1448
1406
  description: "Measure rows over weekly readings bound from e3, at MONTH to start — chart rows (a line with its breach, an area, stacked columns, a scatter, every annotation, and a fixed dual-axis composition with its band and swatches), heat rows (colour depth under a hall averaging its presses on its scale with the no-data hatch, weight bars with a planned tail, status segments) and table rows (subtotals at every depth, footer emphasis with a negative and the em-dash, multi-value positions in every split × gutter combination); each row folds a month's weeks by what its values mean, or by its override (peak load by max, a running total and the paper stock by last, the dual chart's columns by mean), the toolbar's resolution segment switches MONTH and WEEK, and one Slice.bindRecords rail's weeks source, which the Weeks tab lists, holds the window the horizon brush moves",
@@ -1456,31 +1414,18 @@ export const planMeasures = example({
1456
1414
  const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1457
1415
  return w1.addWeeks(n.subtract(1n));
1458
1416
  }));
1459
- const Point = StructType({ week: DateTimeType, v: FloatType });
1460
- const BandPoint = StructType({ week: DateTimeType, lo: FloatType, hi: FloatType });
1461
- const Weekly = StructType({ at: DateTimeType, value: OptionType(FloatType) });
1462
- // A measure's readings from W23, as each row kind reads them: a
1463
- // chart's points (a week with no reading draws none), heat cells
1464
- // printing their values (a week with none the no-data hatch),
1465
- // booked fractions (the weeks after now the planned, pale tail),
1466
- // and table cells (a week with none the em-dash).
1467
- const points = $.const(East.function([ArrayType(OptionType(FloatType))], ArrayType(Point), ($, readings) => readings.filterMap((_$2, r, i) => r.match({
1468
- some: (_$3, v) => East.value(some({ week: week(i.add(23n)), v }), OptionType(Point)),
1469
- none: (_$3) => East.value(none, OptionType(Point)),
1470
- }))));
1471
- const band = $.const(East.function([ArrayType(FloatType), ArrayType(FloatType)], ArrayType(BandPoint), ($, lo, hi) => East.Array.generate(lo.size(), BandPoint, (_$2, i) => ({ week: week(i.add(23n)), lo: lo.get(i), hi: hi.get(i) }))));
1472
- const heat = $.const(East.function([ArrayType(OptionType(FloatType))], ArrayType(Plan.Types.HeatCell), ($, readings) => East.Array.generate(readings.size(), Plan.Types.HeatCell, ($2, i) => {
1473
- const value = $2.let(readings.get(i), OptionType(FloatType));
1474
- const label = $2.let(none, OptionType(StringType));
1475
- $2.match(value, { some: ($3, v) => { $3.assign(label, some(East.Float.printFixed(v, 0n))); } });
1476
- return { at: Plan.at.time(week(i.add(23n))), value, label };
1477
- })));
1478
- const booked = $.const(East.function([ArrayType(OptionType(FloatType))], ArrayType(Plan.Types.WeightCell), ($, readings) => readings.filterMap((_$2, r, i) => r.match({
1479
- some: (_$3, f) => East.value(some({ at: Plan.at.time(week(i.add(23n))), fraction: f, planned: i.add(23n).greater(27n) }), OptionType(Plan.Types.WeightCell)),
1480
- none: (_$3) => East.value(none, OptionType(Plan.Types.WeightCell)),
1481
- }))));
1482
- const weekly = $.const(East.function([ArrayType(OptionType(FloatType))], ArrayType(Weekly), ($, readings) => East.Array.generate(readings.size(), Weekly, (_$2, i) => ({ at: week(i.add(23n)), value: readings.get(i) }))));
1417
+ // Each row reads its measure's values BY NAME, each at its week
1418
+ // (#1436): a chart's points, heat cells printing their values (a
1419
+ // week with none the no-data hatch), booked fractions (the weeks
1420
+ // after now the planned, pale tail), and table cells (a week with
1421
+ // none the em-dash).
1422
+ const printed = (v) => v.match({
1423
+ some: (_$2, x) => East.value(some(East.Float.printFixed(x, 0n)), OptionType(StringType)),
1424
+ none: () => East.value(none, OptionType(StringType)),
1425
+ });
1426
+ const now = $.const(week(27n));
1483
1427
  const whole = $.const(Format.Number({ maximumFractionDigits: 0n }));
1428
+ const signed = $.const(Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }));
1484
1429
  const series = $.const([
1485
1430
  Plan.series.section(Measure, { key: "output", title: "Output", meta: "5 rows" }, [
1486
1431
  // A level: the on-time line folds a month's weeks by their
@@ -1493,14 +1438,14 @@ export const planMeasures = example({
1493
1438
  label: r => r.label, id: true,
1494
1439
  value: r => r.value, status: _r => some(variant("warning", null)),
1495
1440
  height: "spark", expandable: true, expandedHeight: "120px",
1496
- layers: r => [Plan.layer(Chart.Line(points(r.readings), { x: p => p.week, y: p => p.v }), { breach: { below: 92 } })],
1441
+ layers: [Plan.line(Plan.values("points", (v) => ({ y: v })), { breach: { below: 92 } })],
1497
1442
  }),
1498
1443
  // A running total — a month shows where it CLOSED.
1499
1444
  Plan.series.chart(Measure, {
1500
1445
  key: "cum", title: "Cumulative",
1501
1446
  match: r => r.series.equal("cum"),
1502
1447
  label: r => r.label, id: true, value: r => r.value,
1503
- layers: r => [Plan.layer(Chart.Area(points(r.readings), { x: p => p.week, y: p => p.v }), { fold: "last" })],
1448
+ layers: [Plan.area(Plan.values("points", (v) => ({ y: v })), { fold: "last" })],
1504
1449
  }),
1505
1450
  // Amounts — the two halls' columns stacked by one series id,
1506
1451
  // each month their weeks' SUM, on a two-line gutter (label
@@ -1509,9 +1454,9 @@ export const planMeasures = example({
1509
1454
  key: "stacked", title: "Stacked",
1510
1455
  match: r => r.series.equal("stacked"),
1511
1456
  label: r => r.label, id: true, stacked: true, sub: r => r.sub,
1512
- layers: r => [
1513
- Plan.layer(Chart.Column(points(r.readings), { x: p => p.week, y: p => p.v }), { series: "H1" }),
1514
- Plan.layer(Chart.Column(points(r.second), { x: p => p.week, y: p => p.v }), { series: "H2" }),
1457
+ layers: [
1458
+ Plan.column(Plan.values("points", (v) => ({ y: v })), { series: "H1" }),
1459
+ Plan.column(Plan.values("paired", (v) => ({ y: v })), { series: "H2" }),
1515
1460
  ],
1516
1461
  }),
1517
1462
  // A scatter draws every reading, folded or not.
@@ -1519,7 +1464,7 @@ export const planMeasures = example({
1519
1464
  key: "ppm", title: "Ppm",
1520
1465
  match: r => r.series.equal("ppm"),
1521
1466
  label: r => r.label, id: true, value: r => r.value,
1522
- layers: r => [Chart.Scatter(points(r.readings), { x: p => p.week, y: p => p.v })],
1467
+ layers: [Plan.scatter(Plan.values("points", (v) => ({ y: v })))],
1523
1468
  }),
1524
1469
  // A line and every annotation kind, at expanded density.
1525
1470
  Plan.series.chart(Measure, {
@@ -1527,8 +1472,8 @@ export const planMeasures = example({
1527
1472
  match: r => r.series.equal("refs"),
1528
1473
  label: r => r.label, id: true,
1529
1474
  height: "expanded",
1530
- layers: r => [
1531
- Plan.layer(Chart.Line(points(r.readings), { x: p => p.week, y: p => p.v }), { breach: { below: 92 } }),
1475
+ layers: [
1476
+ Plan.line(Plan.values("points", (v) => ({ y: v })), { breach: { below: 92 } }),
1532
1477
  Chart.refLine({ y: 100, label: "TARGET 100" }),
1533
1478
  Chart.refBand({ x: [week(29n), week(31n)], label: "CRUNCH" }),
1534
1479
  Chart.refDot({ x: week(30n), y: 88.9, label: "LOW" }),
@@ -1549,10 +1494,10 @@ export const planMeasures = example({
1549
1494
  left: { domain: [0, 60], tickValues: [0, 25, 50] },
1550
1495
  right: { domain: [80, 105], tickValues: [85, 95, 105] },
1551
1496
  swatches: [{ color: "ink.3", label: "out" }, { color: "brand.d", label: "on-time · rh" }],
1552
- layers: r => [
1553
- Plan.layer(Chart.Column(points(r.readings), { x: p => p.week, y: p => p.v }), { fold: "mean" }),
1554
- Plan.layer(Chart.Line(points(r.second), { x: p => p.week, y: p => p.v }), { axis: "right" }),
1555
- Plan.layer(Chart.Band(band(r.lo, r.hi), { x: p => p.week, low: p => p.lo, high: p => p.hi }), { axis: "right" }),
1497
+ layers: [
1498
+ Plan.column(Plan.values("points", (v) => ({ y: v })), { fold: "mean" }),
1499
+ Plan.line(Plan.values("paired", (v) => ({ y: v })), { axis: "right" }),
1500
+ Plan.band(Plan.values("band", (b) => ({ lo: b.lo, hi: b.hi })), { axis: "right" }),
1556
1501
  ],
1557
1502
  }),
1558
1503
  ]),
@@ -1564,8 +1509,8 @@ export const planMeasures = example({
1564
1509
  key: "depth", title: "Depth",
1565
1510
  match: r => r.series.equal("depth"),
1566
1511
  label: r => r.label, id: true,
1567
- cells: r => Plan.heatCells(heat(r.readings), { min: 0, max: 100, warnAt: 95 }),
1568
- children: r => r.children, aggregate: "mean",
1512
+ cells: Plan.heatCells(Plan.values("readings", (v) => ({ value: v, label: printed(v) })), { min: 0, max: 100, warnAt: 95 }),
1513
+ children: "children", aggregate: "mean",
1569
1514
  scale: { min: 0, max: 100, warnAt: 95 },
1570
1515
  }),
1571
1516
  // The same kind of reading, folded by its MAX — the peak a
@@ -1574,7 +1519,7 @@ export const planMeasures = example({
1574
1519
  key: "peak", title: "Peak load",
1575
1520
  match: r => r.series.equal("peak"),
1576
1521
  label: r => r.label,
1577
- cells: r => Plan.heatCells(heat(r.readings), { min: 0, max: 100, fold: "max", format: whole }),
1522
+ cells: Plan.heatCells(Plan.values("readings", (v) => ({ value: v, label: printed(v) })), { min: 0, max: 100, fold: "max", format: whole }),
1578
1523
  }),
1579
1524
  // A fraction of each week booked — a month's is its weeks'
1580
1525
  // mean, pale only when every one of them is planned.
@@ -1582,7 +1527,7 @@ export const planMeasures = example({
1582
1527
  key: "booked", title: "Booked",
1583
1528
  match: r => r.series.equal("booked"),
1584
1529
  label: r => r.label, sub: r => r.sub,
1585
- cells: r => Plan.weightCells(booked(r.readings)),
1530
+ cells: Plan.weightCells(Plan.values("points", (fraction, at) => ({ fraction, planned: at.greater(now) }))),
1586
1531
  }),
1587
1532
  ]),
1588
1533
  Plan.series.section(Measure, { key: "finishing", title: "Finishing", meta: "1 row" }, [
@@ -1592,7 +1537,7 @@ export const planMeasures = example({
1592
1537
  key: "segments", title: "Segments",
1593
1538
  match: r => r.series.equal("segments"),
1594
1539
  label: r => r.label, sub: r => r.sub,
1595
- cells: r => Plan.segmentCells(r.segments),
1540
+ cells: Plan.segmentCells(Plan.values("segments", (segments) => ({ segments }))),
1596
1541
  }),
1597
1542
  ]),
1598
1543
  Plan.series.section(Measure, { key: "deliveries", title: "Deliveries", meta: "21 rows" }, [
@@ -1603,15 +1548,15 @@ export const planMeasures = example({
1603
1548
  key: "orders", title: "Orders",
1604
1549
  match: r => r.series.equal("orders"),
1605
1550
  label: r => r.label,
1606
- cells: r => Plan.tableCells(weekly(r.readings)),
1607
- children: r => r.children, aggregate: "sum",
1551
+ cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))),
1552
+ children: "children", aggregate: "sum",
1608
1553
  format: whole,
1609
1554
  }),
1610
1555
  Plan.series.table(Measure, {
1611
1556
  key: "net", title: "Net",
1612
1557
  match: r => r.series.equal("net"),
1613
1558
  label: r => r.label, emphasis: "footer",
1614
- cells: r => Plan.tableCells(weekly(r.readings)),
1559
+ cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))),
1615
1560
  format: whole,
1616
1561
  }),
1617
1562
  // Per-POSITION style declared ONCE, in the CONFIG — a strong
@@ -1620,13 +1565,9 @@ export const planMeasures = example({
1620
1565
  key: "actplan", title: "Actual vs plan",
1621
1566
  match: r => r.series.equal("actplan"),
1622
1567
  label: r => r.label, stacked: true, sub: r => r.sub,
1623
- series: r => [
1624
- Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(weekly(r.readings)) }),
1625
- Plan.tableSeries({
1626
- tone: "muted",
1627
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1628
- cells: Plan.tableCells(weekly(r.second)),
1629
- }),
1568
+ series: [
1569
+ Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1570
+ Plan.tableSeries({ tone: "muted", format: signed, cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1630
1571
  ],
1631
1572
  format: whole,
1632
1573
  }),
@@ -1635,9 +1576,9 @@ export const planMeasures = example({
1635
1576
  key: "inout", title: "Inout",
1636
1577
  match: r => r.series.equal("inout"),
1637
1578
  label: r => r.label, split: "vertical",
1638
- series: r => [
1639
- Plan.tableSeries({ cells: Plan.tableCells(weekly(r.readings)) }),
1640
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(weekly(r.second)) }),
1579
+ series: [
1580
+ Plan.tableSeries({ cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1581
+ Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1641
1582
  ],
1642
1583
  format: whole,
1643
1584
  }),
@@ -1648,9 +1589,9 @@ export const planMeasures = example({
1648
1589
  key: "sidebyside", title: "Side by side",
1649
1590
  match: r => r.series.equal("sidebyside"),
1650
1591
  label: r => r.label, split: "horizontal",
1651
- series: r => [
1652
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(weekly(r.readings)) }),
1653
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(weekly(r.second)) }),
1592
+ series: [
1593
+ Plan.tableSeries({ strong: true, cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1594
+ Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1654
1595
  ],
1655
1596
  format: whole,
1656
1597
  }),
@@ -1663,15 +1604,11 @@ export const planMeasures = example({
1663
1604
  key: "lot", title: "Lot",
1664
1605
  match: r => r.series.equal("lot"),
1665
1606
  label: r => r.label,
1666
- series: r => [
1667
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(weekly(r.readings)) }),
1668
- Plan.tableSeries({
1669
- tone: "muted",
1670
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1671
- cells: Plan.tableCells(weekly(r.second)),
1672
- }),
1607
+ series: [
1608
+ Plan.tableSeries({ strong: true, cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1609
+ Plan.tableSeries({ tone: "muted", format: signed, cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1673
1610
  ],
1674
- children: r => r.children, aggregate: "sum",
1611
+ children: "children", aggregate: "sum",
1675
1612
  format: whole,
1676
1613
  }),
1677
1614
  // NESTED and VERTICAL — the subtotal stacks its positions the
@@ -1683,11 +1620,11 @@ export const planMeasures = example({
1683
1620
  key: "stack", title: "Stack",
1684
1621
  match: r => r.series.equal("stack"),
1685
1622
  label: r => r.label, split: "vertical",
1686
- series: r => [
1687
- Plan.tableSeries({ strong: true, cells: Plan.tableCells(weekly(r.readings)) }),
1688
- Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(weekly(r.second)) }),
1623
+ series: [
1624
+ Plan.tableSeries({ strong: true, cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1625
+ Plan.tableSeries({ tone: "muted", cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1689
1626
  ],
1690
- children: r => r.children, aggregate: "sum",
1627
+ children: "children", aggregate: "sum",
1691
1628
  format: whole,
1692
1629
  }),
1693
1630
  // VERTICAL on a TWO-line gutter — the remaining combination,
@@ -1696,13 +1633,9 @@ export const planMeasures = example({
1696
1633
  key: "overunder", title: "Over / under",
1697
1634
  match: r => r.series.equal("overunder"),
1698
1635
  label: r => r.label, split: "vertical", stacked: true, sub: r => r.sub,
1699
- series: r => [
1700
- Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(weekly(r.readings)) }),
1701
- Plan.tableSeries({
1702
- tone: "muted",
1703
- format: Format.Number({ maximumFractionDigits: 0n, signDisplay: "always" }),
1704
- cells: Plan.tableCells(weekly(r.second)),
1705
- }),
1636
+ series: [
1637
+ Plan.tableSeries({ strong: true, rollup: true, cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))) }),
1638
+ Plan.tableSeries({ tone: "muted", format: signed, cells: Plan.tableCells(Plan.values("second", (v) => ({ value: v }))) }),
1706
1639
  ],
1707
1640
  format: whole,
1708
1641
  }),
@@ -1711,7 +1644,7 @@ export const planMeasures = example({
1711
1644
  key: "stock", title: "Paper stock",
1712
1645
  match: r => r.series.equal("stock"),
1713
1646
  label: r => r.label,
1714
- cells: r => Plan.tableCells(weekly(r.readings)), fold: "last",
1647
+ cells: Plan.tableCells(Plan.values("readings", (v) => ({ value: v }))), fold: "last",
1715
1648
  format: whole,
1716
1649
  }),
1717
1650
  ]),
@@ -1721,7 +1654,7 @@ export const planMeasures = example({
1721
1654
  // resolution, MONTH to start; the Weeks tab lists the weeks in it.
1722
1655
  const slice = $.let(Slice.bindRecords("ex.plan.measures", {
1723
1656
  weeks: {
1724
- source: Data.bindPaged(planMeasureWeeks, { indexes: [planMeasureWeeksByWeek] }),
1657
+ source: Data.bindPaged(planMeasureWeeks, { indexes: ["plan_measure_weeks_by_week"] }),
1725
1658
  label: "Weeks", fields: { week: { label: "Week", format: { date: "MMM D" } } }, range: "week",
1726
1659
  },
1727
1660
  }));
@@ -1738,75 +1671,83 @@ export const planMeasures = example({
1738
1671
  // planRowDrop — `data`'s rows edited in place: cards dropped from the Plan's
1739
1672
  // library panel, runs moved and resized, one checked Save (#880, #825, #1259)
1740
1673
  // ============================================================================
1741
- /** A job, its label already composed. */
1674
+ /** A job, under its id — its label already composed. */
1742
1675
  export const DropJob = StructType({
1743
- key: StringType, label: StringType,
1676
+ label: StringType,
1744
1677
  start: DateTimeType, end: DateTimeType, state: EventStateType,
1745
1678
  });
1746
- /** A van drop. */
1747
- export const DropAlloc = StructType({ key: StringType, at: DateTimeType, state: EventStateType });
1748
- /** A crew shift, its label already composed. */
1679
+ /** A van drop, under its id. */
1680
+ export const DropAlloc = StructType({ at: DateTimeType, state: EventStateType });
1681
+ /** A crew shift, under its id — its label already composed. */
1749
1682
  export const DropShift = StructType({
1750
- key: StringType, from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
1683
+ from: DateTimeType, to: DateTimeType, label: StringType, state: EventStateType,
1751
1684
  });
1685
+ /** A milestone on a stream, under its id. */
1686
+ export const DropMark = StructType({ at: DateTimeType, kind: Plan.Types.EventMarkKind, label: OptionType(StringType) });
1752
1687
  /** A press a hall holds — its jobs; its name is its key. */
1753
- export const DropPress = StructType({ jobs: ArrayType(DropJob) });
1754
- /** One row of the ops record — `series` names the series that claims it;
1755
- * `readings` are fortnightly from W27; a hall holds its presses
1756
- * (`presses`), the hierarchy the data's own. */
1688
+ export const DropPress = StructType({ jobs: DictType(StringType, DropJob) });
1689
+ /** One row of the ops record — `series` names the series that claims it; its
1690
+ * collections are Dicts a series reads by name (#1436): the things people
1691
+ * drag by their ids, `readings` and `nums` a value a fortnight keyed by its
1692
+ * week; a hall holds its presses (`presses`), the hierarchy the data's own. */
1757
1693
  export const DropOpsRow = StructType({
1758
1694
  series: StringType, label: StringType,
1759
- jobs: ArrayType(DropJob),
1760
- readings: ArrayType(FloatType),
1761
- allocs: ArrayType(DropAlloc),
1762
- nums: ArrayType(Plan.Types.TableCell),
1763
- shifts: ArrayType(DropShift),
1764
- marks: ArrayType(Plan.Types.EventMark),
1695
+ jobs: DictType(StringType, DropJob),
1696
+ readings: DictType(DateTimeType, FloatType),
1697
+ allocs: DictType(StringType, DropAlloc),
1698
+ nums: DictType(DateTimeType, FloatType),
1699
+ shifts: DictType(StringType, DropShift),
1700
+ marks: DictType(StringType, DropMark),
1765
1701
  presses: DictType(StringType, DropPress),
1766
1702
  });
1703
+ /** Every collection an ops row holds, empty: a row names the few it fills. */
1704
+ const DROP_EMPTY = {
1705
+ jobs: new Map(), readings: new Map(), allocs: new Map(), nums: new Map(), shifts: new Map(), marks: new Map(), presses: new Map(),
1706
+ };
1707
+ /** A value a fortnight from W27 (Monday 29 June 2026), each keyed by its week's instant. */
1708
+ const fortnightly = (readings) => atInstants(readings.map((v, i) => [new Date(Date.UTC(2026, 5, 29 + 14 * i)), v]));
1767
1709
  /**
1768
1710
  * The ops RECORD every gesture drafts and Save commits to — its initial state
1769
1711
  * the genesis commit. The droppable and inert kinds interleave, and Hall 3 is
1770
1712
  * an entry holding its two presses, H3-P11 already running four jobs, so one
1771
1713
  * more is refused.
1772
1714
  */
1773
- export const planDropOps = e3.record("plan_drop_ops", DictType(StringType, DropOpsRow), new Map([
1774
- ["util", { series: "util", label: "Util %", jobs: [], readings: [46.0, 58.0, 66.0, 72.0, 84.0, 96.0],
1775
- allocs: [], nums: [], shifts: [], marks: [], presses: new Map() }],
1776
- ["p03", { series: "press", label: "H1-P03", readings: [], allocs: [], nums: [], shifts: [], marks: [], presses: new Map(),
1777
- jobs: [{ key: "j4642", label: "RUN · J-4642", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("in-progress", null) }] }],
1778
- ["p04", { series: "press", label: "H1-P04", readings: [], allocs: [], nums: [], shifts: [], marks: [], presses: new Map(),
1779
- jobs: [{ key: "j4624", label: "RUN · J-4624", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), state: variant("confirmed", null) }] }],
1780
- ["load", { series: "load", label: "H2 load", jobs: [], readings: [46.0, 58.0, 66.0, 72.0, 84.0, 96.0],
1781
- allocs: [], nums: [], shifts: [], marks: [], presses: new Map() }],
1782
- ["van1", { series: "van", label: "Van 1", jobs: [], readings: [], nums: [], shifts: [], marks: [], presses: new Map(),
1783
- allocs: [{ key: "a1", at: new Date("2026-07-13T00:00:00Z"), state: variant("confirmed", null) }] }],
1784
- ["desp", { series: "table", label: "Delivered · k", jobs: [], readings: [], allocs: [], shifts: [], marks: [], presses: new Map(),
1785
- nums: [
1786
- { at: variant("time", new Date("2026-07-06T00:00:00Z")), value: some(128.0), text: none, tone: none },
1787
- { at: variant("time", new Date("2026-07-27T00:00:00Z")), value: some(-96.0), text: none, tone: none },
1788
- ] }],
1789
- ["crewA", { series: "crew", label: "Crew A", jobs: [], readings: [], allocs: [], nums: [], marks: [], presses: new Map(),
1790
- shifts: [{ key: "s1", from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), label: "80h", state: variant("confirmed", null) }] }],
1791
- ["ms", { series: "strm", label: "Milestones", jobs: [], readings: [], allocs: [], nums: [], shifts: [], presses: new Map(),
1792
- marks: [{ key: "k", at: variant("time", new Date("2026-07-13T00:00:00Z")), kind: variant("milestone", null), icon: none, label: some("KICKOFF") }] }],
1793
- // A HALL, holding its presses: a gesture on one of them drafts the hall,
1794
- // the entry they ride in. H3-P11 already runs four jobs.
1795
- ["hall3", { series: "hall", label: "Hall 3", jobs: [], readings: [], allocs: [], nums: [], shifts: [], marks: [],
1796
- presses: new Map([
1797
- ["H3-P11", { jobs: [
1798
- { key: "j4897", label: "RUN · J-4897", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-13T00:00:00Z"), state: variant("confirmed", null) },
1799
- { key: "j4903", label: "RUN · J-4903", start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), state: variant("confirmed", null) },
1800
- { key: "j4911", label: "RUN · J-4911", start: new Date("2026-08-17T00:00:00Z"), end: new Date("2026-08-31T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
1801
- { key: "j4918", label: "RUN · J-4918", start: new Date("2026-08-31T00:00:00Z"), end: new Date("2026-09-14T00:00:00Z"), state: variant("proposed", variant("recommended", null)) },
1802
- ] }],
1803
- ["H3-P12", { jobs: [
1804
- { key: "j4925", label: "RUN · J-4925", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("confirmed", null) },
1805
- ] }],
1806
- ]) }],
1807
- ]));
1808
- /** The ops record's patch door — every Save commits through it. */
1809
- export const planDropOpsPatch = e3.mutation.patch(planDropOps);
1715
+ export const planDropOps = e3.record("plan_drop_ops", DictType(StringType, DropOpsRow), {
1716
+ initial: new Map([
1717
+ ["util", { ...DROP_EMPTY, series: "util", label: "Util %", readings: fortnightly([46.0, 58.0, 66.0, 72.0, 84.0, 96.0]) }],
1718
+ ["p03", { ...DROP_EMPTY, series: "press", label: "H1-P03",
1719
+ jobs: new Map([["j4642", { label: "RUN · J-4642", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("in-progress", null) }]]) }],
1720
+ ["p04", { ...DROP_EMPTY, series: "press", label: "H1-P04",
1721
+ jobs: new Map([["j4624", { label: "RUN · J-4624", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-20T00:00:00Z"), state: variant("confirmed", null) }]]) }],
1722
+ ["load", { ...DROP_EMPTY, series: "load", label: "H2 load", readings: fortnightly([46.0, 58.0, 66.0, 72.0, 84.0, 96.0]) }],
1723
+ ["van1", { ...DROP_EMPTY, series: "van", label: "Van 1",
1724
+ allocs: new Map([["a1", { at: new Date("2026-07-13T00:00:00Z"), state: variant("confirmed", null) }]]) }],
1725
+ ["desp", { ...DROP_EMPTY, series: "table", label: "Delivered · k",
1726
+ nums: atInstants([[new Date("2026-07-06T00:00:00Z"), 128.0], [new Date("2026-07-27T00:00:00Z"), -96.0]]) }],
1727
+ ["crewA", { ...DROP_EMPTY, series: "crew", label: "Crew A",
1728
+ shifts: new Map([["s1", { from: new Date("2026-06-29T00:00:00Z"), to: new Date("2026-07-13T00:00:00Z"), label: "80h", state: variant("confirmed", null) }]]) }],
1729
+ ["ms", { ...DROP_EMPTY, series: "strm", label: "Milestones",
1730
+ marks: new Map([["k", { at: new Date("2026-07-13T00:00:00Z"), kind: variant("milestone", null), label: some("KICKOFF") }]]) }],
1731
+ // A HALL, holding its presses: a gesture on one of them drafts the hall,
1732
+ // the entry they ride in. H3-P11 already runs four jobs.
1733
+ ["hall3", { ...DROP_EMPTY, series: "hall", label: "Hall 3",
1734
+ presses: new Map([
1735
+ ["H3-P11", { jobs: new Map([
1736
+ ["j4897", { label: "RUN · J-4897", start: new Date("2026-06-29T00:00:00Z"), end: new Date("2026-07-13T00:00:00Z"), state: variant("confirmed", null) }],
1737
+ ["j4903", { label: "RUN · J-4903", start: new Date("2026-07-20T00:00:00Z"), end: new Date("2026-08-17T00:00:00Z"), state: variant("confirmed", null) }],
1738
+ ["j4911", { label: "RUN · J-4911", start: new Date("2026-08-17T00:00:00Z"), end: new Date("2026-08-31T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
1739
+ ["j4918", { label: "RUN · J-4918", start: new Date("2026-08-31T00:00:00Z"), end: new Date("2026-09-14T00:00:00Z"), state: variant("proposed", variant("recommended", null)) }],
1740
+ ]) }],
1741
+ ["H3-P12", { jobs: new Map([
1742
+ ["j4925", { label: "RUN · J-4925", start: new Date("2026-07-06T00:00:00Z"), end: new Date("2026-07-27T00:00:00Z"), state: variant("confirmed", null) }],
1743
+ ]) }],
1744
+ ]) }],
1745
+ ]),
1746
+ mutations: (mutation) => ({
1747
+ // The ops record's patch door — every Save commits through it.
1748
+ patch: mutation.patch(),
1749
+ }),
1750
+ });
1810
1751
  /** A card in the palette — a thing a row of its family takes. */
1811
1752
  export const DropCard = StructType({ name: StringType, family: StringType, note: StringType });
1812
1753
  /** The palette's cards, by key — a card's key is what a drop names it by. */
@@ -1837,13 +1778,14 @@ export const planDropCards = e3.input("plan_drop_cards", DictType(StringType, Dr
1837
1778
  * so the question is answered at three levels:
1838
1779
  *
1839
1780
  * 1. **Structurally, by series.** A card lands only on a row whose series
1840
- * declares `edit` — WHERE it lands (`items`, the entry's list the row's
1841
- * elements come from) and HOW it becomes one (`create`, from the drop: the
1842
- * card, the row and the bucket's instant). Only the kinds holding discrete
1843
- * scheduled objects can — `span` (runs), `buckets` (tiles), `events`
1844
- * (marks), `cards` (chips). A `chart` / `heat` / `table` row renders
1845
- * DERIVED values, and section headers and group strips are wayfinding:
1846
- * they register no cell and never light up during a drag.
1781
+ * declares `edit: { create }` — it lands in the collection the row's
1782
+ * elements read (`Plan.items`, #1436), under an id the Plan mints, and
1783
+ * `create` says what it becomes, from the drop: the card, the row and the
1784
+ * bucket's instant. Only the kinds holding discrete scheduled objects can
1785
+ * — `span` (runs), `buckets` (tiles), `events` (marks), `cards` (chips). A
1786
+ * `chart` / `heat` / `table` row renders DERIVED values, and section
1787
+ * headers and group strips are wayfinding: they register no cell and never
1788
+ * light up during a drag.
1847
1789
  * 2. **By policy, with `canDrop`.** Of the rows that can receive, this canvas
1848
1790
  * admits only the matching FAMILY: a job goes on a press, a delivery on
1849
1791
  * a van, a shift on a crew, a milestone on a stream. The `PALLET` card
@@ -1857,13 +1799,14 @@ export const planDropCards = e3.input("plan_drop_cards", DictType(StringType, Dr
1857
1799
  * door (`Record.onApply`).
1858
1800
  *
1859
1801
  * A press's run moves along its press or onto another press and resizes by
1860
- * either end: its series names the job's key and the instant fields a move
1861
- * writes (`key`, `start`, `end`). It moves in whole weeks, or in days with
1802
+ * either end: its runs' read names the jobs and the times a move writes
1803
+ * (`Plan.items("jobs", { from: "start", to: "end" }, …)`), and the job keeps
1804
+ * its id wherever it goes. It moves in whole weeks, or in days with
1862
1805
  * Shift held; from the keyboard, Space on a focused run picks it up: ←/→ move
1863
1806
  * it a week, Shift+←/→ its end, Alt+←/→ its start, ↑/↓ carry it to another
1864
1807
  * press, and Space drops it.
1865
1808
  *
1866
- * Hall 3 is an entry of its own, holding its presses (`Plan.children`): its
1809
+ * Hall 3 is an entry of its own, holding its presses (`Plan.children("presses", …)`): its
1867
1810
  * row rolls their runs up and takes no drop, and every gesture on one of its
1868
1811
  * presses lands on the press row, one level down, yet drafts the HALL — the
1869
1812
  * source's top-level entry, which the whole subtree rides in. A job moved
@@ -1882,7 +1825,7 @@ export const planDropCards = e3.input("plan_drop_cards", DictType(StringType, Dr
1882
1825
  export const planRowDrop = example({
1883
1826
  keywords: [
1884
1827
  "Plan", "library", "panel", "Plan.library.tab", "palette", "DnD", "drag", "drop", "canDrop",
1885
- "edit", "items", "create", "add", "target", "surface", "cell", "slot", "row kind", "selective", "veto",
1828
+ "edit", "items", "Plan.items", "Plan.values", "create", "minted id", "add", "target", "surface", "cell", "slot", "row kind", "selective", "veto",
1886
1829
  "invalid", "span", "buckets", "events", "cards", "chart", "heat", "table", "section", "row id", "row text",
1887
1830
  "droppable", "inert", "bucket instant", "editing", "session", "onApply", "onPatch", "ready",
1888
1831
  "Readiness", "Editing.Types.Readiness", "draft", "drafts", "transaction", "Save", "undo", "redo",
@@ -1897,7 +1840,7 @@ export const planRowDrop = example({
1897
1840
  // The source is a RECORD: the canvas pages it, and each Save is
1898
1841
  // one commit through its patch door.
1899
1842
  const ops = $.let(Data.bindPaged(planDropOps));
1900
- const record = $.let(Record.bind(planDropOps, [planDropOpsPatch]));
1843
+ const record = $.let(Record.bind(planDropOps));
1901
1844
  // The palette the cards come from, by key.
1902
1845
  const cards = $.let(Data.bind(planDropCards));
1903
1846
  const palette = $.let(cards.read());
@@ -1906,15 +1849,6 @@ export const planRowDrop = example({
1906
1849
  const w1 = $.const(new Date("2025-12-29T00:00:00Z"), DateTimeType);
1907
1850
  return w1.addWeeks(n.subtract(1n));
1908
1851
  }));
1909
- const MeasureRow = StructType({ week: DateTimeType, pct: FloatType });
1910
- // A row's fortnightly readings from W27, as a chart's points and as
1911
- // heat cells printing their values.
1912
- const points = $.const(East.function([ArrayType(FloatType)], ArrayType(MeasureRow), ($, readings) => East.Array.generate(readings.size(), MeasureRow, (_$, i) => ({ week: week(i.multiply(2n).add(27n)), pct: readings.get(i) }))));
1913
- const cells = $.const(East.function([ArrayType(FloatType)], ArrayType(Plan.Types.HeatCell), ($, readings) => East.Array.generate(readings.size(), Plan.Types.HeatCell, (_$, i) => ({
1914
- at: Plan.at.time(week(i.multiply(2n).add(27n))),
1915
- value: some(readings.get(i)),
1916
- label: some(East.Float.printFixed(readings.get(i), 0n)),
1917
- }))));
1918
1852
  // Everything a drop creates is a PROPOSAL — it is a suggestion the
1919
1853
  // host has not committed, and the lifecycle is how the canvas says so.
1920
1854
  const ADDED = variant("proposed", variant("added", null));
@@ -1998,28 +1932,23 @@ export const planRowDrop = example({
1998
1932
  key: "util", title: "Utilisation",
1999
1933
  match: r => r.series.equal("util"),
2000
1934
  label: r => r.label, id: true, height: "spark",
2001
- layers: r => [Chart.Line(points(r.readings), { x: p => p.week, y: p => p.pct })],
1935
+ layers: [Plan.line(Plan.values("readings", (pct) => ({ y: pct })))],
2002
1936
  }),
2003
1937
  // RECEIVES and MOVES — runs are discrete scheduled
2004
- // objects. A dropped job joins the press's `jobs`, a
2005
- // fortnight long from the bucket the pointer named;
2006
- // a run moves and resizes — the job found by `key`,
2007
- // the run's own key, and a move writing `start` and
2008
- // `end`. Keys stay unique on a press: a drop or a move
2009
- // that would repeat one is refused, so a new job's key
2010
- // names its card and place.
1938
+ // objects, read by name (#1436). A dropped job joins
1939
+ // the press's `jobs` under the id the Plan mints, a
1940
+ // fortnight long from the bucket the pointer named; a
1941
+ // run moves and resizes — the job found by its id, the
1942
+ // run's own key, and a move writing the `start` and
1943
+ // `end` the read names. A job keeps its id wherever it
1944
+ // goes: a move onto a press already holding it is refused.
2011
1945
  Plan.series.span(DropOpsRow, {
2012
1946
  key: "press", title: "Press jobs",
2013
1947
  match: r => r.series.equal("press"),
2014
1948
  label: r => r.label, id: true,
2015
- runs: r => r.jobs.map((_$, j) => Plan.run({
2016
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2017
- })),
1949
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
2018
1950
  edit: {
2019
- items: "jobs",
2020
- key: "key", start: "start", end: "end",
2021
- create: (drop, r) => ({
2022
- key: East.str `drop-${drop.from.key}-${East.print(r.jobs.size())}`,
1951
+ create: (drop) => ({
2023
1952
  label: palette.get(drop.from.key).name,
2024
1953
  start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
2025
1954
  state: ADDED,
@@ -2031,7 +1960,7 @@ export const planRowDrop = example({
2031
1960
  key: "load", title: "Hall load",
2032
1961
  match: r => r.series.equal("load"),
2033
1962
  label: r => r.label,
2034
- cells: r => Plan.heatCells(cells(r.readings), { min: 0, max: 100 }),
1963
+ cells: Plan.heatCells(Plan.values("readings", (pct) => ({ value: some(pct), label: some(East.Float.printFixed(pct, 0n)) })), { min: 0, max: 100 }),
2035
1964
  }),
2036
1965
  // RECEIVES — a dropped delivery becomes a tile in
2037
1966
  // the bucket under the pointer.
@@ -2039,21 +1968,15 @@ export const planRowDrop = example({
2039
1968
  key: "van", title: "Van drops",
2040
1969
  match: r => r.series.equal("van"),
2041
1970
  label: r => r.label,
2042
- events: r => r.allocs.map((_$, a) => Plan.event({ key: a.key, at: a.at, state: a.state })),
2043
- edit: {
2044
- items: "allocs",
2045
- create: (drop, r) => ({
2046
- key: East.str `drop-${drop.from.key}-${East.print(r.allocs.size())}`,
2047
- at: drop.at.unwrap("time"), state: ADDED,
2048
- }),
2049
- },
1971
+ events: Plan.items("allocs", { at: "at" }, (alloc) => ({ state: alloc.state })),
1972
+ edit: { create: (drop) => ({ at: drop.at.unwrap("time"), state: ADDED }) },
2050
1973
  }),
2051
1974
  // INERT — the cells are computed numbers.
2052
1975
  Plan.series.table(DropOpsRow, {
2053
1976
  key: "table", title: "Delivered sheets",
2054
1977
  match: r => r.series.equal("table"),
2055
1978
  label: r => r.label,
2056
- cells: r => r.nums,
1979
+ cells: Plan.tableCells(Plan.values("nums", (n) => ({ value: some(n) }))),
2057
1980
  format: Format.Number({ maximumFractionDigits: 0n }),
2058
1981
  }),
2059
1982
  // RECEIVES — a dropped shift becomes a chip.
@@ -2061,13 +1984,9 @@ export const planRowDrop = example({
2061
1984
  key: "crew", title: "Crew shifts",
2062
1985
  match: r => r.series.equal("crew"),
2063
1986
  label: r => r.label,
2064
- chips: r => r.shifts.map((_$, s) => Plan.chip({
2065
- key: s.key, from: s.from, to: s.to, label: s.label, state: s.state,
2066
- })),
1987
+ chips: Plan.items("shifts", { from: "from", to: "to" }, (shift) => ({ label: shift.label, state: shift.state })),
2067
1988
  edit: {
2068
- items: "shifts",
2069
- create: (drop, r) => ({
2070
- key: East.str `drop-${drop.from.key}-${East.print(r.shifts.size())}`,
1989
+ create: (drop) => ({
2071
1990
  from: drop.at.unwrap("time"), to: drop.at.unwrap("time").addWeeks(2n),
2072
1991
  label: palette.get(drop.from.key).name, state: ADDED,
2073
1992
  }),
@@ -2079,20 +1998,18 @@ export const planRowDrop = example({
2079
1998
  key: "strm", title: "Milestones",
2080
1999
  match: r => r.series.equal("strm"),
2081
2000
  label: r => r.label, id: true,
2082
- marks: r => r.marks,
2001
+ marks: Plan.items("marks", { at: "at" }, (mark) => ({ kind: mark.kind, label: mark.label })),
2083
2002
  edit: {
2084
- items: "marks",
2085
- create: (drop, r) => ({
2086
- key: East.str `drop-${drop.from.key}-${East.print(r.marks.size())}`,
2087
- at: drop.at, kind: variant("milestone", null), icon: none,
2003
+ create: (drop) => ({
2004
+ at: drop.at.unwrap("time"), kind: variant("milestone", null),
2088
2005
  label: some(palette.get(drop.from.key).name),
2089
2006
  }),
2090
2007
  },
2091
2008
  }),
2092
2009
  // A SECTION's header is inert. Under it, a HALL: an
2093
- // entry holding its presses, stepped down into
2094
- // through a plain field — which is what lets a
2095
- // gesture on a press write back into its hall. The
2010
+ // entry holding its presses, stepped down into by
2011
+ // field — which is what lets a gesture on a press
2012
+ // write back into its hall. The
2096
2013
  // hall's row rolls their runs up and takes no drop; a
2097
2014
  // press of it receives, and its runs move — onto the
2098
2015
  // Hall 1 presses too.
@@ -2102,18 +2019,13 @@ export const planRowDrop = example({
2102
2019
  match: r => r.series.equal("hall"),
2103
2020
  label: r => r.label,
2104
2021
  runs: _r => [], rollup: "union",
2105
- children: Plan.children(r => r.presses, [
2022
+ children: Plan.children("presses", [
2106
2023
  Plan.series.span(DropPress, {
2107
2024
  key: "gpress", title: "Hall presses",
2108
2025
  label: (_p, k) => k, id: true,
2109
- runs: p => p.jobs.map((_$, j) => Plan.run({
2110
- key: j.key, start: j.start, end: j.end, label: j.label, state: j.state,
2111
- })),
2026
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
2112
2027
  edit: {
2113
- items: "jobs",
2114
- key: "key", start: "start", end: "end",
2115
- create: (drop, p) => ({
2116
- key: East.str `drop-${drop.from.key}-${East.print(p.jobs.size())}`,
2028
+ create: (drop) => ({
2117
2029
  label: palette.get(drop.from.key).name,
2118
2030
  start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
2119
2031
  state: ADDED,
@@ -2147,114 +2059,94 @@ export const generateNumberHorizon = East.function([IntegerType], ArrayType(Numb
2147
2059
  * number axis's Orders tab lists, the rail's `orders` source narrowing them by
2148
2060
  * hall, its integer range the canvas's window (#1344).
2149
2061
  */
2150
- export const planNumberOrders = e3.record("plan_number_orders", DictType(StringType, NumberHorizonRow), East.compile(East.function([IntegerType], DictType(StringType, NumberHorizonRow), ($, count) => {
2151
- const generate = $.const(generateNumberHorizon);
2152
- return generate(count).toDict((_$2, order) => order.key, (_$2, order) => order);
2153
- }), [])(24n));
2154
- // The orders by their day, alone and after their hall: a hall's narrowing holds the window too.
2155
- export const planNumberOrdersByDay = e3.recordIndex("plan_number_orders_by_day", planNumberOrders, { on: ["day"] });
2156
- export const planNumberOrdersByHallDay = e3.recordIndex("plan_number_orders_by_hall_day", planNumberOrders, { on: ["hall", "day"] });
2157
- /** A van drop — its day (a plain number), lane and lifecycle state. */
2158
- export const NumberAlloc = StructType({ key: StringType, day: FloatType, lane: StringType, state: EventStateType });
2159
- /** A job — its days. */
2160
- export const NumberJob = StructType({ key: StringType, label: StringType, start: FloatType, end: FloatType, state: EventStateType });
2161
- /** A crew shift — its days and hours. */
2162
- export const NumberShift = StructType({ key: StringType, from: FloatType, to: FloatType, hours: FloatType, state: EventStateType });
2163
- /** A milestone — its day. */
2164
- export const NumberMark = StructType({ key: StringType, day: FloatType, label: StringType });
2165
- /** A chart point — a day and a value. */
2166
- export const NumberPoint = StructType({ day: FloatType, sheets: FloatType });
2167
- /** A table reading — a day and its value. */
2168
- export const NumberReading = StructType({ at: FloatType, value: OptionType(FloatType) });
2169
- /** RAW rows — every instant is a plain number (a day index). */
2062
+ export const planNumberOrders = e3.record("plan_number_orders", DictType(StringType, NumberHorizonRow), {
2063
+ initial: () => East.compile(East.function([IntegerType], DictType(StringType, NumberHorizonRow), ($, count) => {
2064
+ const generate = $.const(generateNumberHorizon);
2065
+ return generate(count).toDict((_$2, order) => order.key, (_$2, order) => order);
2066
+ }), [])(24n),
2067
+ indexes: (index) => ({
2068
+ // The orders by their day, alone and after their hall: a hall's narrowing holds the window too.
2069
+ plan_number_orders_by_day: index.on(["day"]),
2070
+ plan_number_orders_by_hall_day: index.on(["hall", "day"]),
2071
+ }),
2072
+ });
2073
+ /** A van drop, under its id — its day (a plain number), lane and lifecycle state. */
2074
+ export const NumberAlloc = StructType({ day: FloatType, lane: StringType, state: EventStateType });
2075
+ /** A job, under its id — its days. */
2076
+ export const NumberJob = StructType({ label: StringType, start: FloatType, end: FloatType, state: EventStateType });
2077
+ /** A crew shift, under its id — its days and hours. */
2078
+ export const NumberShift = StructType({ from: FloatType, to: FloatType, hours: FloatType, state: EventStateType });
2079
+ /** A milestone, under its id — its day. */
2080
+ export const NumberMark = StructType({ day: FloatType, label: StringType });
2081
+ /** RAW rows — every instant is a plain number (a day index): the things people drag by their ids, their days fields
2082
+ * of them, and a value a day keyed by its day, each a Dict a series reads by name (#1436). */
2170
2083
  export const NumberOpsRow = StructType({
2171
2084
  series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
2172
- allocations: ArrayType(NumberAlloc), jobs: ArrayType(NumberJob), shifts: ArrayType(NumberShift),
2173
- marks: ArrayType(NumberMark), points: ArrayType(NumberPoint), sheets: ArrayType(NumberReading),
2174
- cells: ArrayType(Plan.Types.HeatCell),
2085
+ allocations: DictType(StringType, NumberAlloc), jobs: DictType(StringType, NumberJob), shifts: DictType(StringType, NumberShift),
2086
+ marks: DictType(StringType, NumberMark), output: DictType(FloatType, FloatType), delivered: DictType(FloatType, FloatType),
2087
+ load: DictType(FloatType, FloatType),
2175
2088
  });
2089
+ /** Every collection a row holds, empty: a row names the few it fills. */
2090
+ const NUMBER_EMPTY = {
2091
+ allocations: new Map(), jobs: new Map(), shifts: new Map(), marks: new Map(), output: new Map(), delivered: new Map(), load: new Map(),
2092
+ };
2093
+ /** A value a day from day 1, each keyed by its day. */
2094
+ const daily = (values) => new Map(values.map((v, i) => [i + 1, v]));
2176
2095
  /** The rows — the keys are the entries' identities; the series list is the layout. */
2177
2096
  export const planNumberOps = e3.input("plan_number_ops", DictType(StringType, NumberOpsRow), variant("value", new Map([
2178
- ["van1", { series: "van", label: "Van 1", value: none, sub: some("drops/day"),
2179
- jobs: [], shifts: [], marks: [], points: [], sheets: [], cells: [],
2180
- allocations: [
2181
- { key: "a1", day: 1.0, lane: "am", state: variant("confirmed", null) },
2182
- { key: "a2", day: 1.0, lane: "pm", state: variant("confirmed", null) },
2183
- { key: "a3", day: 2.0, lane: "am", state: variant("confirmed", null) },
2184
- { key: "a4", day: 3.0, lane: "pm", state: variant("proposed", variant("recommended", null)) },
2185
- { key: "a5", day: 5.0, lane: "am", state: variant("proposed", variant("recommended", null)) },
2186
- { key: "a6", day: 6.0, lane: "pm", state: variant("confirmed", null) },
2187
- { key: "a7", day: 8.0, lane: "am", state: variant("proposed", variant("recommended", null)) },
2188
- ] }],
2189
- ["van2", { series: "van", label: "Van 2", value: none, sub: some("drops/day"),
2190
- jobs: [], shifts: [], marks: [], points: [], sheets: [], cells: [],
2191
- allocations: [
2192
- { key: "b1", day: 2.0, lane: "pm", state: variant("confirmed", null) },
2193
- { key: "b2", day: 4.0, lane: "am", state: variant("confirmed", null) },
2194
- { key: "b3", day: 4.0, lane: "pm", state: variant("proposed", variant("recommended", null)) },
2195
- { key: "b4", day: 7.0, lane: "am", state: variant("proposed", variant("recommended", null)) },
2196
- ] }],
2197
- ["p03", { series: "press", label: "H1-P03", value: some("12k/h"), sub: none,
2198
- allocations: [], shifts: [], marks: [], points: [], sheets: [], cells: [],
2199
- jobs: [
2200
- { key: "mr", label: "MAKEREADY", start: 1.0, end: 2.0, state: variant("actual", null) },
2201
- { key: "j4642", label: "RUN · J-4642", start: 2.0, end: 5.0, state: variant("in-progress", null) },
2202
- { key: "j4663", label: "RUN · J-4663", start: 6.0, end: 8.0, state: variant("proposed", variant("recommended", null)) },
2203
- ] }],
2204
- ["p04", { series: "press", label: "H1-P04", value: some("8k/h"), sub: none,
2205
- allocations: [], shifts: [], marks: [], points: [], sheets: [], cells: [],
2206
- jobs: [
2207
- { key: "j4624", label: "RUN · J-4624", start: 1.0, end: 4.0, state: variant("actual", null) },
2208
- { key: "proof", label: "PROOF", start: 4.0, end: 5.0, state: variant("confirmed", null) },
2209
- { key: "j4693", label: "RUN · J-4693", start: 5.0, end: 9.0, state: variant("proposed", variant("recommended", null)) },
2210
- ] }],
2211
- // Heat cells as STORED records — the instant spelled explicitly.
2212
- ["load", { series: "load", label: "Hall load", value: none, sub: none,
2213
- allocations: [], jobs: [], shifts: [], marks: [], points: [], sheets: [],
2214
- cells: [
2215
- { at: variant("number", 1.0), value: some(40.0), label: some("40") },
2216
- { at: variant("number", 2.0), value: some(69.0), label: some("69") },
2217
- { at: variant("number", 3.0), value: some(43.0), label: some("43") },
2218
- { at: variant("number", 4.0), value: some(72.0), label: some("72") },
2219
- { at: variant("number", 5.0), value: some(46.0), label: some("46") },
2220
- { at: variant("number", 6.0), value: some(75.0), label: some("75") },
2221
- { at: variant("number", 7.0), value: some(49.0), label: some("49") },
2222
- { at: variant("number", 8.0), value: some(78.0), label: some("78") },
2223
- ] }],
2224
- ["out", { series: "out", label: "Printed · k", value: some("612 k sheets"), sub: none,
2225
- allocations: [], jobs: [], shifts: [], marks: [], sheets: [], cells: [],
2226
- points: [
2227
- { day: 1.0, sheets: 60.0 }, { day: 2.0, sheets: 77.0 }, { day: 3.0, sheets: 94.0 }, { day: 4.0, sheets: 71.0 },
2228
- { day: 5.0, sheets: 88.0 }, { day: 6.0, sheets: 65.0 }, { day: 7.0, sheets: 82.0 }, { day: 8.0, sheets: 99.0 },
2229
- ] }],
2230
- ["desp", { series: "table", label: "Delivered · k", value: none, sub: none,
2231
- allocations: [], jobs: [], shifts: [], marks: [], points: [], cells: [],
2232
- sheets: [
2233
- { at: 1.0, value: some(80.0) }, { at: 2.0, value: some(103.0) }, { at: 3.0, value: some(126.0) }, { at: 4.0, value: some(149.0) },
2234
- { at: 5.0, value: some(102.0) }, { at: 6.0, value: some(125.0) }, { at: 7.0, value: some(148.0) }, { at: 8.0, value: some(101.0) },
2235
- ] }],
2236
- ["crewA", { series: "crew", label: "Crew A", value: none, sub: none,
2237
- allocations: [], jobs: [], marks: [], points: [], sheets: [], cells: [],
2238
- shifts: [
2239
- { key: "s1", from: 1.0, to: 3.0, hours: 24.0, state: variant("confirmed", null) },
2240
- { key: "s2", from: 3.0, to: 6.0, hours: 36.0, state: variant("confirmed", null) },
2241
- { key: "s3", from: 6.0, to: 8.0, hours: 24.0, state: variant("proposed", variant("recommended", null)) },
2242
- ] }],
2243
- ["ms", { series: "ms", label: "Milestones", value: none, sub: none,
2244
- allocations: [], jobs: [], shifts: [], points: [], sheets: [], cells: [],
2245
- marks: [
2246
- { key: "kick", day: 2.0, label: "KICKOFF" },
2247
- { key: "rel", day: 6.0, label: "REL" },
2248
- ] }],
2097
+ ["van1", { ...NUMBER_EMPTY, series: "van", label: "Van 1", value: none, sub: some("drops/day"),
2098
+ allocations: new Map([
2099
+ ["a1", { day: 1.0, lane: "am", state: variant("confirmed", null) }],
2100
+ ["a2", { day: 1.0, lane: "pm", state: variant("confirmed", null) }],
2101
+ ["a3", { day: 2.0, lane: "am", state: variant("confirmed", null) }],
2102
+ ["a4", { day: 3.0, lane: "pm", state: variant("proposed", variant("recommended", null)) }],
2103
+ ["a5", { day: 5.0, lane: "am", state: variant("proposed", variant("recommended", null)) }],
2104
+ ["a6", { day: 6.0, lane: "pm", state: variant("confirmed", null) }],
2105
+ ["a7", { day: 8.0, lane: "am", state: variant("proposed", variant("recommended", null)) }],
2106
+ ]) }],
2107
+ ["van2", { ...NUMBER_EMPTY, series: "van", label: "Van 2", value: none, sub: some("drops/day"),
2108
+ allocations: new Map([
2109
+ ["b1", { day: 2.0, lane: "pm", state: variant("confirmed", null) }],
2110
+ ["b2", { day: 4.0, lane: "am", state: variant("confirmed", null) }],
2111
+ ["b3", { day: 4.0, lane: "pm", state: variant("proposed", variant("recommended", null)) }],
2112
+ ["b4", { day: 7.0, lane: "am", state: variant("proposed", variant("recommended", null)) }],
2113
+ ]) }],
2114
+ ["p03", { ...NUMBER_EMPTY, series: "press", label: "H1-P03", value: some("12k/h"), sub: none,
2115
+ jobs: new Map([
2116
+ ["mr", { label: "MAKEREADY", start: 1.0, end: 2.0, state: variant("actual", null) }],
2117
+ ["j4642", { label: "RUN · J-4642", start: 2.0, end: 5.0, state: variant("in-progress", null) }],
2118
+ ["j4663", { label: "RUN · J-4663", start: 6.0, end: 8.0, state: variant("proposed", variant("recommended", null)) }],
2119
+ ]) }],
2120
+ ["p04", { ...NUMBER_EMPTY, series: "press", label: "H1-P04", value: some("8k/h"), sub: none,
2121
+ jobs: new Map([
2122
+ ["j4624", { label: "RUN · J-4624", start: 1.0, end: 4.0, state: variant("actual", null) }],
2123
+ ["proof", { label: "PROOF", start: 4.0, end: 5.0, state: variant("confirmed", null) }],
2124
+ ["j4693", { label: "RUN · J-4693", start: 5.0, end: 9.0, state: variant("proposed", variant("recommended", null)) }],
2125
+ ]) }],
2126
+ ["load", { ...NUMBER_EMPTY, series: "load", label: "Hall load", value: none, sub: none, load: daily([40.0, 69.0, 43.0, 72.0, 46.0, 75.0, 49.0, 78.0]) }],
2127
+ ["out", { ...NUMBER_EMPTY, series: "out", label: "Printed · k", value: some("612 k sheets"), sub: none,
2128
+ output: daily([60.0, 77.0, 94.0, 71.0, 88.0, 65.0, 82.0, 99.0]) }],
2129
+ ["desp", { ...NUMBER_EMPTY, series: "table", label: "Delivered · k", value: none, sub: none,
2130
+ delivered: daily([80.0, 103.0, 126.0, 149.0, 102.0, 125.0, 148.0, 101.0]) }],
2131
+ ["crewA", { ...NUMBER_EMPTY, series: "crew", label: "Crew A", value: none, sub: none,
2132
+ shifts: new Map([
2133
+ ["s1", { from: 1.0, to: 3.0, hours: 24.0, state: variant("confirmed", null) }],
2134
+ ["s2", { from: 3.0, to: 6.0, hours: 36.0, state: variant("confirmed", null) }],
2135
+ ["s3", { from: 6.0, to: 8.0, hours: 24.0, state: variant("proposed", variant("recommended", null)) }],
2136
+ ]) }],
2137
+ ["ms", { ...NUMBER_EMPTY, series: "ms", label: "Milestones", value: none, sub: none,
2138
+ marks: new Map([
2139
+ ["kick", { day: 2.0, label: "KICKOFF" }],
2140
+ ["rel", { day: 6.0, label: "REL" }],
2141
+ ]) }],
2249
2142
  ])));
2250
2143
  /**
2251
2144
  * The `number` axis — the retired Planner's `plannerPoint` canvas on the
2252
2145
  * Plan: eight days at step 1, AM/PM lanes in the bucket rows, `now` at day
2253
2146
  * 5. Every kind positions on the one numeric scale, and every instant in the
2254
- * raw data is a plain number: a `FloatType` field wraps to the `number` arm
2255
- * through the element builders (`Plan.run({ start: j.start })`), a chart
2256
- * layer's numeric x accessor lands its columns on the same arm, `Plan.tableCells`
2257
- * reads a numeric `at`, and the heat cells stored as RECORDS spell it out with
2147
+ * raw data is a plain number, read by name (#1436): a Float time field of the
2148
+ * things people drag (`Plan.items("jobs", { from: "start", to: "end" }, …)`),
2149
+ * and the Float key of a value a day (`Plan.values("load", …)`), each lands on
2258
2150
  * the `number` arm. One rail (`Slice.bindRecords`, #1344): its `orders`
2259
2151
  * source — twelve days of orders behind the eight-day window, a record of
2260
2152
  * their own — narrows the library's Orders tab by hall, and its range field is
@@ -2265,10 +2157,10 @@ export const planNumberOps = e3.input("plan_number_ops", DictType(StringType, Nu
2265
2157
  */
2266
2158
  export const planNumberAxis = example({
2267
2159
  keywords: [
2268
- "Plan", "axis", "number", "numeric", "step", "Plan.axis.number", "Plan.at", "instant",
2160
+ "Plan", "axis", "number", "numeric", "step", "Plan.axis.number", "Plan.items", "Plan.values", "instant",
2269
2161
  "day", "AM", "PM", "lanes", "buckets", "Planner", "plannerPoint", "brush", "integer", "range", "half-open",
2270
2162
  "format", "now", "typed axis", "#631", "raw", "Data.bind", "bound", "e3.input", "e3.record",
2271
- "Slice.bindRecords", "source", "Data.bindPaged", "recordIndex", "library", "Plan.library.tab", "#1344",
2163
+ "Slice.bindRecords", "source", "Data.bindPaged", "indexes", "library", "Plan.library.tab", "#1344",
2272
2164
  ],
2273
2165
  description: "The number axis over rows bound from e3 — day 1..8 at step 1 with AM/PM lanes (the retired Planner's plannerPoint), every row kind on the numeric scale; one Slice.bindRecords rail whose orders source narrows the Orders tab by hall and holds the window, the horizon brush over its integer range",
2274
2166
  fn: East.function([], UIComponentType, (_$) => {
@@ -2285,51 +2177,49 @@ export const planNumberAxis = example({
2285
2177
  label: r => r.label, sub: r => r.sub,
2286
2178
  // The Planner's AM/PM lanes — sub-slots of each day column.
2287
2179
  lanes: _r => [Plan.lane({ key: "am", label: "AM" }), Plan.lane({ key: "pm", label: "PM" })],
2288
- // `a.day` is a FloatType field — the builder wraps it to the number arm.
2289
- events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.day, lane: a.lane, state: a.state })),
2180
+ // `day` is a Float field of each drop — the read places it on the number arm.
2181
+ events: Plan.items("allocations", { at: "day" }, (alloc) => ({ lane: alloc.lane, state: alloc.state })),
2290
2182
  }),
2291
2183
  ]),
2292
2184
  Plan.series.span(NumberOpsRow, {
2293
2185
  key: "press", title: "Presses",
2294
2186
  match: r => r.series.equal("press"),
2295
2187
  label: r => r.label, id: true, value: r => r.value,
2296
- runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
2188
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
2297
2189
  }),
2298
2190
  Plan.series.heat(NumberOpsRow, {
2299
2191
  key: "load", title: "Hall load",
2300
2192
  match: r => r.series.equal("load"),
2301
2193
  label: r => r.label,
2302
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
2194
+ // A value a day, keyed by its Float day — each cell on the number arm.
2195
+ cells: Plan.heatCells(Plan.values("load", (pct) => ({ value: pct, label: some(East.Float.printFixed(pct, 0n)) })), { min: 0, max: 100, warnAt: 90 }),
2303
2196
  }),
2304
2197
  Plan.series.chart(NumberOpsRow, {
2305
2198
  key: "out", title: "Output",
2306
2199
  match: r => r.series.equal("out"),
2307
2200
  label: r => r.label, id: true, value: r => r.value, height: "expanded",
2308
- // A numeric x accessor lands the columns on the number arm.
2309
- layers: r => [Chart.Column(r.points, { x: p => p.day, y: p => p.sheets })],
2201
+ layers: [Plan.column(Plan.values("output", (sheets) => ({ y: sheets })))],
2310
2202
  }),
2311
2203
  Plan.series.table(NumberOpsRow, {
2312
2204
  key: "table", title: "Deliveries",
2313
2205
  match: r => r.series.equal("table"),
2314
2206
  label: r => r.label,
2315
- // A `{ at: FloatType, value }` record wraps by its field type.
2316
- cells: r => Plan.tableCells(r.sheets),
2207
+ cells: Plan.tableCells(Plan.values("delivered", (sheets) => ({ value: sheets }))),
2317
2208
  format: Format.Number({ maximumFractionDigits: 0n }),
2318
2209
  }),
2319
2210
  Plan.series.cards(NumberOpsRow, {
2320
2211
  key: "crew", title: "Crews",
2321
2212
  match: r => r.series.equal("crew"),
2322
2213
  label: r => r.label,
2323
- chips: r => r.shifts.map((_$, s) => Plan.chip({
2324
- key: s.key, from: s.from, to: s.to,
2325
- label: East.str `${East.Float.printFixed(s.hours, 0n)}h`, state: s.state,
2214
+ chips: Plan.items("shifts", { from: "from", to: "to" }, (shift) => ({
2215
+ label: East.str `${East.Float.printFixed(shift.hours, 0n)}h`, state: shift.state,
2326
2216
  })),
2327
2217
  }),
2328
2218
  Plan.series.events(NumberOpsRow, {
2329
2219
  key: "ms", title: "Milestones",
2330
2220
  match: r => r.series.equal("ms"),
2331
2221
  label: r => r.label, id: true,
2332
- marks: r => r.marks.map((_$, m) => Plan.mark({ key: m.key, at: m.day, kind: "milestone", label: m.label })),
2222
+ marks: Plan.items("marks", { at: "day" }, (mark) => ({ kind: "milestone", label: mark.label })),
2333
2223
  }),
2334
2224
  ], ArrayType(Plan.Types.Series(NumberOpsRow)));
2335
2225
  // The orders' range field is an INTEGER day, so the brush, the
@@ -2337,7 +2227,7 @@ export const planNumberAxis = example({
2337
2227
  // half-open range — the eight days `[1, 9)`.
2338
2228
  const slice = $.let(Slice.bindRecords("ex.plan.number", {
2339
2229
  orders: {
2340
- source: Data.bindPaged(planNumberOrders, { indexes: [planNumberOrdersByDay, planNumberOrdersByHallDay] }),
2230
+ source: Data.bindPaged(planNumberOrders, { indexes: ["plan_number_orders_by_day", "plan_number_orders_by_hall_day"] }),
2341
2231
  label: "Orders", icon: { prefix: "fas", name: "box" },
2342
2232
  fields: { hall: { label: "Hall", hints: ["Hall 1", "Hall 2"] }, day: { label: "Day" } },
2343
2233
  range: "day", search: [],
@@ -2363,100 +2253,78 @@ export const planNumberAxis = example({
2363
2253
  // ============================================================================
2364
2254
  // planOrdinalAxis — the `ordinal` axis (#631): workflow phases
2365
2255
  // ============================================================================
2366
- /** A job — the phases it spans, by name. */
2367
- export const OrdinalJob = StructType({ key: StringType, label: StringType, start: StringType, end: StringType, state: EventStateType });
2368
- /** A bindery slot — the phase it sits in. */
2369
- export const OrdinalAlloc = StructType({ key: StringType, phase: StringType, state: EventStateType });
2370
- /** A crew shift — the phases it covers. */
2371
- export const OrdinalShift = StructType({ key: StringType, from: StringType, to: StringType, label: StringType, state: EventStateType });
2372
- /** A gate — its phase, and whether it holds the work. */
2373
- export const OrdinalMark = StructType({ key: StringType, phase: StringType, label: StringType, exception: BooleanType });
2374
- /** A chart point — a phase and a count. */
2375
- export const OrdinalPoint = StructType({ phase: StringType, n: FloatType });
2376
- /** A table reading — a phase and its value. */
2377
- export const OrdinalReading = StructType({ at: StringType, value: OptionType(FloatType) });
2378
- /** RAW rows — every instant is a phase NAME. */
2256
+ /** A job, under its id — the phases it spans, by name. */
2257
+ export const OrdinalJob = StructType({ label: StringType, start: StringType, end: StringType, state: EventStateType });
2258
+ /** A bindery slot, under its id — the phase it sits in. */
2259
+ export const OrdinalAlloc = StructType({ phase: StringType, state: EventStateType });
2260
+ /** A crew shift, under its id — the phases it covers. */
2261
+ export const OrdinalShift = StructType({ from: StringType, to: StringType, label: StringType, state: EventStateType });
2262
+ /** A gate, under its id — its phase, and whether it holds the work. */
2263
+ export const OrdinalMark = StructType({ phase: StringType, label: StringType, exception: BooleanType });
2264
+ /** RAW rows — every instant is a phase NAME: the things people drag by their ids, their phases fields of them, and a
2265
+ * value a phase keyed by the phase, each a Dict a series reads by name (#1436). */
2379
2266
  export const OrdinalOrderRow = StructType({
2380
2267
  series: StringType, label: StringType, value: OptionType(StringType), sub: OptionType(StringType),
2381
- jobs: ArrayType(OrdinalJob), allocations: ArrayType(OrdinalAlloc), shifts: ArrayType(OrdinalShift),
2382
- marks: ArrayType(OrdinalMark), points: ArrayType(OrdinalPoint), counts: ArrayType(OrdinalReading),
2383
- cells: ArrayType(Plan.Types.HeatCell),
2268
+ jobs: DictType(StringType, OrdinalJob), allocations: DictType(StringType, OrdinalAlloc), shifts: DictType(StringType, OrdinalShift),
2269
+ marks: DictType(StringType, OrdinalMark), wip: DictType(StringType, FloatType), counts: DictType(StringType, OptionType(FloatType)),
2270
+ load: DictType(StringType, FloatType),
2384
2271
  });
2272
+ /** Every collection a row holds, empty: a row names the few it fills. */
2273
+ const ORDINAL_EMPTY = {
2274
+ jobs: new Map(), allocations: new Map(), shifts: new Map(), marks: new Map(), wip: new Map(), counts: new Map(), load: new Map(),
2275
+ };
2385
2276
  /** The jobs, bindery slots, loads and gates of a six-phase workflow. */
2386
2277
  export const planOrdinalOrders = e3.input("plan_ordinal_orders", DictType(StringType, OrdinalOrderRow), variant("value", new Map([
2387
- ["j6188", { series: "job", label: "J-6188", value: some("96 k sheets"), sub: none,
2388
- allocations: [], shifts: [], marks: [], points: [], counts: [], cells: [],
2389
- jobs: [
2390
- { key: "plates", label: "PLATES", start: "PREPRESS", end: "PLATES", state: variant("actual", null) },
2391
- { key: "print", label: "PRINT · J-4642", start: "PRINT", end: "FINISH", state: variant("in-progress", null) },
2392
- { key: "deliver", label: "BIND + DELIVER", start: "BIND", end: "DELIVER", state: variant("proposed", variant("recommended", null)) },
2393
- ] }],
2394
- ["j6204", { series: "job", label: "J-6204", value: some("54 k sheets"), sub: none,
2395
- allocations: [], shifts: [], marks: [], points: [], counts: [], cells: [],
2396
- jobs: [
2397
- { key: "prepress", label: "PREPRESS", start: "PREPRESS", end: "PREPRESS", state: variant("actual", null) },
2398
- { key: "print", label: "PRINT · J-4663", start: "PLATES", end: "BIND", state: variant("proposed", variant("recommended", null)) },
2399
- ] }],
2400
- ["bindery", { series: "bindery", label: "Bindery 2", value: none, sub: some("slots"),
2401
- jobs: [], shifts: [], marks: [], points: [], counts: [], cells: [],
2402
- allocations: [
2403
- { key: "a1", phase: "PLATES", state: variant("confirmed", null) }, { key: "a2", phase: "PRINT", state: variant("confirmed", null) },
2404
- { key: "a3", phase: "PRINT", state: variant("proposed", variant("recommended", null)) }, { key: "a4", phase: "BIND", state: variant("proposed", variant("recommended", null)) },
2405
- ] }],
2406
- // Heat cells as STORED records — the phase spelled on the `ordinal` arm.
2407
- ["load", { series: "load", label: "Phase load", value: none, sub: none,
2408
- jobs: [], allocations: [], shifts: [], marks: [], points: [], counts: [],
2409
- cells: [
2410
- { at: variant("ordinal", "PREPRESS"), value: some(35.0), label: some("35") },
2411
- { at: variant("ordinal", "PLATES"), value: some(66.0), label: some("66") },
2412
- { at: variant("ordinal", "PRINT"), value: some(37.0), label: some("37") },
2413
- { at: variant("ordinal", "FINISH"), value: some(68.0), label: some("68") },
2414
- { at: variant("ordinal", "BIND"), value: some(39.0), label: some("39") },
2415
- { at: variant("ordinal", "DELIVER"), value: some(70.0), label: some("70") },
2416
- ] }],
2417
- ["wip", { series: "wip", label: "WIP · jobs", value: some("31"), sub: none,
2418
- jobs: [], allocations: [], shifts: [], marks: [], counts: [], cells: [],
2419
- points: [
2420
- { phase: "PREPRESS", n: 4.0 }, { phase: "PLATES", n: 11.0 }, { phase: "PRINT", n: 18.0 },
2421
- { phase: "FINISH", n: 5.0 }, { phase: "BIND", n: 12.0 }, { phase: "DELIVER", n: 19.0 },
2422
- ] }],
2423
- ["count", { series: "count", label: "Jobs in phase", value: none, sub: none,
2424
- jobs: [], allocations: [], shifts: [], marks: [], points: [], cells: [],
2425
- counts: [
2426
- { at: "PREPRESS", value: some(12.0) }, { at: "PLATES", value: some(23.0) }, { at: "PRINT", value: some(34.0) },
2427
- { at: "FINISH", value: some(15.0) }, { at: "BIND", value: none }, { at: "DELIVER", value: some(37.0) },
2428
- ] }],
2429
- ["crew", { series: "crew", label: "Crew B", value: none, sub: none,
2430
- jobs: [], allocations: [], marks: [], points: [], counts: [], cells: [],
2431
- shifts: [
2432
- { key: "s1", from: "PREPRESS", to: "PLATES", label: "prepress crew", state: variant("confirmed", null) },
2433
- { key: "s2", from: "PRINT", to: "DELIVER", label: "+ bindery crew", state: variant("proposed", variant("recommended", null)) },
2434
- ] }],
2435
- ["gates", { series: "gates", label: "Gates", value: none, sub: none,
2436
- jobs: [], allocations: [], shifts: [], points: [], counts: [], cells: [],
2437
- marks: [
2438
- { key: "g1", phase: "FINISH", label: "HOLD", exception: true },
2439
- { key: "g2", phase: "DELIVER", label: "RELEASE", exception: false },
2440
- ] }],
2278
+ ["j6188", { ...ORDINAL_EMPTY, series: "job", label: "J-6188", value: some("96 k sheets"), sub: none,
2279
+ jobs: new Map([
2280
+ ["plates", { label: "PLATES", start: "PREPRESS", end: "PLATES", state: variant("actual", null) }],
2281
+ ["print", { label: "PRINT · J-4642", start: "PRINT", end: "FINISH", state: variant("in-progress", null) }],
2282
+ ["deliver", { label: "BIND + DELIVER", start: "BIND", end: "DELIVER", state: variant("proposed", variant("recommended", null)) }],
2283
+ ]) }],
2284
+ ["j6204", { ...ORDINAL_EMPTY, series: "job", label: "J-6204", value: some("54 k sheets"), sub: none,
2285
+ jobs: new Map([
2286
+ ["prepress", { label: "PREPRESS", start: "PREPRESS", end: "PREPRESS", state: variant("actual", null) }],
2287
+ ["print", { label: "PRINT · J-4663", start: "PLATES", end: "BIND", state: variant("proposed", variant("recommended", null)) }],
2288
+ ]) }],
2289
+ ["bindery", { ...ORDINAL_EMPTY, series: "bindery", label: "Bindery 2", value: none, sub: some("slots"),
2290
+ allocations: new Map([
2291
+ ["a1", { phase: "PLATES", state: variant("confirmed", null) }], ["a2", { phase: "PRINT", state: variant("confirmed", null) }],
2292
+ ["a3", { phase: "PRINT", state: variant("proposed", variant("recommended", null)) }], ["a4", { phase: "BIND", state: variant("proposed", variant("recommended", null)) }],
2293
+ ]) }],
2294
+ ["load", { ...ORDINAL_EMPTY, series: "load", label: "Phase load", value: none, sub: none,
2295
+ load: new Map([["PREPRESS", 35.0], ["PLATES", 66.0], ["PRINT", 37.0], ["FINISH", 68.0], ["BIND", 39.0], ["DELIVER", 70.0]]) }],
2296
+ ["wip", { ...ORDINAL_EMPTY, series: "wip", label: "WIP · jobs", value: some("31"), sub: none,
2297
+ wip: new Map([["PREPRESS", 4.0], ["PLATES", 11.0], ["PRINT", 18.0], ["FINISH", 5.0], ["BIND", 12.0], ["DELIVER", 19.0]]) }],
2298
+ ["count", { ...ORDINAL_EMPTY, series: "count", label: "Jobs in phase", value: none, sub: none,
2299
+ counts: new Map([["PREPRESS", some(12.0)], ["PLATES", some(23.0)], ["PRINT", some(34.0)], ["FINISH", some(15.0)], ["BIND", none], ["DELIVER", some(37.0)]]) }],
2300
+ ["crew", { ...ORDINAL_EMPTY, series: "crew", label: "Crew B", value: none, sub: none,
2301
+ shifts: new Map([
2302
+ ["s1", { from: "PREPRESS", to: "PLATES", label: "prepress crew", state: variant("confirmed", null) }],
2303
+ ["s2", { from: "PRINT", to: "DELIVER", label: "+ bindery crew", state: variant("proposed", variant("recommended", null)) }],
2304
+ ]) }],
2305
+ ["gates", { ...ORDINAL_EMPTY, series: "gates", label: "Gates", value: none, sub: none,
2306
+ marks: new Map([
2307
+ ["g1", { phase: "FINISH", label: "HOLD", exception: true }],
2308
+ ["g2", { phase: "DELIVER", label: "RELEASE", exception: false }],
2309
+ ]) }],
2441
2310
  ])));
2442
2311
  /**
2443
2312
  * The `ordinal` axis — a workflow of six phases, each a bucket, in declared
2444
- * order. Instants are the phase VALUES: a run's `start` / `end` are phase
2445
- * names (an interval covers `[start, end]` in phase order — on an ordinal
2446
- * axis the end names the LAST bucket covered, since values are buckets, not
2447
- * edges), a tile sits in a phase, a chart's string x accessor lands its
2448
- * columns on the phase arm, `Plan.tableCells` reads a string `at`, and a
2449
- * record written as data spells its phase on the `ordinal` arm. There is no
2313
+ * order. Instants are the phase VALUES, read by name (#1436): a run's `start`
2314
+ * / `end` are phase names (an interval covers `[start, end]` in phase order —
2315
+ * on an ordinal axis the end names the LAST bucket covered, since values are
2316
+ * buckets, not edges), a tile and a gate sit in a phase, and a value a phase is
2317
+ * keyed by the phase's name — a load cell, a column, a count. There is no
2450
2318
  * slice range for an ordinal axis — the list IS the window, so the horizon
2451
2319
  * brush does not mount and the window keys idle; `now` names a phase.
2452
2320
  */
2453
2321
  export const planOrdinalAxis = example({
2454
2322
  keywords: [
2455
2323
  "Plan", "axis", "ordinal", "phase", "phases", "workflow", "stage", "Plan.axis.ordinal",
2456
- "Plan.at", "instant", "values", "list", "span", "buckets", "heat", "chart", "table",
2324
+ "Plan.items", "Plan.values", "instant", "values", "list", "span", "buckets", "heat", "chart", "table",
2457
2325
  "cards", "events", "Planner", "typed axis", "#631", "raw", "Data.bind", "bound", "e3.input",
2458
2326
  ],
2459
- description: "The ordinal axis over rows bound from e3 — six workflow phases as the buckets, in declared order; jobs span phases, tiles and cells sit in them, a string x accessor lands a chart on them",
2327
+ description: "The ordinal axis over rows bound from e3 — six workflow phases as the buckets, in declared order; jobs span phases, tiles and gates sit in them, and a value a phase, keyed by the phase, lands a heat cell, a column and a count in each",
2460
2328
  fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
2461
2329
  const orders = $.let(Data.bind(planOrdinalOrders));
2462
2330
  const PHASES = $.const(["PREPRESS", "PLATES", "PRINT", "FINISH", "BIND", "DELIVER"], ArrayType(StringType));
@@ -2468,49 +2336,48 @@ export const planOrdinalAxis = example({
2468
2336
  key: "job", title: "Jobs",
2469
2337
  match: r => r.series.equal("job"),
2470
2338
  label: r => r.label, id: true, value: r => r.value,
2471
- // `j.start` / `j.end` are StringType fields — the builder wraps them to the ordinal arm.
2472
- runs: r => r.jobs.map((_$, j) => Plan.run({ key: j.key, start: j.start, end: j.end, label: j.label, state: j.state })),
2339
+ // `start` / `end` are String fields of each job — the read places them on the ordinal arm.
2340
+ runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
2473
2341
  }),
2474
2342
  ]),
2475
2343
  Plan.series.buckets(OrdinalOrderRow, {
2476
2344
  key: "bindery", title: "Bindery",
2477
2345
  match: r => r.series.equal("bindery"),
2478
2346
  label: r => r.label, sub: r => r.sub,
2479
- events: r => r.allocations.map((_$, a) => Plan.event({ key: a.key, at: a.phase, state: a.state })),
2347
+ events: Plan.items("allocations", { at: "phase" }, (alloc) => ({ state: alloc.state })),
2480
2348
  }),
2481
2349
  Plan.series.heat(OrdinalOrderRow, {
2482
2350
  key: "load", title: "Phase load",
2483
2351
  match: r => r.series.equal("load"),
2484
2352
  label: r => r.label,
2485
- cells: r => Plan.heatCells(r.cells, { min: 0, max: 100, warnAt: 90 }),
2353
+ // A value a phase, keyed by the phase's name — each cell in its phase.
2354
+ cells: Plan.heatCells(Plan.values("load", (pct) => ({ value: pct, label: some(East.Float.printFixed(pct, 0n)) })), { min: 0, max: 100, warnAt: 90 }),
2486
2355
  }),
2487
2356
  Plan.series.chart(OrdinalOrderRow, {
2488
2357
  key: "wip", title: "WIP",
2489
2358
  match: r => r.series.equal("wip"),
2490
2359
  label: r => r.label, id: true, value: r => r.value, height: "expanded",
2491
- // A string x accessor lands the columns on the ordinal arm.
2492
- layers: r => [Chart.Column(r.points, { x: p => p.phase, y: p => p.n })],
2360
+ layers: [Plan.column(Plan.values("wip", (n) => ({ y: n })))],
2493
2361
  }),
2494
2362
  Plan.series.table(OrdinalOrderRow, {
2495
2363
  key: "count", title: "Counts",
2496
2364
  match: r => r.series.equal("count"),
2497
2365
  label: r => r.label,
2498
- cells: r => Plan.tableCells(r.counts),
2366
+ cells: Plan.tableCells(Plan.values("counts", (n) => ({ value: n }))),
2499
2367
  format: Format.Number({ maximumFractionDigits: 0n }),
2500
2368
  }),
2501
2369
  Plan.series.cards(OrdinalOrderRow, {
2502
2370
  key: "crew", title: "Crews",
2503
2371
  match: r => r.series.equal("crew"),
2504
2372
  label: r => r.label,
2505
- chips: r => r.shifts.map((_$, s) => Plan.chip({ key: s.key, from: s.from, to: s.to, label: s.label, state: s.state })),
2373
+ chips: Plan.items("shifts", { from: "from", to: "to" }, (shift) => ({ label: shift.label, state: shift.state })),
2506
2374
  }),
2507
2375
  Plan.series.events(OrdinalOrderRow, {
2508
2376
  key: "gates", title: "Gates",
2509
2377
  match: r => r.series.equal("gates"),
2510
2378
  label: r => r.label, id: true,
2511
- marks: r => r.marks.map((_$, m) => Plan.mark({
2512
- key: m.key, at: m.phase, label: m.label,
2513
- kind: m.exception.ifElse(() => EXCEPTION, () => MILESTONE),
2379
+ marks: Plan.items("marks", { at: "phase" }, (gate) => ({
2380
+ label: gate.label, kind: gate.exception.ifElse(() => EXCEPTION, () => MILESTONE),
2514
2381
  })),
2515
2382
  }),
2516
2383
  ], ArrayType(Plan.Types.Series(OrdinalOrderRow)));
@@ -2530,18 +2397,22 @@ export const planOrdinalAxis = example({
2530
2397
  /** A task on the plan — its owner and its window. */
2531
2398
  export const SliceJob = StructType({ task: StringType, owner: StringType, start: DateTimeType, end: DateTimeType });
2532
2399
  /** The plan's tasks, by name — a record of their own, the rows the slice's `rows` source narrows (#1344). */
2533
- export const planSliceJobs = e3.record("plan_slice_jobs", DictType(StringType, SliceJob), new Map([
2534
- ["Planning", { task: "Planning", owner: "Team A", start: new Date("2024-01-01T00:00:00Z"), end: new Date("2024-01-15T00:00:00Z") }],
2535
- ["Design", { task: "Design", owner: "Team B", start: new Date("2024-01-10T00:00:00Z"), end: new Date("2024-02-01T00:00:00Z") }],
2536
- ["Development", { task: "Development", owner: "Team C", start: new Date("2024-01-20T00:00:00Z"), end: new Date("2024-03-15T00:00:00Z") }],
2537
- ["Testing", { task: "Testing", owner: "Team A", start: new Date("2024-03-01T00:00:00Z"), end: new Date("2024-03-30T00:00:00Z") }],
2538
- ]));
2539
- // The tasks over the interval each runs, alone and after its owner: a range of dates the rail chooses reads every task
2540
- // overlapping it once, an owner's beside it too.
2541
- export const planSliceJobsByTime = e3.recordIndex.interval("plan_slice_jobs_by_time", planSliceJobs, { from: "start", to: "end" });
2542
- export const planSliceJobsByOwnerTime = e3.recordIndex.interval("plan_slice_jobs_by_owner_time", planSliceJobs, { on: ["owner"], from: "start", to: "end" });
2400
+ export const planSliceJobs = e3.record("plan_slice_jobs", DictType(StringType, SliceJob), {
2401
+ initial: new Map([
2402
+ ["Planning", { task: "Planning", owner: "Team A", start: new Date("2024-01-01T00:00:00Z"), end: new Date("2024-01-15T00:00:00Z") }],
2403
+ ["Design", { task: "Design", owner: "Team B", start: new Date("2024-01-10T00:00:00Z"), end: new Date("2024-02-01T00:00:00Z") }],
2404
+ ["Development", { task: "Development", owner: "Team C", start: new Date("2024-01-20T00:00:00Z"), end: new Date("2024-03-15T00:00:00Z") }],
2405
+ ["Testing", { task: "Testing", owner: "Team A", start: new Date("2024-03-01T00:00:00Z"), end: new Date("2024-03-30T00:00:00Z") }],
2406
+ ]),
2407
+ indexes: (index) => ({
2408
+ // The tasks over the interval each runs, alone and after its owner: a range of dates the rail chooses reads every task
2409
+ // overlapping it once, an owner's beside it too.
2410
+ plan_slice_jobs_by_time: index.interval({ from: "start", to: "end" }),
2411
+ plan_slice_jobs_by_owner_time: index.interval({ on: ["owner"], from: "start", to: "end" }),
2412
+ }),
2413
+ });
2543
2414
  export const slicePlanChrome = example({
2544
- keywords: ["Slice", "Slice.bindRecords", "rows", "Plan", "slice", "rail", "filter", "range", "brush", "timeline", "Data.bindPaged", "recordIndex", "interval", "e3.record", "#1344"],
2415
+ keywords: ["Slice", "Slice.bindRecords", "rows", "Plan", "slice", "rail", "filter", "range", "brush", "timeline", "Data.bindPaged", "indexes", "interval", "e3.record", "#1344"],
2545
2416
  description: "Ops plan — a Plan narrowed through one Slice.bindRecords rail over tasks bound from e3: its `rows` source gives the canvas its rows, paged through the tasks' interval index and narrowed by owner and by their dates — fields of the rail the viewer sets — while the window is the canvas's own: the toolbar's dates and the horizon brush move it, and never decide which tasks are listed",
2546
2417
  fn: East.function([], UIComponentType, (_$) => {
2547
2418
  return (_jsx(Reactive, { children: $ => {
@@ -2549,7 +2420,7 @@ export const slicePlanChrome = example({
2549
2420
  // the rail, the viewer's to set: the dates on screen never narrow the tasks.
2550
2421
  const slice = $.let(Slice.bindRecords("ex.slice.plan.chrome", {
2551
2422
  rows: {
2552
- source: Data.bindPaged(planSliceJobs, { indexes: [planSliceJobsByTime, planSliceJobsByOwnerTime] }),
2423
+ source: Data.bindPaged(planSliceJobs, { indexes: ["plan_slice_jobs_by_time", "plan_slice_jobs_by_owner_time"] }),
2553
2424
  label: "Tasks",
2554
2425
  fields: { owner: { label: "Owner", hints: ["Team A", "Team B", "Team C"] }, start: { label: "Start" } },
2555
2426
  search: [],