@elaraai/e3-ui 1.0.94 → 1.0.95

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 (303) hide show
  1. package/dist/src/allocation/adapters.d.ts +159 -0
  2. package/dist/src/allocation/adapters.d.ts.map +1 -0
  3. package/dist/src/allocation/adapters.js +241 -0
  4. package/dist/src/allocation/adapters.js.map +1 -0
  5. package/dist/src/allocation/functions.d.ts +664 -0
  6. package/dist/src/allocation/functions.d.ts.map +1 -0
  7. package/dist/src/allocation/functions.js +782 -0
  8. package/dist/src/allocation/functions.js.map +1 -0
  9. package/dist/src/allocation/index.d.ts +2985 -0
  10. package/dist/src/allocation/index.d.ts.map +1 -0
  11. package/dist/src/allocation/index.js +499 -0
  12. package/dist/src/allocation/index.js.map +1 -0
  13. package/dist/src/allocation/types.d.ts +832 -0
  14. package/dist/src/allocation/types.d.ts.map +1 -0
  15. package/dist/src/allocation/types.js +360 -0
  16. package/dist/src/allocation/types.js.map +1 -0
  17. package/dist/src/bind/func.d.ts +14 -0
  18. package/dist/src/bind/func.d.ts.map +1 -1
  19. package/dist/src/bind/func.js +22 -1
  20. package/dist/src/bind/func.js.map +1 -1
  21. package/dist/src/calendar/index.d.ts +510 -1639
  22. package/dist/src/calendar/index.d.ts.map +1 -1
  23. package/dist/src/calendar/index.js +56 -29
  24. package/dist/src/calendar/index.js.map +1 -1
  25. package/dist/src/flowchart/canvas.d.ts +9 -21
  26. package/dist/src/flowchart/canvas.d.ts.map +1 -1
  27. package/dist/src/flowchart/canvas.js +10 -22
  28. package/dist/src/flowchart/canvas.js.map +1 -1
  29. package/dist/src/flowchart/flowchart.d.ts +19 -13
  30. package/dist/src/flowchart/flowchart.d.ts.map +1 -1
  31. package/dist/src/flowchart/flowchart.js.map +1 -1
  32. package/dist/src/flowchart/index.d.ts +44 -21
  33. package/dist/src/flowchart/index.d.ts.map +1 -1
  34. package/dist/src/flowchart/index.js +43 -20
  35. package/dist/src/flowchart/index.js.map +1 -1
  36. package/dist/src/flowchart/library.d.ts +166 -81
  37. package/dist/src/flowchart/library.d.ts.map +1 -1
  38. package/dist/src/flowchart/library.js +232 -105
  39. package/dist/src/flowchart/library.js.map +1 -1
  40. package/dist/src/flowchart/over.d.ts +9 -16
  41. package/dist/src/flowchart/over.d.ts.map +1 -1
  42. package/dist/src/flowchart/over.js +9 -16
  43. package/dist/src/flowchart/over.js.map +1 -1
  44. package/dist/src/flowchart/payload.d.ts +710 -508
  45. package/dist/src/flowchart/payload.d.ts.map +1 -1
  46. package/dist/src/flowchart/payload.js +128 -74
  47. package/dist/src/flowchart/payload.js.map +1 -1
  48. package/dist/src/flowchart/window.d.ts +132 -347
  49. package/dist/src/flowchart/window.d.ts.map +1 -1
  50. package/dist/src/flowchart/window.js +48 -55
  51. package/dist/src/flowchart/window.js.map +1 -1
  52. package/dist/src/index.d.ts +11 -7
  53. package/dist/src/index.d.ts.map +1 -1
  54. package/dist/src/index.js +14 -6
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/internal.d.ts +18 -7
  57. package/dist/src/internal.d.ts.map +1 -1
  58. package/dist/src/internal.js +28 -9
  59. package/dist/src/internal.js.map +1 -1
  60. package/dist/src/plan/event-rows.d.ts +26 -10
  61. package/dist/src/plan/event-rows.d.ts.map +1 -1
  62. package/dist/src/plan/event-rows.js +39 -13
  63. package/dist/src/plan/event-rows.js.map +1 -1
  64. package/dist/src/plan/ir.d.ts +244 -815
  65. package/dist/src/plan/ir.d.ts.map +1 -1
  66. package/dist/src/plan/ir.js +8 -6
  67. package/dist/src/plan/ir.js.map +1 -1
  68. package/dist/src/plan/library.d.ts +3 -0
  69. package/dist/src/plan/library.d.ts.map +1 -1
  70. package/dist/src/plan/library.js +1 -1
  71. package/dist/src/plan/library.js.map +1 -1
  72. package/dist/src/plan/ordered-source.d.ts +1 -0
  73. package/dist/src/plan/ordered-source.d.ts.map +1 -1
  74. package/dist/src/plan/plan.d.ts +559 -1667
  75. package/dist/src/plan/plan.d.ts.map +1 -1
  76. package/dist/src/plan/plan.js +123 -45
  77. package/dist/src/plan/plan.js.map +1 -1
  78. package/dist/src/plan/root.d.ts +16 -20
  79. package/dist/src/plan/root.d.ts.map +1 -1
  80. package/dist/src/plan/root.js +23 -33
  81. package/dist/src/plan/root.js.map +1 -1
  82. package/dist/src/roster/coverage.d.ts +17 -231
  83. package/dist/src/roster/coverage.d.ts.map +1 -1
  84. package/dist/src/roster/coverage.js +94 -90
  85. package/dist/src/roster/coverage.js.map +1 -1
  86. package/dist/src/roster/fit.d.ts +59 -0
  87. package/dist/src/roster/fit.d.ts.map +1 -0
  88. package/dist/src/roster/fit.js +155 -0
  89. package/dist/src/roster/fit.js.map +1 -0
  90. package/dist/src/roster/index.d.ts +2862 -3662
  91. package/dist/src/roster/index.d.ts.map +1 -1
  92. package/dist/src/roster/index.js +383 -81
  93. package/dist/src/roster/index.js.map +1 -1
  94. package/dist/src/roster/library.d.ts +37 -92
  95. package/dist/src/roster/library.d.ts.map +1 -1
  96. package/dist/src/roster/library.js +25 -64
  97. package/dist/src/roster/library.js.map +1 -1
  98. package/dist/src/roster/lists.d.ts +195 -0
  99. package/dist/src/roster/lists.d.ts.map +1 -0
  100. package/dist/src/roster/lists.js +233 -0
  101. package/dist/src/roster/lists.js.map +1 -0
  102. package/dist/src/roster/rules.d.ts +17 -260
  103. package/dist/src/roster/rules.d.ts.map +1 -1
  104. package/dist/src/roster/rules.js +135 -132
  105. package/dist/src/roster/rules.js.map +1 -1
  106. package/dist/src/roster/targets.d.ts +115 -0
  107. package/dist/src/roster/targets.d.ts.map +1 -0
  108. package/dist/src/roster/targets.js +213 -0
  109. package/dist/src/roster/targets.js.map +1 -0
  110. package/dist/src/roster/times.d.ts +251 -0
  111. package/dist/src/roster/times.d.ts.map +1 -0
  112. package/dist/src/roster/times.js +174 -0
  113. package/dist/src/roster/times.js.map +1 -0
  114. package/dist/src/roster/types.d.ts +828 -183
  115. package/dist/src/roster/types.d.ts.map +1 -1
  116. package/dist/src/roster/types.js +224 -97
  117. package/dist/src/roster/types.js.map +1 -1
  118. package/dist/src/schedule/accessor.d.ts +28 -0
  119. package/dist/src/schedule/accessor.d.ts.map +1 -0
  120. package/dist/src/schedule/accessor.js +39 -0
  121. package/dist/src/schedule/accessor.js.map +1 -0
  122. package/dist/src/schedule/events.d.ts +170 -42
  123. package/dist/src/schedule/events.d.ts.map +1 -1
  124. package/dist/src/schedule/events.js +431 -171
  125. package/dist/src/schedule/events.js.map +1 -1
  126. package/dist/src/schedule/index.d.ts +41 -4
  127. package/dist/src/schedule/index.d.ts.map +1 -1
  128. package/dist/src/schedule/index.js +35 -3
  129. package/dist/src/schedule/index.js.map +1 -1
  130. package/dist/src/schedule/library.d.ts +13 -3
  131. package/dist/src/schedule/library.d.ts.map +1 -1
  132. package/dist/src/schedule/library.js +11 -4
  133. package/dist/src/schedule/library.js.map +1 -1
  134. package/dist/src/schedule/periods.d.ts +115 -0
  135. package/dist/src/schedule/periods.d.ts.map +1 -0
  136. package/dist/src/schedule/periods.js +215 -0
  137. package/dist/src/schedule/periods.js.map +1 -0
  138. package/dist/src/schedule/record-window.d.ts +25 -3
  139. package/dist/src/schedule/record-window.d.ts.map +1 -1
  140. package/dist/src/schedule/record-window.js +31 -18
  141. package/dist/src/schedule/record-window.js.map +1 -1
  142. package/dist/src/schedule/resources.d.ts +82 -4
  143. package/dist/src/schedule/resources.d.ts.map +1 -1
  144. package/dist/src/schedule/resources.js +44 -14
  145. package/dist/src/schedule/resources.js.map +1 -1
  146. package/dist/src/schedule/session.d.ts +69 -0
  147. package/dist/src/schedule/session.d.ts.map +1 -0
  148. package/dist/src/schedule/session.js +121 -0
  149. package/dist/src/schedule/session.js.map +1 -0
  150. package/dist/src/schedule/templates.d.ts +14 -2
  151. package/dist/src/schedule/templates.d.ts.map +1 -1
  152. package/dist/src/schedule/templates.js +5 -3
  153. package/dist/src/schedule/templates.js.map +1 -1
  154. package/dist/src/schedule/types.d.ts +1671 -37
  155. package/dist/src/schedule/types.d.ts.map +1 -1
  156. package/dist/src/schedule/types.js +196 -5
  157. package/dist/src/schedule/types.js.map +1 -1
  158. package/dist/src/schedule/window.d.ts +14 -0
  159. package/dist/src/schedule/window.d.ts.map +1 -1
  160. package/dist/src/schedule/window.js +14 -4
  161. package/dist/src/schedule/window.js.map +1 -1
  162. package/dist/src/schedule/words.d.ts +22 -0
  163. package/dist/src/schedule/words.d.ts.map +1 -0
  164. package/dist/src/schedule/words.js +25 -0
  165. package/dist/src/schedule/words.js.map +1 -0
  166. package/dist/src/sheet/index.d.ts +61 -27
  167. package/dist/src/sheet/index.d.ts.map +1 -1
  168. package/dist/src/sheet/index.js +59 -25
  169. package/dist/src/sheet/index.js.map +1 -1
  170. package/dist/src/sheet/library.d.ts +21 -3
  171. package/dist/src/sheet/library.d.ts.map +1 -1
  172. package/dist/src/sheet/library.js +23 -6
  173. package/dist/src/sheet/library.js.map +1 -1
  174. package/dist/src/sheet/registers.d.ts +100 -85
  175. package/dist/src/sheet/registers.d.ts.map +1 -1
  176. package/dist/src/sheet/registers.js +100 -85
  177. package/dist/src/sheet/registers.js.map +1 -1
  178. package/dist/src/sheet/root.d.ts +8 -12
  179. package/dist/src/sheet/root.d.ts.map +1 -1
  180. package/dist/src/sheet/root.js +6 -40
  181. package/dist/src/sheet/root.js.map +1 -1
  182. package/dist/src/sheet/sheet.d.ts +598 -2349
  183. package/dist/src/sheet/sheet.d.ts.map +1 -1
  184. package/dist/src/sheet/sheet.js +124 -37
  185. package/dist/src/sheet/sheet.js.map +1 -1
  186. package/dist/src/sheet/sub-rows.d.ts +74 -28
  187. package/dist/src/sheet/sub-rows.d.ts.map +1 -1
  188. package/dist/src/sheet/sub-rows.js +74 -28
  189. package/dist/src/sheet/sub-rows.js.map +1 -1
  190. package/dist/src/sheet/templates.d.ts +98 -34
  191. package/dist/src/sheet/templates.d.ts.map +1 -1
  192. package/dist/src/sheet/templates.js +102 -58
  193. package/dist/src/sheet/templates.js.map +1 -1
  194. package/dist/src/sheet/types.d.ts +289 -1359
  195. package/dist/src/sheet/types.d.ts.map +1 -1
  196. package/dist/src/sheet/types.js +20 -26
  197. package/dist/src/sheet/types.js.map +1 -1
  198. package/dist/src/sheet/views.d.ts +10 -11
  199. package/dist/src/sheet/views.d.ts.map +1 -1
  200. package/dist/src/sheet/views.js +15 -20
  201. package/dist/src/sheet/views.js.map +1 -1
  202. package/dist/src/{roster/visibility.d.ts → utils/key-visibility.d.ts} +42 -7
  203. package/dist/src/utils/key-visibility.d.ts.map +1 -0
  204. package/dist/src/utils/key-visibility.js +36 -0
  205. package/dist/src/utils/key-visibility.js.map +1 -0
  206. package/dist/src/utils/ordered-source.d.ts +1 -0
  207. package/dist/src/utils/ordered-source.d.ts.map +1 -1
  208. package/dist/src/utils/ordered-source.js +8 -3
  209. package/dist/src/utils/ordered-source.js.map +1 -1
  210. package/dist/src/utils/record-sources.d.ts +107 -0
  211. package/dist/src/utils/record-sources.d.ts.map +1 -0
  212. package/dist/src/utils/record-sources.js +132 -0
  213. package/dist/src/utils/record-sources.js.map +1 -0
  214. package/dist/test/allocation/allocation.examples.d.ts +2902 -0
  215. package/dist/test/allocation/allocation.examples.d.ts.map +1 -0
  216. package/dist/test/allocation/allocation.examples.js +643 -0
  217. package/dist/test/allocation/allocation.examples.js.map +1 -0
  218. package/dist/test/allocation/fit.examples.d.ts +546 -0
  219. package/dist/test/allocation/fit.examples.d.ts.map +1 -0
  220. package/dist/test/allocation/fit.examples.js +171 -0
  221. package/dist/test/allocation/fit.examples.js.map +1 -0
  222. package/dist/test/allocation/kiln-yard.d.ts +181 -0
  223. package/dist/test/allocation/kiln-yard.d.ts.map +1 -0
  224. package/dist/test/allocation/kiln-yard.js +228 -0
  225. package/dist/test/allocation/kiln-yard.js.map +1 -0
  226. package/dist/test/bind/data/data.examples.d.ts +651 -1
  227. package/dist/test/bind/data/data.examples.d.ts.map +1 -1
  228. package/dist/test/bind/data/data.examples.js +140 -2
  229. package/dist/test/bind/data/data.examples.js.map +1 -1
  230. package/dist/test/bind/func/func.examples.d.ts +10 -1
  231. package/dist/test/bind/func/func.examples.d.ts.map +1 -1
  232. package/dist/test/bind/func/func.examples.js +8 -1
  233. package/dist/test/bind/func/func.examples.js.map +1 -1
  234. package/dist/test/bind/record/record.examples.d.ts +633 -1
  235. package/dist/test/bind/record/record.examples.d.ts.map +1 -1
  236. package/dist/test/bind/record/record.examples.js +26 -2
  237. package/dist/test/bind/record/record.examples.js.map +1 -1
  238. package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -1
  239. package/dist/test/calendar/calendar-records.examples.js +18 -14
  240. package/dist/test/calendar/calendar-records.examples.js.map +1 -1
  241. package/dist/test/calendar/calendar.examples.d.ts +521 -16
  242. package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
  243. package/dist/test/calendar/calendar.examples.js +182 -135
  244. package/dist/test/calendar/calendar.examples.js.map +1 -1
  245. package/dist/test/examples-package.d.ts +75 -0
  246. package/dist/test/examples-package.d.ts.map +1 -0
  247. package/dist/test/examples-package.js +263 -0
  248. package/dist/test/examples-package.js.map +1 -0
  249. package/dist/test/flowchart/flowchart.examples.d.ts +211 -19
  250. package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
  251. package/dist/test/flowchart/flowchart.examples.js +102 -75
  252. package/dist/test/flowchart/flowchart.examples.js.map +1 -1
  253. package/dist/test/plan/plan-events.examples.d.ts +1439 -209
  254. package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
  255. package/dist/test/plan/plan-events.examples.js +199 -59
  256. package/dist/test/plan/plan-events.examples.js.map +1 -1
  257. package/dist/test/plan/plan-records.examples.d.ts +420 -0
  258. package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
  259. package/dist/test/plan/plan-records.examples.js +70 -19
  260. package/dist/test/plan/plan-records.examples.js.map +1 -1
  261. package/dist/test/plan/plan-windows.examples.d.ts +636 -55
  262. package/dist/test/plan/plan-windows.examples.d.ts.map +1 -1
  263. package/dist/test/plan/plan-windows.examples.js +69 -33
  264. package/dist/test/plan/plan-windows.examples.js.map +1 -1
  265. package/dist/test/plan/plan.examples.d.ts +153 -31
  266. package/dist/test/plan/plan.examples.d.ts.map +1 -1
  267. package/dist/test/plan/plan.examples.js +178 -101
  268. package/dist/test/plan/plan.examples.js.map +1 -1
  269. package/dist/test/roster/domain.examples.d.ts +467 -250
  270. package/dist/test/roster/domain.examples.d.ts.map +1 -1
  271. package/dist/test/roster/domain.examples.js +92 -29
  272. package/dist/test/roster/domain.examples.js.map +1 -1
  273. package/dist/test/roster/roster-records.examples.d.ts +430 -586
  274. package/dist/test/roster/roster-records.examples.d.ts.map +1 -1
  275. package/dist/test/roster/roster-records.examples.js +102 -43
  276. package/dist/test/roster/roster-records.examples.js.map +1 -1
  277. package/dist/test/roster/roster.examples.d.ts +2319 -606
  278. package/dist/test/roster/roster.examples.d.ts.map +1 -1
  279. package/dist/test/roster/roster.examples.js +456 -148
  280. package/dist/test/roster/roster.examples.js.map +1 -1
  281. package/dist/test/roster/warehouse.d.ts +172 -63
  282. package/dist/test/roster/warehouse.d.ts.map +1 -1
  283. package/dist/test/roster/warehouse.js +172 -39
  284. package/dist/test/roster/warehouse.js.map +1 -1
  285. package/dist/test/schedule/schedule.examples.d.ts +3499 -0
  286. package/dist/test/schedule/schedule.examples.d.ts.map +1 -0
  287. package/dist/test/schedule/schedule.examples.js +192 -0
  288. package/dist/test/schedule/schedule.examples.js.map +1 -0
  289. package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -1
  290. package/dist/test/sheet/sheet-records.examples.js +22 -18
  291. package/dist/test/sheet/sheet-records.examples.js.map +1 -1
  292. package/dist/test/sheet/sheet.examples.d.ts +715 -26
  293. package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
  294. package/dist/test/sheet/sheet.examples.js +204 -130
  295. package/dist/test/sheet/sheet.examples.js.map +1 -1
  296. package/package.json +17 -10
  297. package/dist/src/roster/people.d.ts +0 -50
  298. package/dist/src/roster/people.d.ts.map +0 -1
  299. package/dist/src/roster/people.js +0 -32
  300. package/dist/src/roster/people.js.map +0 -1
  301. package/dist/src/roster/visibility.d.ts.map +0 -1
  302. package/dist/src/roster/visibility.js +0 -25
  303. package/dist/src/roster/visibility.js.map +0 -1
