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