@@ -20,6 +20,16 @@
20
20
  * events on one resource at once are a conflict (`overlaps`), and the kind's
21
21
  * own inspector for one event (`inspector`, #1197).
22
22
  *
23
+ * The builders over the app's own records take their roles beside Plan's
24
+ * (#1367, `Builder Interfaces Spec.md` §4.1): the Roster's — the fields a
25
+ * duty's shift, group, position and activity are (`shift`, `group`,
26
+ * `position`, `activity`), and the hours it counts (`hours`, an accessor) —
27
+ * and the Allocation's — the fields a bin's arrival, parts, hold and kind are
28
+ * (`arrival`, `parts`, `hold`, `kind`). A kind is one builder's or the
29
+ * other's. A builder ignores the roles it has no use for, so one record of
30
+ * duties drives a Calendar and the Roster at once. `title` is optional: the
31
+ * Calendar and Plan, which draw it, refuse a kind without one.
32
+ *
23
33
  * The kind closes the record behind East functions over beast2 bytes
24
34
  * ({@link ScheduleKindType}): its events over a window and its backlog, each
25
35
  * with the drafts in place; a gesture written into an entry; the author's
@@ -28,13 +38,15 @@
28
38
  * true })`); and its history. A builder's payload is one type whatever the
29
39
  * records hold, and the renderer never needs a row type.
30
40
  *
31
- * A record Slice reads overlap queries through its declared interval indexes
32
- * (`slice: Slice.bindRecord(...)`, with `range` naming the start field).
33
- * It shares the toolbar's filters and window, counts original events once,
34
- * and reads backlog through the same choices with an Unset start. It requires
35
- * `entries` for original-key inspection and Save. Existing drafts outside a
36
- * new narrowing remain in the session; Save includes them. Do not combine
37
- * this with a local `filter`, `window`, or `backlogWindow`.
41
+ * A builder's `Slice.bindRecords` narrows a kind through the source it names
42
+ * after the kind's slot (#1343): the builder hands that part to `build(slot,
43
+ * part)`, and the kind reads overlap queries through its declared interval
44
+ * indexes (the source's `range` naming the start field). It shares the
45
+ * toolbar's filters and window, counts original events once, and reads backlog
46
+ * through the same choices with an Unset start. It requires `entries` for
47
+ * original-key inspection and Save. Existing drafts outside a new narrowing
48
+ * remain in the session; Save includes them. A kind narrowed so takes no
49
+ * `window` or `backlogWindow`.
38
50
  *
39
51
  * A large record may also be read a window at a time (#1199, `window.ts`): given
40
52
  * `window`, a paged read of a day index keyed by `Schedule.days`, the kind's
@@ -50,36 +62,41 @@
50
62
  * editing session's, which is a session over the record's revision rather
51
63
  * than its snapshot — its drafts held by key, each checked by the record
52
64
  * against what it began from, and a Save's entries read back by key. It builds
53
- * two ways:
54
- * `build(slot)` is the Calendar's kind, which takes no notice of Plan's
55
- * options, and `buildPlan(slot)` Plan's ({@link PlanEventKindType}), the same
56
- * kind with how it draws, its roles, and its events as Plan draws them.
65
+ * four ways:
66
+ * `build(slot, part)` is the Calendar's kind, which takes no notice of Plan's
67
+ * options; `buildPlan(slot, part)` Plan's ({@link PlanEventKindType}), the
68
+ * same kind with how it draws, its roles, and its events as Plan draws them;
69
+ * and `buildRoster(slot, part)` the Roster's ({@link ScheduleRosterKindType})
70
+ * and `buildAllocation(slot, part)` the Allocation's
71
+ * ({@link ScheduleAllocationKindType}): the Calendar's kind, its form over the
72
+ * fields none of the builder's roles writes, with the stored rows of a
73
+ * period, and the builder's roles read from a row and written into one —
74
+ * every other field of the row as it was (B1), a new row's its type's
75
+ * default.
57
76
  *
58
77
  * A record may be keyed by any type: an event is named by its key's text, a
59
78
  * String key as it is, any other key as East prints it.
60
79
  *
61
80
  * @packageDocumentation
62
81
  */
63
- import { ArrayType, BlobType, BooleanType, DateTimeType, DictType, East, Expr, FloatType, IntegerType, OptionType, StringType, StructType, isTypeEqual, none, printType, some, toEastTypeValue, variant, } from "@elaraai/east";
64
- import { EventStateType } from "@elaraai/east-ui";
65
- import { EditingApplyResultType, EditingChangeSetTypeFor, EditingDraftFieldType, EditingReadinessType, EditingType, EditingWireApplyType, Fields, FieldSpecType, TickFormatType, } from "@elaraai/east-ui/internal";
66
- import { Record } from "../bind/record.js";
82
+ import { ArrayType, BlobType, BooleanType, DateTimeType, DictType, East, Expr, FloatType, IntegerType, OptionType, StringType, StructType, defaultValue, isTypeEqual, none, printType, some, toEastTypeValue, variant, } from "@elaraai/east";
83
+ import { EventStateType, TimeStepType } from "@elaraai/east-ui";
84
+ import { EditingDraftFieldType, EditingReadinessType, Fields, FieldSpecType, TickFormatType } from "@elaraai/east-ui/internal";
67
85
  import { RowInspectorType, rowInspector } from "../utils/row-inspector.js";
86
+ import { scheduleAccessor } from "./accessor.js";
68
87
  import { SCHEDULE_TEMPLATES, checkedTemplates } from "./templates.js";
69
88
  import { SCHEDULE_DEF } from "./resources.js";
70
89
  import { scheduleRecordWindow } from "./record-window.js";
71
- import { checkEntries, checkIndexWindow, scheduleBacklogWindow, scheduleDayWindow, scheduleEntryByKey, scheduleLastKey } from "./window.js";
72
- import { PlanEventItemType, PlanEventKindType, PlanEventReadType, ScheduleClockType, ScheduleDraftsType, ScheduleDurationType, ScheduleEntriesType, ScheduleItemType, ScheduleKindType, ScheduleReadyEntryType, ScheduleResourceRefType, ScheduleStatusCasesFor, ScheduleStatusType, ScheduleWriteType, ScheduleTemplateType, ScheduleReadType, } from "./types.js";
90
+ import { scheduleRecordSession } from "./session.js";
91
+ import { checkEntries, checkIndexWindow, optionOf, scheduleBacklogWindow, scheduleDayWindow, typeText } from "./window.js";
92
+ import { PlanEventItemType, PlanEventKindType, PlanEventReadType, ScheduleAllocationEntryType, ScheduleAllocationKindType, ScheduleAllocationRolesType, ScheduleClockType, ScheduleDraftsType, ScheduleDurationType, ScheduleItemType, ScheduleKindType, ScheduleReadyEntryType, ScheduleResourceRefType, ScheduleRosterEntryType, ScheduleRosterKindType, ScheduleRosterRolesType, ScheduleStatusCasesFor, ScheduleStatusType, ScheduleWriteType, ScheduleTemplateType, ScheduleReadType, } from "./types.js";
93
+ /** The Roster's roles, as a kind's options name them. */
94
+ const ROSTER_ROLES = ["shift", "group", "position", "activity", "hours"];
95
+ /** The Allocation's roles, as a kind's options name them. */
96
+ const ALLOCATION_ROLES = ["arrival", "parts", "hold", "kind"];
73
97
  // ============================================================================
74
98
  // Reading East types
75
99
  // ============================================================================
76
- /** Whether a type is an Option, and of what. */
77
- function optionOf(type) {
78
- if (type.type !== "Variant")
79
- return undefined;
80
- const some = type.cases["some"];
81
- return some !== undefined && isTypeEqual(type, OptionType(some)) ? some : undefined;
82
- }
83
100
  /** A variant's cases, when every one of them holds a String. */
84
101
  function stringCases(type) {
85
102
  if (type.type !== "Variant" || optionOf(type) !== undefined)
@@ -91,17 +108,19 @@ function stringCases(type) {
91
108
  function fieldOf(row, name) {
92
109
  return row[name];
93
110
  }
94
- /** A type as the refusals name it: an Option as an Option of what it holds. */
95
- function typeText(type) {
96
- const inner = optionOf(type);
97
- return inner === undefined ? printType(type) : `an Option of ${printType(inner)}`;
98
- }
99
111
  /** The ways a kind draws. */
100
112
  const DRAWS = ["span", "buckets", "cards", "marks"];
101
113
  export function scheduleEvents(record, input) {
102
114
  const config = input;
103
115
  const where = `Schedule.events: "${config.name}"`;
104
- const handle = record;
116
+ // The options #1343 moved to the builder, refused by name for an app that still passes them.
117
+ if ("slice" in config) {
118
+ throw new Error(`${where}: \`slice\` moves to the builder — name a source of its Slice.bindRecords after this kind's slot, ` +
119
+ "slice={{ slice: Slice.bindRecords(key, { <slot>: { source, fields, range } }) }}, and the builder narrows the kind through it");
120
+ }
121
+ if ("filter" in config) {
122
+ throw new Error(`${where}: \`filter\` is gone — narrow the kind through its builder's Slice.bindRecords, a source named after its slot, over the record's indexes`);
123
+ }
105
124
  const handleType = Expr.type(record);
106
125
  const read = handleType.type === "Struct" ? handleType.fields["read"] : undefined;
107
126
  if (handleType.type !== "Struct" || read === undefined || read.type !== "Function"
@@ -121,9 +140,10 @@ export function scheduleEvents(record, input) {
121
140
  throw new Error(`${where}: \`${prop}\` names "${name}", a field the row does not have (${Object.keys(fields).join(", ")})`);
122
141
  return type;
123
142
  };
124
- // The title.
125
- if (!isTypeEqual(fieldType("title", config.title), StringType)) {
126
- throw new Error(`${where}: \`title\` names a String field — "${config.title}" holds ${typeText(fields[config.title])}`);
143
+ // The title: optional, as the Roster and the Allocation draw none (#1367); the Calendar and Plan refuse a kind without one.
144
+ const titleField = config.title;
145
+ if (titleField !== undefined && !isTypeEqual(fieldType("title", titleField), StringType)) {
146
+ throw new Error(`${where}: \`title\` names a String field — "${titleField}" holds ${typeText(fields[titleField])}`);
127
147
  }
128
148
  // The times: `start` and `end`, DateTime or Options of DateTime alike; or an instant kind's `at`, in their place.
129
149
  const instantKind = config.at !== undefined;
@@ -229,6 +249,33 @@ export function scheduleEvents(record, input) {
229
249
  const stateField = role("state", config.state, EventStateType, "an EventStateType field, the lifecycle an event wears");
230
250
  const quantityField = role("quantity.field", config.quantity === undefined ? undefined : config.quantity.field, FloatType, "a Float field, the quantity a bar prints");
231
251
  const laneField = role("lane", config.lane, StringType, "a String field, the lane a tile sits in");
252
+ // The builders' roles (#1367, `Builder Interfaces Spec.md` §4.1), each a field of the row, checked as Plan's are. A
253
+ // kind is the Roster's duties or the Allocation's bins, never both.
254
+ const given = (roles) => roles.filter((r) => config[r] !== undefined);
255
+ const rosterGiven = given(ROSTER_ROLES);
256
+ const allocationGiven = given(ALLOCATION_ROLES);
257
+ if (rosterGiven.length > 0 && allocationGiven.length > 0) {
258
+ throw new Error(`${where}: holds roles of both the Roster (${rosterGiven.join(", ")}) and the Allocation (${allocationGiven.join(", ")}) — ` +
259
+ "a kind is the Roster's duties or the Allocation's bins: give it one builder's roles");
260
+ }
261
+ const shiftField = role("shift", config.shift, StringType, "a String field");
262
+ const groupField = role("group", config.group, StringType, "a String field");
263
+ const positionField = role("position", config.position, StringType, "a String field");
264
+ const activityField = role("activity", config.activity, OptionType(StringType), "an Option of a String field");
265
+ const arrivalField = role("arrival", config.arrival, OptionType(StringType), "an Option of a String field");
266
+ const partsField = role("parts.field", config.parts === undefined ? undefined : config.parts.field, DictType(StringType, FloatType), "a Dict of String to Float field");
267
+ if (config.parts !== undefined && (typeof config.parts.of !== "string" || config.parts.of === "")) {
268
+ throw new Error(`${where}: \`parts.of\` names the resource slot its keys are of — the builder's resources a bin is allocated to (of: "kilns")`);
269
+ }
270
+ const holdField = role("hold", config.hold, TimeStepType, "a TimeStep field");
271
+ const kindField = role("kind", config.kind, OptionType(StringType), "an Option of a String field");
272
+ // The fields none of the Allocation's roles maps (#1371): what an arrival's values give a booked bin (B13) — every
273
+ // field but its time, its lane and its roles' fields — which the Allocation reads at build, and `buildAllocation` writes.
274
+ const allocationMapped = new Set([startField, endField, ...[resource?.field, arrivalField, quantityField, partsField, holdField, kindField]
275
+ .filter((name) => name !== undefined)]);
276
+ const allocationValues = StructType(Object.fromEntries(Object.entries(fields).filter(([name]) => !allocationMapped.has(name))));
277
+ // The hours a duty counts, reified once and called per row.
278
+ const hoursOf = config.hours === undefined ? undefined : scheduleAccessor(rowType, keyType, FloatType, config.hours, (returns) => `${where}: \`hours\` returns a Float, the hours a duty counts — and this one returns ${returns}`);
232
279
  // The removed verdict, named (#1260) — a plain JS caller would otherwise lose it silently.
233
280
  if ("review" in config) {
234
281
  throw new Error(`${where}: \`review\` is removed (#1260) — a Plan approves and rejects nothing: its changes are drafts of its session, saved together`);
@@ -241,6 +288,17 @@ export function scheduleEvents(record, input) {
241
288
  const valuesType = StructType(Object.fromEntries(Object.entries(fields).filter(([name]) => !placed.has(name))));
242
289
  const boundTemplates = config.templates !== undefined && SCHEDULE_TEMPLATES in config.templates
243
290
  ? checkedTemplates(config.templates, valuesType, where) : undefined;
291
+ // A templates record's events run as long as each says, or — an instant kind's — are one time (#1344).
292
+ if (config.templates !== undefined && SCHEDULE_TEMPLATES in config.templates) {
293
+ if (!instantKind && !config.templates.timed) {
294
+ throw new Error(`${where}: its templates give each event's \`duration\` — Schedule.templates(rows, { …, duration: (row) => variant("hours", …) }), how long the event a template creates runs`);
295
+ }
296
+ if (instantKind && config.templates.timed) {
297
+ throw new Error(`${where}: an instant kind's events are one time, and its templates give a \`duration\` — leave it out of Schedule.templates`);
298
+ }
299
+ }
300
+ // The builder's source whose shown keys filter the templates: the one a templates list names (#1343).
301
+ const templatesSource = config.templates !== undefined && SCHEDULE_TEMPLATES in config.templates ? config.templates.source : undefined;
244
302
  const templates = config.templates !== undefined && !(SCHEDULE_TEMPLATES in config.templates) ? config.templates : [];
245
303
  const seen = new Set();
246
304
  for (const t of templates) {
@@ -255,9 +313,6 @@ export function scheduleEvents(record, input) {
255
313
  }
256
314
  }
257
315
  // The windows (#1199): the days in view through a day index, the backlog through a backlog index.
258
- if (config.slice !== undefined && (config.window !== undefined || config.backlogWindow !== undefined || config.filter !== undefined)) {
259
- throw new Error(`${where}: slice replaces window, backlogWindow and local filter; narrow through the record's indexes`);
260
- }
261
316
  const dayWindow = config.window === undefined ? undefined
262
317
  : checkIndexWindow(config.window, "window", keyType, rowType, DateTimeType, where);
263
318
  let backlogWindow;
@@ -275,34 +330,24 @@ export function scheduleEvents(record, input) {
275
330
  // Nor one event: it is read by its key through the record's own entries.
276
331
  let entries;
277
332
  if (config.entries !== undefined) {
278
- if (dayWindow === undefined && config.slice === undefined) {
279
- throw new Error(`${where}: \`entries\` reads one event by its key beside \`window\` or record \`slice\` — give one of those with it`);
280
- }
281
333
  entries = checkEntries(config.entries, keyType, rowType, `${where}: \`entries\``);
282
334
  }
283
- else if (dayWindow !== undefined || config.slice !== undefined) {
335
+ else if (dayWindow !== undefined) {
284
336
  throw new Error(`${where}: an indexed kind reads only the window — and one event, the inspector's ` +
285
337
  "or its editing's, is read by its key: give `entries` beside it, Data.bindPaged(record), the record's own entries");
286
338
  }
287
- const byKey = entries === undefined ? undefined : scheduleEntryByKey(entries, keyType, rowType);
288
- const lastKey = entries === undefined ? undefined : scheduleLastKey(entries, keyType);
289
- // The inspector's form: the fields a gesture does not write, in the hints' order.
290
- const omit = [...new Set([config.title, ...placed, ...(config.status === undefined ? [] : [config.status.field])])];
291
- const formSpecs = Fields.specs(rowType, (config.fields ?? {}), omit);
292
- // The Save: one patch commit through the record's patch door, its entries
293
- // checked against what the drafts began from.
294
- const apply = Record.onApply(record, { keyed: true });
339
+ // The record's session (`session.ts`): its reads, whole and by key, and its Save, one patch commit through the
340
+ // record's patch door, its entries checked against what the drafts began from — declared now, so a record bound
341
+ // without its patch door is refused where its kind is.
342
+ const session = scheduleRecordSession(record, keyType, rowType, entries);
343
+ const { readAll, byKey, idOf, keyOf } = session;
344
+ // The inspector's form: the fields a gesture does not write, in the hints' order. A builder over the app's own
345
+ // records draws its roles in its own sections, so its form leaves out the fields they name too.
346
+ const formOver = (omitted) => Fields.specs(rowType, (config.fields ?? {}), [...new Set(omitted.filter((name) => name !== undefined))]);
347
+ const formSpecs = formOver([titleField, ...placed, config.status?.field]);
295
348
  // ── The East the seams share ─────────────────────────────────────────
296
349
  const draftType = EditingDraftFieldType(rowType);
297
350
  const EntryRowType = StructType({ id: StringType, key: keyType, row: rowType });
298
- const readAll = handle["read"];
299
- // An entry's id is its key's text: a String key as it is, any other key as East prints it.
300
- const idOf = (keyType.type === "String"
301
- ? East.function([StringType], StringType, (_$, key) => key)
302
- : East.function([keyType], StringType, (_$, key) => East.print(key)));
303
- const keyOf = (keyType.type === "String"
304
- ? East.function([StringType], StringType, (_$, id) => id)
305
- : East.function([StringType], keyType, (_$, id) => id.parse(keyType)));
306
351
  // The entry a draft leaves: the drafted entry, none for one deleted, the held one for a draft it cannot read.
307
352
  const drafted = East.function([BlobType, OptionType(rowType)], OptionType(rowType), (_$, bytes, held) => bytes.decodeBeast(draftType, "v2").match({
308
353
  value: (_$2, row) => East.value(some(row), OptionType(rowType)),
@@ -313,7 +358,7 @@ export function scheduleEvents(record, input) {
313
358
  // entries it does not hold: a draft moved into a window shows, and one moved out of it leaves by its times.
314
359
  // Erased as the record's own read is, so the record whole and a window of it are one type.
315
360
  const HeldType = DictType(keyType, rowType);
316
- const withDrafts = East.function([HeldType, ScheduleDraftsType], ArrayType(EntryRowType), ($, held, drafts) => {
361
+ const withDraftsFor = (narrowed) => East.function([HeldType, ScheduleDraftsType], ArrayType(EntryRowType), ($, held, drafts) => {
317
362
  const idText = $.const(idOf);
318
363
  const parse = $.const(keyOf);
319
364
  const draft = $.const(drafted);
@@ -332,7 +377,7 @@ export function scheduleEvents(record, input) {
332
377
  // Existing drafts outside an indexed result stay in the session,
333
378
  // but do not leak back into a different narrowing. New entries
334
379
  // remain an explicit unsaved layer until their first Save.
335
- if (config.slice !== undefined && byKey !== undefined) {
380
+ if (narrowed && byKey !== undefined) {
336
381
  const find = $3.const(byKey);
337
382
  $3.if(find(id).hasTag("absent").not(), $4 => { $4.continue(label); });
338
383
  }
@@ -461,44 +506,73 @@ export function scheduleEvents(record, input) {
461
506
  }
462
507
  };
463
508
  collect(rowType, []);
464
- // The rows a window reads (#1199): the days in view through the day index, and the backlog through
465
- // the backlog index — each `none` while its search or a page is in flight. Without them, the record whole.
466
- const readDays = config.slice !== undefined
467
- ? scheduleRecordWindow(config.slice, keyType, rowType, startField, endField, optional, false, where)
468
- : dayWindow === undefined ? undefined : scheduleDayWindow(dayWindow, keyType, rowType, where);
469
- const indexedBacklog = config.slice !== undefined && optional
470
- ? scheduleRecordWindow(config.slice, keyType, rowType, startField, endField, optional, true, where) : undefined;
471
- const readBacklog = indexedBacklog === undefined
472
- ? backlogWindow === undefined ? undefined : scheduleBacklogWindow(backlogWindow, keyType, rowType, where)
473
- : East.function([], OptionType(HeldType), $ => {
474
- const read = $.const(indexedBacklog);
475
- return read(new Date(0), new Date(1));
509
+ const readsOf = (slot, part) => {
510
+ if (part === undefined) {
511
+ if (entries !== undefined && dayWindow === undefined) {
512
+ throw new Error(`${where}: \`entries\` reads one event by its key beside \`window\`, or beside the kind's source of its builder's ` +
513
+ `Slice.bindRecords (one named "${slot}") — give one of those with it`);
514
+ }
515
+ const unscheduled = backlogWindow === undefined ? undefined : scheduleBacklogWindow(backlogWindow, keyType, rowType, where);
516
+ return {
517
+ days: dayWindow === undefined ? undefined : scheduleDayWindow(dayWindow, keyType, rowType, where),
518
+ // A backlog index's read takes no window: the window is given, and read past.
519
+ backlog: unscheduled === undefined ? undefined : East.function([DateTimeType, DateTimeType], OptionType(HeldType), ($, _from, _to) => {
520
+ const read = $.const(unscheduled);
521
+ return read();
522
+ }),
523
+ withDrafts: withDraftsFor(false),
524
+ };
525
+ }
526
+ if (dayWindow !== undefined || backlogWindow !== undefined) {
527
+ throw new Error(`${where}: the builder's source "${slot}" narrows this kind through its record's indexes — leave \`window\` and \`backlogWindow\` out`);
528
+ }
529
+ if (entries === undefined) {
530
+ throw new Error(`${where}: the builder's source "${slot}" reads only its narrowing — and one event, the inspector's ` +
531
+ "or its editing's, is read by its key: give `entries` beside it, Data.bindPaged(record), the record's own entries");
532
+ }
533
+ const scheduled = scheduleRecordWindow(part, slot, keyType, rowType, startField, endField, optional, false, where);
534
+ if (!optional)
535
+ return { days: scheduled, backlog: undefined, withDrafts: withDraftsFor(true) };
536
+ // A kind with a backlog reads both through its source as one: what its narrowing holds over the window and in
537
+ // its backlog. A draft then moves an event between the two by its times alone, and a draft the narrowing does
538
+ // not hold never shows in either (#1343).
539
+ const backlogged = scheduleRecordWindow(part, slot, keyType, rowType, startField, endField, optional, true, where);
540
+ const both = East.function([DateTimeType, DateTimeType], OptionType(HeldType), ($, from, to) => {
541
+ const inWindow = $.const(scheduled);
542
+ const inBacklog = $.const(backlogged);
543
+ const result = $.let(East.value(none, OptionType(HeldType)), OptionType(HeldType));
544
+ $.match(inWindow(from, to), {
545
+ some: ($2, rows) => {
546
+ $2.match(inBacklog(from, to), {
547
+ some: ($3, more) => {
548
+ const all = $3.let(rows.copy());
549
+ $3(all.unionInPlace(more, (_$4, held) => held));
550
+ $3.assign(result, East.value(some(all), OptionType(HeldType)));
551
+ },
552
+ });
553
+ },
554
+ });
555
+ return result;
476
556
  });
477
- // One event by its key through the record's own entries, and the largest key (#1199): what a kind read a
478
- // window at a time reads in place of the record whole.
479
- // Adapt the author's typed predicate once; Slice owns its semantics and state.
480
- const filter = config.filter === undefined ? undefined
481
- : East.function([rowType, keyType], BooleanType, (_$, row, key) => config.filter(row, key));
557
+ return { days: both, backlog: both, withDrafts: withDraftsFor(true) };
558
+ };
482
559
  /** An item seam over a window: the events whose items `itemFn` makes that overlap `[from, to)`. */
483
- const windowOf = (itemType, itemFn) => East.function([DateTimeType, DateTimeType, ScheduleDraftsType], OptionType(ArrayType(itemType)), ($, from, to, drafts) => {
484
- const inPlace = $.const(withDrafts);
560
+ const windowOf = (itemType, itemFn, reads) => East.function([DateTimeType, DateTimeType, ScheduleDraftsType], OptionType(ArrayType(itemType)), ($, from, to, drafts) => {
561
+ const inPlace = $.const(reads.withDrafts);
485
562
  const item = $.const(itemFn);
486
- const accepts = filter === undefined ? undefined : $.const(filter);
487
563
  const result = $.let(East.value(none, OptionType(ArrayType(itemType))), OptionType(ArrayType(itemType)));
488
564
  let read;
489
- if (readDays === undefined) {
565
+ if (reads.days === undefined) {
490
566
  read = $.let(East.value(some(readAll()), OptionType(HeldType)));
491
567
  }
492
568
  else {
493
- const days = $.const(readDays);
569
+ const days = $.const(reads.days);
494
570
  read = $.let(days(from, to));
495
571
  }
496
572
  $.match(read, {
497
573
  some: ($2, rows) => {
498
574
  const out = $2.let([], ArrayType(itemType));
499
- $2.for(inPlace(rows, drafts), ($3, entry, _i, label) => {
500
- if (accepts !== undefined)
501
- $3.if(accepts(entry.row, entry.key).not(), $4 => { $4.continue(label); });
575
+ $2.for(inPlace(rows, drafts), ($3, entry) => {
502
576
  // Every item type holds what every view draws, its start and end among them.
503
577
  const one = $3.const(item(entry));
504
578
  $3.match(one.start, {
@@ -520,28 +594,25 @@ export function scheduleEvents(record, input) {
520
594
  });
521
595
  return result;
522
596
  });
523
- /** An item seam over the backlog: the rows with no time, as `itemFn` makes them. */
524
- const backlogOf = (itemType, itemFn) => East.function([ScheduleDraftsType], OptionType(ArrayType(itemType)), ($, drafts) => {
597
+ /** An item seam over the backlog: the rows with no time, as `itemFn` makes them, the window the canvas reads given. */
598
+ const backlogOf = (itemType, itemFn, reads) => East.function([DateTimeType, DateTimeType, ScheduleDraftsType], OptionType(ArrayType(itemType)), ($, from, to, drafts) => {
525
599
  const result = $.let(East.value(some([]), OptionType(ArrayType(itemType))), OptionType(ArrayType(itemType)));
526
600
  if (optional) {
527
- const inPlace = $.const(withDrafts);
601
+ const inPlace = $.const(reads.withDrafts);
528
602
  const item = $.const(itemFn);
529
- const accepts = filter === undefined ? undefined : $.const(filter);
530
603
  let read;
531
- if (readBacklog === undefined) {
604
+ if (reads.backlog === undefined) {
532
605
  read = $.let(East.value(some(readAll()), OptionType(HeldType)));
533
606
  }
534
607
  else {
535
- const backlog = $.const(readBacklog);
536
- read = $.let(backlog());
608
+ const backlog = $.const(reads.backlog);
609
+ read = $.let(backlog(from, to));
537
610
  }
538
611
  $.assign(result, East.value(none, OptionType(ArrayType(itemType))));
539
612
  $.match(read, {
540
613
  some: ($2, rows) => {
541
614
  const out = $2.let([], ArrayType(itemType));
542
- $2.for(inPlace(rows, drafts), ($3, entry, _i, label) => {
543
- if (accepts !== undefined)
544
- $3.if(accepts(entry.row, entry.key).not(), $4 => { $4.continue(label); });
615
+ $2.for(inPlace(rows, drafts), ($3, entry) => {
545
616
  const one = $3.const(item(entry));
546
617
  $3.if(one.start.hasTag("none"), ($4) => { $4(out.pushLast(one)); });
547
618
  });
@@ -551,7 +622,7 @@ export function scheduleEvents(record, input) {
551
622
  }
552
623
  return result;
553
624
  });
554
- const readEvent = (makeItem, readType) => East.function([StringType, ScheduleDraftsType, DateTimeType, DateTimeType], OptionType(readType), ($, id, drafts, from, to) => {
625
+ const readEvent = (makeItem, readType, reads) => East.function([StringType, ScheduleDraftsType, DateTimeType, DateTimeType], OptionType(readType), ($, id, drafts, from, to) => {
555
626
  const parse = $.const(keyOf);
556
627
  const make = $.const(makeItem);
557
628
  const key = $.const(parse(id));
@@ -567,17 +638,17 @@ export function scheduleEvents(record, input) {
567
638
  });
568
639
  });
569
640
  $.if(unread, ($2) => {
570
- if (byKey === undefined || readDays === undefined) {
641
+ if (byKey === undefined || reads.days === undefined) {
571
642
  const held = $2.const(readAll());
572
643
  $2.assign(row, held.tryGet(key));
573
644
  return;
574
645
  }
575
- const days = $2.const(readDays);
646
+ const days = $2.const(reads.days);
576
647
  $2.match(days(from, to), { some: ($3, held) => { $3.assign(row, held.tryGet(key)); } });
577
- if (readBacklog !== undefined) {
578
- const backlog = $2.const(readBacklog);
648
+ if (reads.backlog !== undefined) {
649
+ const backlog = $2.const(reads.backlog);
579
650
  $2.if(row.hasTag("none"), ($3) => {
580
- $3.match(backlog(), { some: ($4, held) => { $4.assign(row, held.tryGet(key)); } });
651
+ $3.match(backlog(from, to), { some: ($4, held) => { $4.assign(row, held.tryGet(key)); } });
581
652
  });
582
653
  }
583
654
  const find = $2.const(byKey);
@@ -590,8 +661,12 @@ export function scheduleEvents(record, input) {
590
661
  none: (_$2) => East.value(none, OptionType(readType)),
591
662
  });
592
663
  });
593
- /** The Calendar's kind, part by part, under its slot — and the function that makes one of its events. */
594
- const kindParts = (slot) => {
664
+ /**
665
+ * The Calendar's kind, part by part, under its slot and through its source's part, its form `form` — and the
666
+ * function that makes one of its events.
667
+ */
668
+ const kindParts = (slot, part, form) => {
669
+ const reads = readsOf(slot, part);
595
670
  // One event, as every view draws it.
596
671
  const itemOf = East.function([EntryRowType], ScheduleItemType, ($, entry) => {
597
672
  const row = entry.row;
@@ -626,7 +701,8 @@ export function scheduleEvents(record, input) {
626
701
  return East.value({
627
702
  kind: slot,
628
703
  key: entry.id,
629
- title: fieldOf(row, config.title),
704
+ // A kind with no title titles its events with nothing (#1367): the Calendar and Plan refuse one.
705
+ title: titleField === undefined ? "" : fieldOf(row, titleField),
630
706
  start,
631
707
  end,
632
708
  resource: ref(row),
@@ -635,8 +711,8 @@ export function scheduleEvents(record, input) {
635
711
  due,
636
712
  }, ScheduleItemType);
637
713
  });
638
- const items = windowOf(ScheduleItemType, itemOf);
639
- const unscheduled = backlogOf(ScheduleItemType, itemOf);
714
+ const items = windowOf(ScheduleItemType, itemOf, reads);
715
+ const unscheduled = backlogOf(ScheduleItemType, itemOf, reads);
640
716
  // Each template's values, by key, as the templates on the wire carry them.
641
717
  const templateRows = boundTemplates ?? East.value(templates.map((t) => ({
642
718
  key: t.key, name: t.name, group: t.group === undefined ? none : some(t.group),
@@ -714,61 +790,9 @@ export function scheduleEvents(record, input) {
714
790
  });
715
791
  });
716
792
  })();
717
- // The shared session over the record: whole-entry drafts, keyed batches, Save through the patch door.
718
- // Its base is the record's snapshot — or, for a kind read a window at a time (#1199), its revision,
719
- // every entry it reads read by key through the record's own entries.
720
- const batchType = EditingChangeSetTypeFor(rowType, keyType);
721
- const encodedEntry = (row) => East.value(some(East.Blob.encodeBeast(row, "v2")), OptionType(BlobType));
722
- const readEntry = byKey === undefined
723
- ? East.function([StringType, IntegerType], OptionType(BlobType), ($, id, _offset) => {
724
- const parse = $.const(keyOf);
725
- const held = $.const(readAll());
726
- return held.tryGet(parse(id)).match({
727
- some: (_$2, row) => encodedEntry(row),
728
- none: (_$2) => East.value(none, OptionType(BlobType)),
729
- });
730
- })
731
- : East.function([StringType, IntegerType], OptionType(BlobType), ($, id, _offset) => {
732
- const find = $.const(byKey);
733
- const result = $.let(none, OptionType(BlobType));
734
- $.match(find(id), { held: ($2, row) => { $2.assign(result, encodedEntry(row)); } });
735
- return result;
736
- });
737
- const onApply = East.asyncFunction([BlobType], EditingApplyResultType, ($, blob) => {
738
- const commit = $.const(apply);
739
- return commit(blob.decodeBeast(batchType, "v2"));
740
- });
741
- const editing = East.value({
742
- sourceId: handle["binding"].name,
743
- entryType: toEastTypeValue(rowType),
744
- idField: none,
745
- draftType: toEastTypeValue(draftType),
746
- children: none,
747
- keyType: some(toEastTypeValue(keyType)),
748
- snapshot: byKey === undefined ? some(East.Blob.encodeBeast(readAll(), "v2")) : none,
749
- readEntry,
750
- onPatch: none,
751
- onApply: some(East.value(variant("async", onApply), EditingWireApplyType)),
752
- mode: variant("batch", null),
753
- }, EditingType);
754
- // The record read by key (#1199): its revision and a move to another, one event's row, and its largest key.
755
- let entriesSeam;
756
- if (entries === undefined || byKey === undefined || lastKey === undefined) {
757
- entriesSeam = East.value(none, OptionType(ScheduleEntriesType));
758
- }
759
- else {
760
- const pinned = entries;
761
- const entry = East.function([StringType], OptionType(OptionType(BlobType)), ($, id) => {
762
- const find = $.const(byKey);
763
- const result = $.let(none, OptionType(OptionType(BlobType)));
764
- $.match(find(id), {
765
- held: ($2, row) => { $2.assign(result, some(encodedEntry(row))); },
766
- absent: ($2) => { $2.assign(result, some(none)); },
767
- });
768
- return result;
769
- });
770
- entriesSeam = East.value(some({ revision: pinned.revision, refresh: pinned.refresh, entry, last: lastKey }), OptionType(ScheduleEntriesType));
771
- }
793
+ // The shared session over the record (`session.ts`): whole-entry drafts, keyed batches, Save through the
794
+ // patch door; its base the record's snapshot, or for a kind read a window at a time (#1199) its revision.
795
+ const { editing, entries: entriesSeam } = session.build();
772
796
  const fieldsOfKind = {
773
797
  key: slot,
774
798
  name: config.name,
@@ -777,22 +801,76 @@ export function scheduleEvents(record, input) {
777
801
  status: statusTable === undefined || statusCases === undefined ? [] : statusCases.map((c) => ({ case: c, status: fieldOf(statusTable, c) })),
778
802
  backlog: optional,
779
803
  templates: templateRows,
780
- fields: East.value(formSpecs, ArrayType(FieldSpecType)),
804
+ templatesSource: templatesSource === undefined ? none : some(templatesSource),
805
+ fields: East.value(form, ArrayType(FieldSpecType)),
781
806
  items,
782
807
  unscheduled,
783
808
  write,
784
809
  ready: ready === undefined ? none : some(ready),
785
810
  editing,
786
811
  entries: entriesSeam,
787
- history: handle["history"],
788
- event: readEvent(itemOf, ScheduleReadType),
789
- schedule: { title: config.title, start: startField, end: endField,
812
+ history: session.history,
813
+ event: readEvent(itemOf, ScheduleReadType, reads),
814
+ // A kind with no title names none: the Calendar and Plan, whose inspectors draw it, refuse one.
815
+ schedule: { title: titleField ?? "", start: startField, end: endField,
790
816
  resource: resource === undefined ? none : some(resource.field),
791
817
  status: config.status === undefined ? none : some(config.status.field) },
792
818
  overlaps: variant(overlaps, null),
793
819
  inspector: inspector === undefined ? none : some(inspector),
794
820
  };
795
- return { fields: fieldsOfKind, itemOf };
821
+ return { fields: fieldsOfKind, itemOf, reads };
822
+ };
823
+ // ── The builders over the app's own records (#1367) ─────────────────────
824
+ /**
825
+ * The stored rows overlapping `[from, to)`, by their key's text: each its roles, read by `rolesOf` in the pass that
826
+ * reads the row, and the row as bytes — through the kind's reads (its source's part, its day index, or the record
827
+ * whole), `none` while a read is in flight. Overlap is the views' rule: a row starts before the window ends and
828
+ * ends after it starts, or, of no length, sits inside it. The builder puts its drafts in place itself (`Builder
829
+ * Interfaces Spec.md` §9). The times are plain DateTime fields: both builds refuse Option times.
830
+ */
831
+ const storedRows = (reads, EntryType, rolesOf) => {
832
+ const RowsType = OptionType(DictType(StringType, EntryType));
833
+ return East.function([DateTimeType, DateTimeType], RowsType, ($, from, to) => {
834
+ const text = $.const(idOf);
835
+ const roles = $.const(rolesOf);
836
+ const result = $.let(East.value(none, RowsType), RowsType);
837
+ let read;
838
+ if (reads.days === undefined) {
839
+ read = $.let(East.value(some(readAll()), OptionType(HeldType)));
840
+ }
841
+ else {
842
+ const days = $.const(reads.days);
843
+ read = $.let(days(from, to));
844
+ }
845
+ $.match(read, {
846
+ some: ($2, held) => {
847
+ const out = $2.let(new Map(), DictType(StringType, EntryType));
848
+ $2.for(held, ($3, row, key) => {
849
+ const start = $3.const(fieldOf(row, startField));
850
+ const end = $3.const(fieldOf(row, endField));
851
+ $3.if(East.less(start, to).and(() => East.greater(end, from).or(() => East.greaterEqual(start, from))), ($4) => {
852
+ const id = $4.const(text(key));
853
+ $4(out.insert(id, East.value({ roles: roles(row, id), row: East.Blob.encodeBeast(row, "v2") }, EntryType)));
854
+ });
855
+ });
856
+ $2.assign(result, East.value(some(out), RowsType));
857
+ },
858
+ });
859
+ return result;
860
+ });
861
+ };
862
+ /** Refuses two of a builder's roles naming one field: a builder writes each of its roles into a field of its own (B1). */
863
+ const oneFieldEach = (builder, named) => {
864
+ const seen = new Map();
865
+ for (const [prop, name] of named) {
866
+ if (name === undefined)
867
+ continue;
868
+ const prior = seen.get(name);
869
+ if (prior !== undefined) {
870
+ throw new Error(`${where}: \`${prior}\` and \`${prop}\` both name "${name}" — the ${builder} writes each of its roles into a field of its own`);
871
+ }
872
+ seen.set(name, prop);
873
+ }
796
874
  };
797
875
  return {
798
876
  [SCHEDULE_DEF]: "events",
@@ -800,13 +878,195 @@ export function scheduleEvents(record, input) {
800
878
  rowType,
801
879
  name: config.name,
802
880
  icon: config.icon,
881
+ title: titleField,
803
882
  takes,
804
883
  draw: draw,
805
- build(slot) {
806
- return East.value(kindParts(slot).fields, ScheduleKindType);
884
+ templatesSource,
885
+ templatesList: config.templates === undefined ? "none" : SCHEDULE_TEMPLATES in config.templates ? "record" : "code",
886
+ roles: [...rosterGiven, ...allocationGiven],
887
+ partsOf: config.parts?.of,
888
+ allocationValues,
889
+ build(slot, part) {
890
+ return East.value(kindParts(slot, part, formSpecs).fields, ScheduleKindType);
891
+ },
892
+ buildRoster(slot, part) {
893
+ // A duty runs from its start to its end, keyed by String, on its slot's shift and group, rostered to someone.
894
+ if (instantKind) {
895
+ throw new Error(`${where}: the Roster reads a duty's \`start\` and \`end\`; this kind is one instant (\`at\`) — give \`start\` and \`end\` in its place`);
896
+ }
897
+ if (optional)
898
+ throw new Error(`${where}: a duty has its times — \`start\` and \`end\` are DateTime fields, not Options`);
899
+ if (keyType.type !== "String") {
900
+ throw new Error(`${where}: the Roster's duties are keyed by String — a duty is named by its key, and this record is keyed by ${printType(keyType)}`);
901
+ }
902
+ if (shiftField === undefined || groupField === undefined) {
903
+ throw new Error(`${where}: the Roster places a duty by its \`shift\` and its \`group\` — give both`);
904
+ }
905
+ if (resource === undefined || resourceValue === undefined) {
906
+ throw new Error(`${where}: the Roster rosters each duty to a person or an agency, its \`resource\` — give it, resource: { field, of: "people" }`);
907
+ }
908
+ oneFieldEach("Roster", [["start", startField], ["end", endField], ["resource.field", resource.field], ["shift", shiftField],
909
+ ["group", groupField], ["position", positionField], ["activity", activityField]]);
910
+ const shift = shiftField;
911
+ const group = groupField;
912
+ const who = resource.field;
913
+ const whoOf = resourceValue;
914
+ const parts = kindParts(slot, part, formOver([startField, endField, who, shift, group, positionField, activityField]));
915
+ const Roles = ScheduleRosterRolesType;
916
+ // A row with the Roster's roles written from `roles`, the resource field from `value`, and every other field
917
+ // from `source`: the row as it was, or a new row's defaults. A kind with no `position` writes none.
918
+ const rosterRow = (source, roles, value) => East.value(Object.fromEntries(Object.keys(fields).map((f) => [f,
919
+ f === startField ? roles.start
920
+ : f === endField ? roles.end
921
+ : f === who ? value
922
+ : f === shift ? roles.shift
923
+ : f === group ? roles.group
924
+ : f === positionField ? roles.position.match({ some: (_$, held) => held, none: () => fieldOf(source, f) })
925
+ : f === activityField ? roles.activity
926
+ : fieldOf(source, f)])), rowType);
927
+ // A duty's roles, from its row and its key: its times and who, its shift and group, its position and
928
+ // activity, and the hours it counts.
929
+ const rolesOf = East.function([rowType, StringType], Roles, ($, row, key) => {
930
+ const ref = $.const(refOf);
931
+ const start = $.const(fieldOf(row, startField));
932
+ const end = $.const(fieldOf(row, endField));
933
+ let hours;
934
+ if (hoursOf === undefined) {
935
+ // Its length.
936
+ hours = $.const(start.durationMinutes(end).divide(60.0));
937
+ }
938
+ else {
939
+ const count = $.const(hoursOf);
940
+ hours = $.const(count(row, key));
941
+ }
942
+ return East.value({
943
+ start, end, resource: ref(row), shift: fieldOf(row, shift), group: fieldOf(row, group),
944
+ position: positionField === undefined ? none : some(fieldOf(row, positionField)),
945
+ activity: activityField === undefined ? none : fieldOf(row, activityField),
946
+ hours,
947
+ }, Roles);
948
+ });
949
+ // A drafted duty's roles, from its bytes: what `rows` gives a stored one in the pass that reads it.
950
+ const read = East.function([BlobType, StringType], Roles, ($, bytes, key) => {
951
+ const of = $.const(rolesOf);
952
+ return of(bytes.decodeBeast(rowType, "v2"), key);
953
+ });
954
+ // The row with its roles written, and every other field as it was (B1); `none` for a resource the kind refuses.
955
+ const write = East.function([BlobType, Roles], OptionType(BlobType), ($, bytes, roles) => {
956
+ const row = $.const(bytes.decodeBeast(rowType, "v2"));
957
+ const resolve = $.const(whoOf);
958
+ const result = $.let(East.value(none, OptionType(BlobType)), OptionType(BlobType));
959
+ $.match(resolve(roles.resource), {
960
+ some: ($2, value) => { $2.assign(result, East.value(some(East.Blob.encodeBeast(rosterRow(row, roles, value), "v2")), OptionType(BlobType))); },
961
+ });
962
+ return result;
963
+ });
964
+ // A new duty: its roles written, and every other field its type's default.
965
+ const blank = East.value(defaultValue(rowType), rowType);
966
+ const create = East.function([Roles], OptionType(BlobType), ($, roles) => {
967
+ const row = $.const(blank);
968
+ const resolve = $.const(whoOf);
969
+ const result = $.let(East.value(none, OptionType(BlobType)), OptionType(BlobType));
970
+ $.match(resolve(roles.resource), {
971
+ some: ($2, value) => { $2.assign(result, East.value(some(East.Blob.encodeBeast(rosterRow(row, roles, value), "v2")), OptionType(BlobType))); },
972
+ });
973
+ return result;
974
+ });
975
+ return East.value({
976
+ kind: East.value(parts.fields, ScheduleKindType),
977
+ rows: storedRows(parts.reads, ScheduleRosterEntryType, rolesOf),
978
+ read,
979
+ write,
980
+ create,
981
+ roles: { position: positionField !== undefined, activity: activityField !== undefined },
982
+ rowType: toEastTypeValue(rowType),
983
+ }, ScheduleRosterKindType);
984
+ },
985
+ buildAllocation(slot, part) {
986
+ // A bin is one instant, keyed by String, in one lane, booking an arrival, its quantity on its parts for its hold.
987
+ if (!instantKind) {
988
+ throw new Error(`${where}: the Allocation's bins are an instant kind: give \`at\` in place of \`start\` and \`end\``);
989
+ }
990
+ if (keyType.type !== "String") {
991
+ throw new Error(`${where}: the Allocation's bins are keyed by String — a bin is named by its key, and this record is keyed by ${printType(keyType)}`);
992
+ }
993
+ if (resource === undefined)
994
+ throw new Error(`${where}: the Allocation books a bin into a lane, its \`resource\` — give it`);
995
+ if (resource.shape !== "key" || resource.optional) {
996
+ throw new Error(`${where}: the Allocation books each bin into one lane — \`resource\` names a String field, a key of the lanes' kind, ` +
997
+ `and "${resource.field}" holds ${typeText(fields[resource.field])}`);
998
+ }
999
+ if (arrivalField === undefined)
1000
+ throw new Error(`${where}: the Allocation books a bin's \`arrival\` — give it`);
1001
+ if (quantityField === undefined)
1002
+ throw new Error(`${where}: the Allocation reads a bin's \`quantity\` — give it`);
1003
+ if (partsField === undefined || config.parts === undefined)
1004
+ throw new Error(`${where}: the Allocation allocates a bin's \`parts\` — give it`);
1005
+ if (holdField === undefined)
1006
+ throw new Error(`${where}: the Allocation holds a bin for its \`hold\` — give it`);
1007
+ oneFieldEach("Allocation", [["at", startField], ["resource.field", resource.field], ["arrival", arrivalField], ["quantity.field", quantityField],
1008
+ ["parts.field", partsField], ["hold", holdField], ["kind", kindField]]);
1009
+ const lane = resource.field;
1010
+ const arrival = arrivalField;
1011
+ const quantity = quantityField;
1012
+ const shares = partsField;
1013
+ const hold = holdField;
1014
+ // The fields no role maps (`allocationValues`): what an arrival's values give a booked bin, a reservation's their type's default.
1015
+ const mapped = allocationMapped;
1016
+ const ValuesType = allocationValues;
1017
+ const parts = kindParts(slot, part, formOver([...mapped]));
1018
+ const Roles = ScheduleAllocationRolesType;
1019
+ // A row with the Allocation's roles written from `roles`, and every other field from `source`: the row as it
1020
+ // was, or a booked bin's values. A kind with no `kind` writes none.
1021
+ const binRow = (source, roles) => East.value(Object.fromEntries(Object.keys(fields).map((f) => [f,
1022
+ f === startField ? roles.at
1023
+ : f === lane ? roles.lane
1024
+ : f === arrival ? roles.arrival
1025
+ : f === quantity ? roles.quantity
1026
+ : f === shares ? roles.parts
1027
+ : f === hold ? roles.hold
1028
+ : f === kindField ? roles.kind
1029
+ : fieldOf(source, f)])), rowType);
1030
+ // A bin's roles, from its row: its time and lane, the arrival it books, its quantity and parts, its hold and kind.
1031
+ const rolesOf = East.function([rowType, StringType], Roles, (_$, row, _key) => East.value({
1032
+ at: fieldOf(row, startField), lane: fieldOf(row, lane), arrival: fieldOf(row, arrival), quantity: fieldOf(row, quantity),
1033
+ parts: fieldOf(row, shares), hold: fieldOf(row, hold), kind: kindField === undefined ? none : fieldOf(row, kindField),
1034
+ }, Roles));
1035
+ // A drafted bin's roles, from its bytes: what `rows` gives a stored one in the pass that reads it.
1036
+ const read = East.function([BlobType], Roles, ($, bytes) => {
1037
+ const of = $.const(rolesOf);
1038
+ return of(bytes.decodeBeast(rowType, "v2"), "");
1039
+ });
1040
+ // The row with its roles written, and every other field as it was (B1).
1041
+ const write = East.function([BlobType, Roles], BlobType, ($, bytes, roles) => {
1042
+ const row = $.const(bytes.decodeBeast(rowType, "v2"));
1043
+ return East.Blob.encodeBeast(binRow(row, roles), "v2");
1044
+ });
1045
+ // A new bin: its roles written, and every other field the arrival's values' — or, with none, its type's default (B13).
1046
+ const blank = East.value(defaultValue(ValuesType), ValuesType);
1047
+ const create = East.function([Roles, OptionType(BlobType)], BlobType, ($, roles, values) => {
1048
+ const given = $.const(values.match({
1049
+ some: (_$2, bytes) => bytes.decodeBeast(ValuesType, "v2"),
1050
+ none: (_$2) => blank,
1051
+ }));
1052
+ return East.Blob.encodeBeast(binRow(given, roles), "v2");
1053
+ });
1054
+ return East.value({
1055
+ kind: East.value(parts.fields, ScheduleKindType),
1056
+ rows: storedRows(parts.reads, ScheduleAllocationEntryType, rolesOf),
1057
+ read,
1058
+ write,
1059
+ create,
1060
+ lanes: resource.of,
1061
+ parts: config.parts.of,
1062
+ kinded: kindField !== undefined,
1063
+ unit: config.quantity?.unit === undefined ? none : some(config.quantity.unit),
1064
+ values: toEastTypeValue(ValuesType),
1065
+ rowType: toEastTypeValue(rowType),
1066
+ }, ScheduleAllocationKindType);
807
1067
  },
808
- buildPlan(slot) {
809
- const parts = kindParts(slot);
1068
+ buildPlan(slot, part) {
1069
+ const parts = kindParts(slot, part, formSpecs);
810
1070
  const quantity = config.quantity;
811
1071
  const format = quantity?.format === undefined ? undefined : East.value(quantity.format, TickFormatType);
812
1072
  // One event as Plan draws it: what every view draws, with its roles read from their fields.
@@ -832,7 +1092,7 @@ export function scheduleEvents(record, input) {
832
1092
  // is the record's: read whole, or for a kind read a window at a time (#1199) looked for first among the
833
1093
  // rows its windows hold over [from, to) and its backlog's, where a gesture's event is drawn, then read
834
1094
  // by its key.
835
- const planEvent = readEvent(planItemOf, PlanEventReadType);
1095
+ const planEvent = readEvent(planItemOf, PlanEventReadType, parts.reads);
836
1096
  const name = (field) => (field === undefined ? none : some(field));
837
1097
  return East.value({
838
1098
  ...parts.fields,
@@ -840,8 +1100,8 @@ export function scheduleEvents(record, input) {
840
1100
  instant: instantKind,
841
1101
  overlaps: variant(overlaps, null),
842
1102
  roles: { state: name(stateField), quantity: name(quantityField), lane: name(laneField) },
843
- planItems: windowOf(PlanEventItemType, planItemOf),
844
- planUnscheduled: backlogOf(PlanEventItemType, planItemOf),
1103
+ planItems: windowOf(PlanEventItemType, planItemOf, parts.reads),
1104
+ planUnscheduled: backlogOf(PlanEventItemType, planItemOf, parts.reads),
845
1105
  planEvent,
846
1106
  inspector: inspector === undefined ? East.value(none, OptionType(RowInspectorType)) : East.value(some(inspector), OptionType(RowInspectorType)),
847
1107
  }, PlanEventKindType);