@elaraai/east-claude-plugin 1.0.98 → 1.0.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build/daemon/lsp.js +40 -3
- package/.build/daemon/server.js +40 -3
- package/.claude-plugin/plugin.json +1 -1
- package/index.json +1108 -241
- package/package.json +1 -1
- package/skills/e3/SKILL.md +339 -135
- package/skills/e3-ui/SKILL.md +426 -259
- package/skills/east/SKILL.md +69 -2
- package/skills/east-contribute/SKILL.md +0 -1
- package/skills/east-py/SKILL.md +53 -11
- package/skills/east-ui/SKILL.md +5 -5
package/skills/e3-ui/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: e3-ui
|
|
3
|
-
description: "e3 + UI bridge — build interactive, reactive decision surfaces as e3 tasks, authored as JSX. Use when: (1) Declaring UI tasks with ui() (e3 tasks of kind 'ui' producing a UIComponentType), (2) Binding reactive workspace data with Data.bind (read/write/has/commit/discard/status against e3.input / task defs) inside a <Reactive>{$ => …}</Reactive> block, (3) Staged vs direct edit modes and reviewing pending changes with the <Diff> tag, (4) Graph/ontology editing with the <Ontology> tag, (5) Calling named package functions (e3.function) RPC-style with Func.bind (call/read/status/error/pending/cancel), (6) Wiring a manifest (reads/writes + bound functions auto-derived from a UI task's IR), (7) Interactive causal-experiment surfaces ('did X change Y?') with the <Experiment> tag, generic over a bound dataset's row and driven by e3.function estimators, (8) The Decide loop — Decision.bind unions reasoning-task decision outputs into one handle (shared selection + commit gate), <DecisionQueue> (urgency-sorted queue with evidence/options/judgement/modify facets, Apply/Reject, grouping, an author-bound Slice scope) and <DecisionJournal> (the resolved read-back), (9) The Studio — Studio.component declares a self-contained East UI function (written exactly like a ui() body) with what the palette shows; the pages operators build are one record of Studio.Types.Pages with one patch write; <Studio.Builder> is the builder — the open page's canvas under one toolbar (its status, the history, Desktop · Tablet, Save as template, Preview and Publish), the palette of the listed components and the project's pages before it, the inspector of the selected placement after it, and the publish preview in its place — every gesture a draft, Save one patch on the page, and a publish stamping each placement with the code it goes live with; <Studio.Library> is a project's templates and pages and where new pages start, opening pages in the builder; <Studio.Page> draws one page's live or draft layout with no chrome, (10) Rendering deployed surfaces in a React app with @elaraai/e3-ui-components — <E3Provider> (E3Config: apiUrl, repo, workspace, token, fetch), <ReactiveDatasetProvider> and <UITaskPreview> — over a server, or over an e3 running in the page (e3-web's createWebE3 and its e3.fetch), (11) Queries — operators build typed jq queries over the datasets a surface binds and save them to one record of Query.Types.Saved with one patch write; <Query.Builder> edits the open query as plain-word steps or as jq, checks it as it is edited, runs it on e3 as a one-shot call — or as a split call over the pieces of a dataset larger than one piece, its plan explained — and shows the result as a Table or a tree; <Query.Library> is the saved queries as a gallery of wireframes and where new queries start, opening them in the builder, (12) The Plan — <Plan>, the one planning canvas, always in its builder frame — one toolbar holding every control it has, the banners, the canvas, the footer's counts: heterogeneous rows on ONE shared { time | number | ordinal } axis from three sources — event kinds over records (Schedule.events placed on Schedule.resources, the kinds a Calendar takes), rows laid out by a series list over local data or a Data.bindPaged source and nested from the data's own structure, and read-only rows (Plan.over a dataset, hand-built rows) — its panes optional props — a library (every event kind's templates, the backlog by when it is due, the series a viewer shows and hides, and cards of the author's own whose drop sets a Schedule.patch) and an inspector of what is selected (one event, several, a row at a bucket, or the window's counts; an event kind's own inspector in place of its form) — with one Slice.bindRecords rail (its rows, each event kind, resource kind and templates record a source, one window; an event kind on paged resources read a cell at a time through a series index and shaded from a summary index zoomed out), key search, links between runs or events, and an editing session — dropped cards and moved or resized runs, chips, tiles and marks as drafts with Undo / Redo and one checked Save — its authoring vocabulary on `Plan` (Plan.axis, Plan.series.*, Plan.over, Plan.eventRef, Plan.library.*, the value builders), (13) The Sheet — <Sheet>, the one planning spreadsheet, always in its builder frame — one toolbar holding every control it has, the banners, the grid with its docked strip, the footer — its panes optional props: a library (templates, columns, the author's cards) and an inspector of the selected row's every field or the author's own; its rows from an e3 record bound with its patch door (the record's entries, one entry's rows or groups with loose rows between them, or a large record a window at a time — Save one patch commit through the record) or the host's (an array, a bind handle, a Data.bindPaged source with a key search), or the `rows` source of its one Slice.bindRecords rail — the record's rows, the source's narrowing drawn over them as the lens (a filtered spreadsheet: only the rows it keeps, one after another, each at its row number, e3 placing them from the index) — the rail narrowing its templates records too; typed columns (dates, quantities with units, register lookups, a directed link between register members, stamped codes), a blank tail that invites the next row, an East-function copilot that fills cells and proposes rows, saved-view tabs over every source's narrowing, groups with loose rows between them and read-only sub rows, drag and drop, Excel round-trip, and an editing session — drafts checked as they are made, Undo / Redo and one checked Save — its authoring vocabulary on `Sheet` (Sheet.column.*, Sheet.register.*, Sheet.driver, Sheet.group, Sheet.subRows, Sheet.library.*, Sheet.field, Sheet.apply). (14) Calendar — the record-backed scheduling builder in e3-ui: Calendar, Resources and Timeline layouts, bound resource and template records, the event record’s unscheduled backlog paged through its source, one Slice.bindRecords rail — a source per event kind, resource kind and templates record, one date window — a resource kind paged a window at a time (Data.bindPaged), the events on it read a cell at a time through a series index and shaded from a summary index past 10,000, shared Toolbar/Library/FieldForm/EditSession behavior, and explicit agenda/card actions below 480px. BuilderFrame remains internal. (15) Roster — weekly staffing over the app's own records: its duties a Schedule.events kind (start, end, resource, shift, group, position?, activity?, hours?), its people and agencies Schedule.resources kinds, its weeks Schedule.periods for Publish, its cover Roster.targets, and its shifts, groups, positions, activities and proposals read through Roster.* accessors; Shifts (Day · Week) and People (Week · Month) layouts — the People week a tile a duty in the shift's colour token (Roster.shifts' color, east-ui's ColorTokenType), the month a column a week, and the app's week patterns (Roster.patterns, Roster.library.patterns()) dropped on a person's week as one step; pure coverage/rules over the duty row type; the app's pay in its own codes, asked a person at a time; Find fixes on the app's fit e3.function (Func.bind, Roster.fit(R)); the week's duties read through the slice's duty source; one Slice.bindRecords rail over the duties, targets, people and activities; one history over a session per record, Save per record and Publish saving first, then the week's status; and explicit mobile actions. (16) The Flowchart — <Flowchart>, the state-transition flowchart, always in its builder frame — one toolbar (find state, LR · TD, the freshness chip, the rail of its one Slice.bindRecords, and where it edits the history item), the canvas filling main, the footer's counts: states as nodes in ordered phase lanes (the layout derived, no coordinates), H/V-routed transitions, decision triggers and evidence-weighted links, over an e3 record of flows by name (Flowchart.Types.Flows, its value written with Flowchart.values and checked at build — a lone flow is a record of one entry) or the host's flows or one flow (data, the value's type picking the arm — Flowchart.over builds one flow from the host's own tables with row mappers); its library pane's Flows tab (Flowchart.library.flows()) lists the flows by name — over a record those its rail's `flows` source narrows it to, counted — and opens one, the open flow kept under the flowchart's name, and '+ New flow' starts one, beside state and transition templates, templates records (Flowchart.library.states / transitions), and the author's own cards (Flowchart.library.tab), each following the source of the rail it names, and each card dropped on the canvas — a state template onto a lane, a transition template onto a transition, an author's card where its drop's type says, ⏎ on a card onto the selection; every gesture on the canvas — '+ LANE', a lane renamed (a double-click on its header) or deleted, a state added, edited, moved or deleted, a handle dragged to connect, Del on the selection — one transaction of the open flow's editing session, each flow keeping its own drafts, with Undo / Redo and one checked Save through the record's patch mutation or the host's onApply; its inspector pane, on by default (inspector={false} takes it away) — Details for the state, transition, decision, lane (a click on its header) or several states selected, or the open flow, each through the shared form, a state's or a transition's own Details in its place with inspector={{ state, transition }}, and Issues, each selecting what it names; with selection and path tracing — its types on Flowchart.Types, a row patch from Flowchart.patch. (17) The Allocation — <Allocation>, a day's arrivals booked into timed lane slots and each bin's quantity allocated to resources with room and time for its hold, always in its builder frame, as a Timeline (lanes, links, resource rows) or a Table (a row per slot), over the app's own records: its bins an instant Schedule.events kind (at, resource, arrival, quantity, parts, hold, kind?), its lanes and resources Schedule.resources kinds (offset; min, max, note, hold, jobs), what it books from Allocation.arrivals (each arrival's values a booked bin's own fields), and its days Schedule.periods for Publish — one history over the bins' and the days' sessions, every gesture a draft, Save per record and Publish saving first, then the day's status — the app's fit an e3.function bound with Func.bind (a place or a take question about the drafted day, FitRequest(R) over the bin row type, built on Allocation.answer / fit / lookahead), split loads and Find a fit, the app's bin fields as columns and its own check over CheckContext(R), one Slice.bindRecords rail over the day's bins, its lanes, resources and arrivals, the shared Library, Inspector and session, explicit-action cards below 480px, and pure loads / candidates / check / slots over Day(R) a task runs too. (18) The Schematic — <Schematic>, the 2D world-coordinate drawing of the app's own records: a kind per record, keyed by String, its fields mapped to the Schematic's roles and its shapes made by accessors over the row's own fields — items (Schematic.items: a centre, a box, a footprint drawn about the centre, a label, a status, a meter, a figure), zones (Schematic.zones: a box, outlined or hatched, a geometry about its centre), links between two items (Schematic.links: from and to naming item slots) and nets joining many to many (Schematic.nets: sources and destinations) — each kind editable bound with its patch door (Record.bind) or read only (Data.bind), read whole or complete through its source of one Slice.bindRecords rail (entries: Data.bindPaged) — a window at a time where that source lists an area index (e3.recordIndex.area), its density shaded past 5,000 rows in view — its details a record keyed as it is (Schematic.details), its templates a record (Schematic.templates), its layers the app's rows (Schematic.layers), the library's tabs over every kind (Schematic.library.templates() / backlog() / index() / layers() / tab), each card dropped on the canvas — an item template adds an item at the point, a backlog row is placed there, a link or net template gives a link or a net of its kind its values, an author's card sets its Schematic.patch's fields, ⏎ onto the selection or at the view's centre — the inspector opted into: Details through the shared form over each kind's fields and its details', an item's position, a link's ends and a net's sides, and Issues, each selecting what it names; below 480px of main nothing drags, Details' Connect to… and the Backlog card's Place taking the gestures' place; a camera with a minimap, selection by a tap, Shift and the marquee, and every gesture — Move, Connect and a Shift session growing a net, an end retargeted, a net extended, Del — one step of one history over a session per record it writes, with Undo / Redo and Save sending each record's changes through its patch door, canConnect answering each pair with none or the refusal's words; onSelect hears the whole selection, each entity by its kind's slot and its key — its shapes and types on Schematic (Schematic.circle / polyline / polygon / rect about the anchor, outline / hatch, solid / dashed, Schematic.patch, Schematic.Types)."
|
|
3
|
+
description: "e3 + UI bridge — build interactive, reactive decision surfaces as e3 tasks, authored as JSX. Use when: (1) Declaring UI tasks with ui() (e3 tasks of kind 'ui' producing a UIComponentType), (2) Binding reactive workspace data with Data.bind (read/write/has/commit/discard/status against e3.input / task defs) inside a <Reactive>{$ => …}</Reactive> block, (3) Staged vs direct edit modes and reviewing pending changes with the <Diff> tag, (4) Graph/ontology editing with the <Ontology> tag, (5) Calling named package functions (e3.function) RPC-style with Func.bind (call/read/status/error/pending/cancel), (6) Wiring a manifest (reads/writes + bound functions auto-derived from a UI task's IR), (7) Interactive causal-experiment surfaces ('did X change Y?') with the <Experiment> tag, generic over a bound dataset's row and driven by e3.function estimators, (8) The Decide loop — Decision.bind unions reasoning-task decision outputs into one handle (shared selection + commit gate), <DecisionQueue> (urgency-sorted queue with evidence/options/judgement/modify facets, Apply/Reject, grouping, an author-bound Slice scope) and <DecisionJournal> (the resolved read-back), (9) The Studio — Studio.component declares a self-contained East UI function (written exactly like a ui() body) with what the palette shows; the pages operators build are one record of Studio.Types.Pages with one patch write; <Studio.Builder> is the builder — the open page's canvas under one toolbar (its status, the history, Desktop · Tablet, Save as template, Preview and Publish), the palette of the listed components and the project's pages before it, the inspector of the selected placement after it, and the publish preview in its place — every gesture a draft, Save one patch on the page, and Publish one patch making the draft its next live version; <Studio.Library> is a project's templates and pages and where new pages start, opening pages in the builder; <Studio.Page> draws one page's live or draft layout with no chrome, (10) Rendering deployed surfaces in a React app with @elaraai/e3-ui-components — <E3Provider> (E3Config: apiUrl, repo, workspace, token, fetch), <ReactiveDatasetProvider> and <UITaskPreview> — over a server, or over an e3 running in the page (e3-web's createWebE3 and its e3.fetch), (11) Queries — operators build typed jq queries over the datasets a surface binds and save them to one record of Query.Types.Saved with one patch write; <Query.Builder> edits the open query as plain-word steps or as jq, checks it as it is edited, runs it on e3 as a one-shot call — or as a split call over the pieces of a dataset larger than one piece, its plan explained — and shows the result as a Table or a tree; <Query.Library> is the saved queries as a gallery of wireframes and where new queries start, opening them in the builder, (12) The Plan — <Plan>, the one planning canvas, always in its builder frame — one toolbar holding every control it has, the banners, the canvas, the footer's counts: heterogeneous rows on ONE shared { time | number | ordinal } axis from three sources — event kinds over records (Schedule.events placed on Schedule.resources, the kinds a Calendar takes), rows laid out by a series list over local data or a Data.bindPaged source and nested from the data's own structure, and read-only rows (Plan.over a dataset, hand-built rows) — its panes optional props — a library (every event kind's templates, the backlog by when it is due, the series a viewer shows and hides, and cards of the author's own whose drop sets a Schedule.patch) and an inspector of what is selected (one event, several, a row at a bucket, or the window's counts; an event kind's own inspector in place of its form) — with one Slice.bindRecords rail (its rows, each event kind, resource kind and templates record a source, one window; an event kind on paged resources read a cell at a time through a series index and shaded from a summary index zoomed out), key search, links between runs or events, and an editing session — dropped cards and moved or resized runs, chips, tiles and marks as drafts with Undo / Redo and one checked Save — its authoring vocabulary on `Plan` (Plan.axis, Plan.series.*, Plan.over, Plan.eventRef, Plan.library.*, the value builders), (13) The Sheet — <Sheet>, the one planning spreadsheet, always in its builder frame — one toolbar holding every control it has, the banners, the grid with its docked strip, the footer — its panes optional props: a library (templates, columns, the author's cards) and an inspector of the selected row's every field or the author's own; its rows from an e3 record bound with its patch door (the record's entries, one entry's rows or groups with loose rows between them, or a large record a window at a time — Save one patch commit through the record) or the host's (an array, a bind handle, a Data.bindPaged source with a key search), or the `rows` source of its one Slice.bindRecords rail — the record's rows, the source's narrowing drawn over them as the lens (a filtered spreadsheet: only the rows it keeps, one after another, each at its row number, e3 placing them from the index) — the rail narrowing its templates records too; typed columns (dates, quantities with units, register lookups, a directed link between register members, stamped codes), a blank tail that invites the next row, an East-function copilot that fills cells and proposes rows, saved-view tabs over every source's narrowing, groups with loose rows between them and read-only sub rows, drag and drop, Excel round-trip, and an editing session — drafts checked as they are made, Undo / Redo and one checked Save — its authoring vocabulary on `Sheet` (Sheet.column.*, Sheet.register.*, Sheet.driver, Sheet.group, Sheet.subRows, Sheet.library.*, Sheet.field, Sheet.apply). (14) Calendar — the record-backed scheduling builder in e3-ui: Calendar, Resources and Timeline layouts, bound resource and template records, the event record’s unscheduled backlog paged through its source, one Slice.bindRecords rail — a source per event kind, resource kind and templates record, one date window — a resource kind paged a window at a time (Data.bindPaged), the events on it read a cell at a time through a series index and shaded from a summary index past 10,000, shared Toolbar/Library/FieldForm/EditSession behavior, and explicit agenda/card actions below 480px. BuilderFrame remains internal. (15) Roster — weekly staffing over the app's own records: its duties a Schedule.events kind (start, end, resource, shift, group, position?, activity?, hours?), its people and agencies Schedule.resources kinds, its weeks Schedule.periods for Publish, its cover Roster.targets, and its shifts, groups, positions, activities and proposals read through Roster.* accessors; Shifts (Day · Week) and People (Week · Month) layouts — the People week a tile a duty in the shift's colour token (Roster.shifts' color, east-ui's ColorTokenType), the month a column a week, and the app's week patterns (Roster.patterns, Roster.library.patterns()) dropped on a person's week as one step; pure coverage/rules over the duty row type; the app's pay in its own codes, asked a person at a time; Find fixes on the app's fit e3.function (Func.bind, Roster.fit(R)); the week's duties read through the slice's duty source; one Slice.bindRecords rail over the duties, targets, people and activities; one history over a session per record, Save per record and Publish saving first, then the week's status; and explicit mobile actions. (16) The Flowchart — <Flowchart>, the state-transition flowchart, always in its builder frame — one toolbar (find state, LR · TD, the freshness chip, the rail of its one Slice.bindRecords, and where it edits the history item), the canvas filling main, the footer's counts: states as nodes in ordered phase lanes (the layout derived, no coordinates), H/V-routed transitions, decision triggers and evidence-weighted links, over an e3 record of flows by name (Flowchart.Types.Flows, its value written with Flowchart.values and checked at build — a lone flow is a record of one entry) or the host's flows or one flow (data, the value's type picking the arm — Flowchart.over builds one flow from the host's own tables with row mappers); its library pane's Flows tab (Flowchart.library.flows()) lists the flows by name — over a record those its rail's `flows` source narrows it to, counted — and opens one, the open flow kept under the flowchart's name, and '+ New flow' starts one, beside state and transition templates, templates records (Flowchart.library.states / transitions), and the author's own cards (Flowchart.library.tab), each following the source of the rail it names, and each card dropped on the canvas — a state template onto a lane, a transition template onto a transition, an author's card where its drop's type says, ⏎ on a card onto the selection; every gesture on the canvas — '+ LANE', a lane renamed (a double-click on its header) or deleted, a state added, edited, moved or deleted, a handle dragged to connect, Del on the selection — one transaction of the open flow's editing session, each flow keeping its own drafts, with Undo / Redo and one checked Save through the record's patch mutation or the host's onApply; its inspector pane, on by default (inspector={false} takes it away) — Details for the state, transition, decision, lane (a click on its header) or several states selected, or the open flow, each through the shared form, a state's or a transition's own Details in its place with inspector={{ state, transition }}, and Issues, each selecting what it names; with selection and path tracing — its types on Flowchart.Types, a row patch from Flowchart.patch. (17) The Allocation — <Allocation>, a day's arrivals booked into timed lane slots and each bin's quantity allocated to resources with room and time for its hold, always in its builder frame, as a Timeline (lanes, links, resource rows) or a Table (a row per slot), over the app's own records: its bins an instant Schedule.events kind (at, resource, arrival, quantity, parts, hold, kind?), its lanes and resources Schedule.resources kinds (offset; min, max, note, hold, jobs), what it books from Allocation.arrivals (each arrival's values a booked bin's own fields), and its days Schedule.periods for Publish — one history over the bins' and the days' sessions, every gesture a draft, Save per record and Publish saving first, then the day's status — the app's fit an e3.function bound with Func.bind (a place or a take question about the drafted day, FitRequest(R) over the bin row type, built on Allocation.answer / fit / lookahead), split loads and Find a fit, the app's bin fields as columns and its own check over CheckContext(R), one Slice.bindRecords rail over the day's bins, its lanes, resources and arrivals, the shared Library, Inspector and session, explicit-action cards below 480px, and pure loads / candidates / check / slots over Day(R) a task runs too. (18) The Schematic — <Schematic>, the 2D world-coordinate drawing of the app's own records: a kind per record, keyed by String, its fields mapped to the Schematic's roles and its shapes made by accessors over the row's own fields — items (Schematic.items: a centre, a box, a footprint drawn about the centre, a label, a status, a meter, a figure), zones (Schematic.zones: a box, outlined or hatched, a geometry about its centre), links between two items (Schematic.links: from and to naming item slots) and nets joining many to many (Schematic.nets: sources and destinations) — each kind editable bound with its patch door (Record.bind) or read only (Data.bind), read whole or complete through its source of one Slice.bindRecords rail (entries: Data.bindPaged) — a window at a time where that source lists an area index (index.area), its density shaded past 5,000 rows in view — its details a record keyed as it is (Schematic.details), its templates a record (Schematic.templates), its layers the app's rows (Schematic.layers), the library's tabs over every kind (Schematic.library.templates() / backlog() / index() / layers() / tab), each card dropped on the canvas — an item template adds an item at the point, a backlog row is placed there, a link or net template gives a link or a net of its kind its values, an author's card sets its Schematic.patch's fields, ⏎ onto the selection or at the view's centre — the inspector opted into: Details through the shared form over each kind's fields and its details', an item's position, a link's ends and a net's sides, and Issues, each selecting what it names; below 480px of main nothing drags, Details' Connect to… and the Backlog card's Place taking the gestures' place; a camera with a minimap, selection by a tap, Shift and the marquee, and every gesture — Move, Connect and a Shift session growing a net, an end retargeted, a net extended, Del — one step of one history over a session per record it writes, with Undo / Redo and Save sending each record's changes through its patch door, canConnect answering each pair with none or the refusal's words; onSelect hears the whole selection, each entity by its kind's slot and its key — its shapes and types on Schematic (Schematic.circle / polyline / polygon / rect about the anchor, outline / hatch, solid / dashed, Schematic.patch, Schematic.Types)."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# e3-ui — e3 + UI Bridge
|
|
@@ -90,7 +90,7 @@ Task → What do you need?
|
|
|
90
90
|
│ ├─ Key search → .seek(query) (none for an Array source)
|
|
91
91
|
│ ├─ The snapshot it serves → .revision() (the content hash; re-fires when it moves)
|
|
92
92
|
│ ├─ Show a confirmed write → .refresh(none) (or .refresh(some(hash)))
|
|
93
|
-
│ └─ Through a record's index → { index:
|
|
93
|
+
│ └─ Through a record's index → { index: "by_status", join?: true }
|
|
94
94
|
│ rows arrive in the INDEX's order as `{ik, key, value, row}`
|
|
95
95
|
│
|
|
96
96
|
├─ Call a named package function (e3.function) from the UI — Func.bind(fn)
|
|
@@ -109,13 +109,13 @@ Task → What do you need?
|
|
|
109
109
|
│
|
|
110
110
|
├─ Offer components for operators to arrange on pages (Studio)
|
|
111
111
|
│ ├─ Declare one — self-contained, like a ui() body → Studio.component(key, meta, fn)
|
|
112
|
-
│ ├─ Store the pages operators build → e3.record("pages", Studio.Types.Pages, new Map())
|
|
112
|
+
│ ├─ Store the pages operators build → e3.record("pages", Studio.Types.Pages, { initial: new Map(), mutations: (mutation) => ({ patch: mutation.patch() }) })
|
|
113
113
|
│ ├─ The builder: canvas, palette, inspector, preview → <Studio.Builder pages components project env? audience? rollout? id? />
|
|
114
114
|
│ ├─ Templates, pages, and where new pages start → <Studio.Library pages components project onOpen? id? />
|
|
115
115
|
│ └─ Show one page, live or draft, with no chrome → <Studio.Page pages components page version? />
|
|
116
116
|
│
|
|
117
117
|
├─ Let users query the bound datasets and save their queries (typed jq)
|
|
118
|
-
│ ├─ Store the queries they save → e3.record("queries", Query.Types.Saved, new Map())
|
|
118
|
+
│ ├─ Store the queries they save → e3.record("queries", Query.Types.Saved, { initial: new Map(), mutations: (mutation) => ({ patch: mutation.patch() }) })
|
|
119
119
|
│ ├─ Ship queries in the record, checked at build → Query.value({ orders, customers }, [{ name, jq, description?, savedAt }])
|
|
120
120
|
│ ├─ The builder: steps or jq, checked, run, saved → <Query.Builder queries datasets query? id? />
|
|
121
121
|
│ └─ The saved queries, and where new ones start → <Query.Library queries datasets onOpen? id? />
|
|
@@ -135,7 +135,7 @@ Task → What do you need?
|
|
|
135
135
|
│ ├─ Data too large to hold whole → data={paged}, a Data.bindPaged handle
|
|
136
136
|
│ ├─ Narrow its rows, event kinds, resources, templates → slice={{ slice: Slice.bindRecords(key, { rows?, job?, presses?, jobTemplates? }) }} (one window, which the rows never follow; Schedule.templates(rows, { …, source }))
|
|
137
137
|
│ ├─ Event kinds over records too large to read whole → a kind's source over its interval index (range: "start") + Schedule.events(record, { entries }), counted first and drawn within 10,000 events; with no rail, { window, backlogWindow, entries } over resources read whole, counted first by the days its events run on; Schedule.resources(Data.bindPaged(record), {…})
|
|
138
|
-
│ ├─ …on paged resources: read a cell at a time → the kind's source lists
|
|
138
|
+
│ ├─ …on paged resources: read a cell at a time → the kind's source lists index.series + index.summary beside its interval index, shaded from the summary zoomed out; any other form is refused, naming both
|
|
139
139
|
│ ├─ …its paged resources narrowed by the rail → a source named after the paged kind's slot, Data.bindPaged(record, { indexes }) over its own record: the canvas pages the narrowing's rows
|
|
140
140
|
│ ├─ A link between two runs, or two events → Plan.link({ from: Plan.ref(…), fromRun, … }) / Plan.link({ from: Plan.eventRef(kind, key), … })
|
|
141
141
|
│ ├─ Its library pane: templates, backlog, series, cards → library={[Plan.library.events(), Plan.library.backlog(), Plan.library.series(), Plan.library.tab(rows, {…})]}
|
|
@@ -147,7 +147,7 @@ Task → What do you need?
|
|
|
147
147
|
│ └─ Dropped cards and moves as drafts → a series' edit + editing={{ onUpdate | onApply }}
|
|
148
148
|
│
|
|
149
149
|
├─ Let planners type rows in place — the Sheet, in its frame (see The Sheet below)
|
|
150
|
-
│ ├─ An e3 record edited as a sheet → <Sheet record={…} columns={{…}} /> (record = Record.bind(r
|
|
150
|
+
│ ├─ An e3 record edited as a sheet → <Sheet record={…} columns={{…}} /> (record = Record.bind(r))
|
|
151
151
|
│ │ ├─ One entry's rows, groups or loose rows → entry={{ key, rows: "field", id: "id" }} (+ group)
|
|
152
152
|
│ │ └─ A large record, a window at a time → window={Data.bindPaged(r)}
|
|
153
153
|
│ ├─ The host's rows → <Sheet data={…} id="id" columns={{…}} />
|
|
@@ -160,7 +160,7 @@ Task → What do you need?
|
|
|
160
160
|
│ └─ Drafts checked as they are made, one checked Save → onUpdate | onApply, ready, newRow
|
|
161
161
|
│
|
|
162
162
|
├─ Draw a state-transition or process flow — the Flowchart (see The Flowchart below)
|
|
163
|
-
│ ├─ An e3 record of flows by name → <Flowchart record={…} flow="…" /> (record = Record.bind(r
|
|
163
|
+
│ ├─ An e3 record of flows by name → <Flowchart record={…} flow="…" /> (record = Record.bind(r), r of Flowchart.Types.Flows)
|
|
164
164
|
│ ├─ The record's value, written as literals → Flowchart.values({ [name]: { lanes, states, links, triggers? } }) (a lone flow: one entry)
|
|
165
165
|
│ ├─ One flow of the host's, an input's value → <Flowchart data={Data.bind(input)} /> (the input's value Flowchart.value({ … }))
|
|
166
166
|
│ ├─ One flow from the app's own tables → <Flowchart data={Flowchart.over(states, { state, links, link, lanes, triggers?, trigger? })} />
|
|
@@ -176,10 +176,10 @@ Task → What do you need?
|
|
|
176
176
|
│
|
|
177
177
|
├─ Draw the app's records at world coordinates — a floor, a site — the Schematic (see The Schematic below)
|
|
178
178
|
│ ├─ A kind per record, under its slot → <Schematic extent={{ width, height }} items={{ racks: Schematic.items(record, { name, icon, x, y, label, … }) }} zones={{ … }} links={{ … }} nets={{ … }} />
|
|
179
|
-
│ ├─ Editable, or read only → Record.bind(record
|
|
179
|
+
│ ├─ Editable, or read only → Record.bind(record) · Data.bind(record)
|
|
180
180
|
│ ├─ Every gesture one step: Undo, Save per record → built in over an editable kind: Move, Connect (Shift grows a net), an end retargeted, a net extended, Del
|
|
181
181
|
│ ├─ Narrow its kinds and templates — one rail → slice={{ slice: Slice.bindRecords(key, { <a kind's slot>: { source: Data.bindPaged(record, { indexes }) }, … }) }} (+ the kind's entries: Data.bindPaged(record)) · narrowed="dim"
|
|
182
|
-
│ ├─ A floor too large to read whole: a window at a time → its source lists
|
|
182
|
+
│ ├─ A floor too large to read whole: a window at a time → its source lists index.area({ x, y, width, height, at: "centre", cell }) (+ ones led by the rail's fields); past 5,000 rows in view, their density
|
|
183
183
|
│ ├─ Find an item by its key, wherever it stands → built in: Find seeks every item kind's record; a pick flies to the item and selects it
|
|
184
184
|
│ ├─ On a phone: below 480px of main, nothing drags → built in: Details' Connect to… and the Backlog card's Place, beside an item's x and y, a link's ends, a net's sides and Delete
|
|
185
185
|
│ ├─ Shapes from the row's own fields → footprint: (u) => some(Schematic.circle(u.radius)) · a zone's geometry: (a) => some(Schematic.polygon(a.corners))
|
|
@@ -191,10 +191,10 @@ Task → What do you need?
|
|
|
191
191
|
│ └─ What the host hears, and the connect veto → onSelect={fn(Array<Schematic.Types.Ref>)} · canConnect={fn(Schematic.Types.Candidate) → Option<String>}
|
|
192
192
|
│
|
|
193
193
|
├─ Staff a week's shifts from the app's own duties — the Roster (see Roster below)
|
|
194
|
-
│ ├─ The duties: the app's record, a span kind → events={{ duty: Schedule.events(Record.bind(duties
|
|
194
|
+
│ ├─ The duties: the app's record, a span kind → events={{ duty: Schedule.events(Record.bind(duties), { start, end, resource: { field, of }, shift, group, position?, activity?, hours? }) }}
|
|
195
195
|
│ ├─ Who is rostered → resources={{ people: Schedule.resources(staff.read(), { name, icon, label, group, … }), agencies? }}
|
|
196
196
|
│ ├─ The lists it reads, over the app's rows → Roster.shifts / groups / positions / activities / proposals(rows, { … })
|
|
197
|
-
│ ├─ Publish, and the targets: the app's weeks and cover → periods={Schedule.periods(Record.bind(weeks
|
|
197
|
+
│ ├─ Publish, and the targets: the app's weeks and cover → periods={Schedule.periods(Record.bind(weeks), { every: "week", status, dismissed? })} · targets={Roster.targets(Record.bind(cover), { day, group, shift, positions, hours })}
|
|
198
198
|
│ ├─ Costs in the app's own pay codes → pay={fn(Roster.Types.PayRequest(R)) → Roster.Types.Pay} currency="…"
|
|
199
199
|
│ ├─ Who fits a Find fix: the app's fit, on e3 → fit={Func.bind(e3.function)} from Roster.Types.FitRequest(R) to FitResult, over Roster.answer(R) + Roster.fit(R)
|
|
200
200
|
│ ├─ The app's optimiser over the week: the Elara eye → optimise={Func.bind(e3.function)} from Roster.Types.OptimiseRequest(R) to Roster.Types.OptimiseResult(R); its answer lands as ONE step of the history (#1409)
|
|
@@ -204,10 +204,10 @@ Task → What do you need?
|
|
|
204
204
|
│ └─ The week's figures in a task → Roster.coverage / check / weekHours / rest (R)
|
|
205
205
|
│
|
|
206
206
|
├─ Book a day's arrivals into lane slots, each bin on its resources — the Allocation (see Allocation below)
|
|
207
|
-
│ ├─ The bins: the app's record, an instant kind → events={{ bin: Schedule.events(Record.bind(bins
|
|
207
|
+
│ ├─ The bins: the app's record, an instant kind → events={{ bin: Schedule.events(Record.bind(bins), { at, resource: { field, of: "<lanes>" }, arrival, quantity: { field, unit }, parts: { field, of: "<resources>" }, hold, kind? }) }}
|
|
208
208
|
│ ├─ The lanes and the resources → resources={{ <lanes>: Schedule.resources(rows, { name, icon, label, offset? }), <resources>: Schedule.resources(rows, { name, icon, label, min, max, … }) }}
|
|
209
209
|
│ ├─ What it books from → arrivals={Allocation.arrivals(rows, { label, quantity, hold, kind?, values?, … })}
|
|
210
|
-
│ ├─ Publish: the app's days → periods={Schedule.periods(Record.bind(days
|
|
210
|
+
│ ├─ Publish: the app's days → periods={Schedule.periods(Record.bind(days), { every: "day", status })}
|
|
211
211
|
│ ├─ Who fits where: the app's fit, on e3 → fit={Func.bind(e3.function)} from Allocation.Types.FitRequest(R) to FitResult, over Allocation.answer(R) + Allocation.fit (+ Allocation.lookahead(R))
|
|
212
212
|
│ ├─ The app's optimiser over the day: the Elara eye → optimise={Func.bind(e3.function)} from Allocation.Types.OptimiseRequest(R) to Allocation.Types.OptimiseResult(R); its answer lands as ONE step of the history (#1409)
|
|
213
213
|
│ ├─ Narrow its bins, lanes, resources and arrivals — one rail → slice={{ slice: Slice.bindRecords(key, { bin: { source, range: "at" }, <lanes>?, <resources>?, arrivals? }) }}
|
|
@@ -314,7 +314,7 @@ rather than the record's, and each row carries what a view needs to render
|
|
|
314
314
|
without touching the record:
|
|
315
315
|
|
|
316
316
|
```tsx
|
|
317
|
-
const queue = $.let(Data.bindPaged(plans, { index:
|
|
317
|
+
const queue = $.let(Data.bindPaged(plans, { index: "by_status" }));
|
|
318
318
|
// rows: Array<{ ik: StatusKey, key: PlanKey, value: Projection, row: Option<Plan> }>
|
|
319
319
|
<Table data={queue} columns={{
|
|
320
320
|
ik: { header: "Status", value: (ik) => ik.status },
|
|
@@ -322,11 +322,12 @@ const queue = $.let(Data.bindPaged(plans, { index: byStatus }));
|
|
|
322
322
|
}} />
|
|
323
323
|
```
|
|
324
324
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
Name one of the indexes the record declares: the name is checked against the
|
|
326
|
+
record, and the window's type — the index key and the covering projection —
|
|
327
|
+
comes from the index's declaration, so the rows are checked at compile time.
|
|
328
|
+
`join: true` reads each entry's row from the record too; leaving it off is the
|
|
329
|
+
point of a covering projection, since the view then touches the index's
|
|
330
|
+
segments and none of the record's.
|
|
330
331
|
|
|
331
332
|
`.seek` takes a `from..to` range as well as a key or prefix, bounding a leading
|
|
332
333
|
prefix of the key's flattened fields — `late, 3..ok` over a `{status, due}`
|
|
@@ -337,18 +338,89 @@ inside it is text, so `"../config"` finds that key rather than a range.
|
|
|
337
338
|
|
|
338
339
|
### Indexed records and shared Slice
|
|
339
340
|
|
|
340
|
-
Declare field indexes
|
|
341
|
-
|
|
342
|
-
supply the controls; do not redeclare those types in Slice. A
|
|
343
|
-
key uses the index name as its field; a Struct key supplies
|
|
344
|
-
key order. `keys` accepts a scalar or a single-field Struct and
|
|
345
|
-
exact member, keeping pages and counts unique by primary ID.
|
|
346
|
-
compound fan-out remains available through `{ index }`; Slice refuses
|
|
347
|
-
`Data.bindPaged(record, { indexes: [
|
|
341
|
+
Declare field indexes in the record's `indexes` with `index.on([fields...])`, or
|
|
342
|
+
computed indexes with `index.key(fn)` / `index.keys(fn)`. Their existing
|
|
343
|
+
declared key types supply the controls; do not redeclare those types in Slice. A
|
|
344
|
+
scalar computed key uses the index name as its field; a Struct key supplies
|
|
345
|
+
named fields in key order. `keys` accepts a scalar or a single-field Struct and
|
|
346
|
+
permits one exact member, keeping pages and counts unique by primary ID.
|
|
347
|
+
Arbitrary compound fan-out remains available through `{ index }`; Slice refuses
|
|
348
|
+
it. `Data.bindPaged(record, { indexes: [names...] })` carries their field/key
|
|
348
349
|
types and joins rows by default. The indexes a binding lists carry one
|
|
349
|
-
projection, or each carries the whole row — as an area index does — or
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
projection, or each carries the whole row — as an area index does — or nothing:
|
|
351
|
+
one that carries the row is read without a join, the others with one, and any
|
|
352
|
+
other mix is refused at build, naming the index.
|
|
353
|
+
|
|
354
|
+
**Beside a nested series index** (`index.nested({ collections, tile, summary
|
|
355
|
+
})`), the record's other indexes carry each row without the collections it
|
|
356
|
+
declares, and a binding listing any of them lists it too:
|
|
357
|
+
`Data.bindPaged(machines, { indexes: ["machines_by_site_status",
|
|
358
|
+
"machines_in_time"] })`. Such a binding pages the lean rows (its projection the
|
|
359
|
+
record's row, each listed collection empty) and never joins one: `join: true` is
|
|
360
|
+
refused, and so is an index with a `value` of its own beside it, which is bound
|
|
361
|
+
apart. A row is read whole by its key. The binding's type carries how each
|
|
362
|
+
collection is declared (what it holds, its times, its rollups), and the nested
|
|
363
|
+
index's own entries — items, totals and subtotals — are read a cell at a time: a
|
|
364
|
+
page or a count of it is refused. A component given an indexed binding as `data`
|
|
365
|
+
reads its unfiltered view, a page at a time in its default index's order. A
|
|
366
|
+
`<Plan>` given such a binding as `data`, or a Slice whose `rows` source is one,
|
|
367
|
+
holds its `series` to the index in the editor: a series reading a collection the
|
|
368
|
+
index does not declare, by other times than it files by, items as values, a
|
|
369
|
+
subtotal by a rollup it does not keep, or runs rolled up that it files under no
|
|
370
|
+
parent, fails to compile, the error naming the index and what to write. The Plan
|
|
371
|
+
holds them again as it is built, a series list held in a variable too, and
|
|
372
|
+
refuses as well a function of the row mapping a collection the page carries
|
|
373
|
+
empty (a function it cannot see into, given the row, is refused rather than
|
|
374
|
+
guessed), a subtotal of values drawn as another number than stored or at a
|
|
375
|
+
resolution finer than the index's tile, a read by name where the binding lists
|
|
376
|
+
no nested series index or on an axis other than time, and an index keeping no
|
|
377
|
+
totals. Rows paged whole (`Data.bindPaged(dataset)`) are read as before.
|
|
378
|
+
|
|
379
|
+
**A Plan over them reads a cell at a time** (#1438; the Plan's `plan-nested`
|
|
380
|
+
example, `planNestedAll`: every series kind over two hundred works and ten
|
|
381
|
+
years — chart, events, cards and buckets switched off to start — its series
|
|
382
|
+
picked from the library, a crew's hours typed into). The rail pages the rows lean; for the rows in view and 25
|
|
383
|
+
either side, in blocks of fifty, the Plan reads each collection its series
|
|
384
|
+
draw — one range a row a level its items are filed at — in one batch of
|
|
385
|
+
searches and one read of the entries, and draws them with the same series as
|
|
386
|
+
over rows read whole. A pan reads only the tiles it adds; a zoom reads the
|
|
387
|
+
levels it shows. Past 10,000 items of a collection in the cells it wants, the
|
|
388
|
+
rows shade from the index's totals at its summary tile (runs and chips by the
|
|
389
|
+
share of the bucket booked, marks by how many, values folded as the series
|
|
390
|
+
folds them), or are hatched, a banner saying how many and why. A parent reads
|
|
391
|
+
what e3 keeps for it: a table's or a heat's aggregate and a group's strip are
|
|
392
|
+
its subtotals, one range at the resolution, by the rollup its `fold` and
|
|
393
|
+
`aggregate` make (which the index must keep: `values({ rollup })`); a span's
|
|
394
|
+
bands are the items e3 files under it (`items(…, { rollup: true })`) — so a
|
|
395
|
+
folded parent reads none of the rows beneath it. Editing over them: a gesture
|
|
396
|
+
drafts each row it touches read whole by its key, every row asked meanwhile in
|
|
397
|
+
one request; each drafted row's Save, previewed by e3 (#1437), is laid over
|
|
398
|
+
the cells, so the moved item and every subtotal over it draw before Save,
|
|
399
|
+
while which rows show keeps to what is saved; Save restates each listed
|
|
400
|
+
collection's change by key (`Record.onApply(record, { keyed: true })`), so
|
|
401
|
+
another planner's item added meanwhile is kept. A table's `edit: true` takes
|
|
402
|
+
typed numbers the same way (the ledgers: a reading typed into a leaf moves
|
|
403
|
+
every subtotal above it at once). What each series reads is known as the Plan
|
|
404
|
+
is built, so its series are a list of `Plan.series.*` values: in `series`, or
|
|
405
|
+
in `Plan.pick(key, [...])` passed to `pick` as it returns it, which lets a
|
|
406
|
+
viewer switch each series on and off from the library's Series tab
|
|
407
|
+
(`Plan.library.series()`). A list held as an East expression, or a pick held
|
|
408
|
+
in an East variable (`$.let`), is refused: the Plan could not see what its
|
|
409
|
+
series read. While cells are read — a pan, a zoom, a scroll, a commit — the
|
|
410
|
+
rows waiting on them wash, their plots busy, until the cells land. Its horizon
|
|
411
|
+
spans the time every series reads, shown or not, as e3 keeps it beside the
|
|
412
|
+
index (its span, `<index>_span`): from the first month to the last any row's
|
|
413
|
+
items or values touch, in the collections the series read, read once and again
|
|
414
|
+
at each commit — a series switched off moves no edge of it, and with every
|
|
415
|
+
series off the strip still spans the data — so the brush reaches every part of
|
|
416
|
+
the data from the start, not the window's own margins. Its bars are how much
|
|
417
|
+
the shown series' collections hold in each month, as the span counts it: a
|
|
418
|
+
bar each month, quarter or year — the finest that keeps 4px a bar — toned on
|
|
419
|
+
the heat ramp by its count, the largest in a meta line under the caption
|
|
420
|
+
(`max 1,559 items / mo`), and a bar's hover saying its period and count. Its
|
|
421
|
+
canvas holds its own window, and a zoom zooms it: as many buckets of the new
|
|
422
|
+
resolution as it showed of the old, about the same middle, inside that span —
|
|
423
|
+
the year shows the whole of it.
|
|
352
424
|
|
|
353
425
|
**A builder narrows through `Slice.bindRecords` alone** (#1340): one rail on
|
|
354
426
|
its toolbar, `Slice.bindRecords(key, { [source]: { source:
|
|
@@ -393,10 +465,10 @@ Local arrays use `Slice.bind(key, rows, options?)`; neither arm needs a type
|
|
|
393
465
|
argument for `Slice.rows(slice)`.
|
|
394
466
|
|
|
395
467
|
Calendar event windows preserve overlap with `[from, to)`, including carry-in
|
|
396
|
-
and whole-window spans. Declare an interval index
|
|
397
|
-
|
|
468
|
+
and whole-window spans. Declare an interval index among the record's indexes,
|
|
469
|
+
`index.interval({ on?, from: "start", to: "end" })` (optional leading `on` fields), name
|
|
398
470
|
the event kind's source after its slot with `range: "start"` (`job: { source:
|
|
399
|
-
Data.bindPaged(jobs, { indexes: [
|
|
471
|
+
Data.bindPaged(jobs, { indexes: ["jobs_by_time"] }), range: "start" }`), and give
|
|
400
472
|
the kind `entries: Data.bindPaged(jobs)` for its original-key reads. A kind
|
|
401
473
|
narrowed through its source takes no `window` or `backlogWindow`. Do not
|
|
402
474
|
replace it with a start-only query or guessed lookback. Optional missing ends
|
|
@@ -404,27 +476,29 @@ are instants; an instant at `from` is included and one at `to` is excluded.
|
|
|
404
476
|
Backlog remains part of the same authoritative record.
|
|
405
477
|
|
|
406
478
|
A **window over a drawing** — the rows whose box meets a viewport — reads
|
|
407
|
-
through an area index (`
|
|
408
|
-
source's binding: `Data.bindPaged(racks, { indexes: [
|
|
409
|
-
|
|
479
|
+
through an area index (`index.area`, the **e3** skill) listed in a
|
|
480
|
+
source's binding: `Data.bindPaged(racks, { indexes: ["racks_by_area",
|
|
481
|
+
"racks_by_hall_area"] })`. The Schematic reads a kind so wherever the kind's source
|
|
410
482
|
of its rail lists one, the window its view shows (see The Schematic below,
|
|
411
|
-
`schematicRecordIndexes`). A read of
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
`
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
naming why. An area index carries
|
|
426
|
-
so a window reads the index alone, without a join, each entry's
|
|
427
|
-
row.
|
|
483
|
+
`schematicRecordIndexes`). A read of a source — its `pageWhere` and
|
|
484
|
+
`countWhere` — takes a read, `{ where, area }`: the narrowing, and for a read
|
|
485
|
+
by area its clause beside it, `some(variant("window", { minX, minY, maxX,
|
|
486
|
+
maxY }))` in world units, or `some(variant("unplaced", null))` for the rows not
|
|
487
|
+
yet placed, on an index whose `x` and `y` are Options; any other read gives
|
|
488
|
+
`area: none`. The narrowing's exact choices of the fields an area index is led
|
|
489
|
+
by pick it. The record slice plans a window as at most 266 searches a
|
|
490
|
+
combination of those choices, sent in one request, and reads every row whose
|
|
491
|
+
box meets the viewport grown by an eighth of its larger side, each once —
|
|
492
|
+
beside rows of the searched cells that it misses, which the reader draws or
|
|
493
|
+
not. The window is the reader's, never the rail's: no choice holds it, and the
|
|
494
|
+
rail's state, its presets and `contains` keep the narrowing alone. A read no
|
|
495
|
+
listed area index serves — choices beside the window no area index is led by
|
|
496
|
+
exactly, a range or a prefix beside it, or more than one value of a Set field,
|
|
497
|
+
which would read a row twice — is refused, naming why. An area index carries
|
|
498
|
+
its rows, so a window reads the index alone, without a join, each entry's
|
|
499
|
+
`value` its row. e3's parts of a generated key are its index's kind's, never a
|
|
500
|
+
field's name (#1432): a row's field named `area` or `__area` is a field of the
|
|
501
|
+
rail like any other, and may lead an area index a window reads through.
|
|
428
502
|
|
|
429
503
|
Sessions retain original-key drafts across paging, narrowing and remount in the
|
|
430
504
|
same UI store. Save includes hidden edits and confirms primary rows at the
|
|
@@ -642,12 +716,11 @@ export const salesCount = Studio.component("sales_count", {
|
|
|
642
716
|
| `tags` / `collections` | the palette's Filter facets |
|
|
643
717
|
| `deprecated` | hidden from the palette; placements keep rendering |
|
|
644
718
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
it.
|
|
719
|
+
One field is derived from `fn`, never written: `reads`, the datasets,
|
|
720
|
+
functions and records its code binds (`deriveManifest` over `fn`) — the palette
|
|
721
|
+
card's meta line, the canvas tile's and the inspector's data list, each dataset
|
|
722
|
+
and record named in words (a task's output by its task's name:
|
|
723
|
+
`.inputs.sales_daily` reads "Sales daily"), never a keypath.
|
|
651
724
|
|
|
652
725
|
A surface lists the components it offers with `$.let([...])`, in palette order,
|
|
653
726
|
and hands the list to the Studio's components, which draw each placement by
|
|
@@ -658,6 +731,8 @@ calling its component's function:
|
|
|
658
731
|
placements of it share them.
|
|
659
732
|
- A placement whose component the list does not hold is a placeholder naming its
|
|
660
733
|
key; a key two listed components share is an error naming it.
|
|
734
|
+
- A placement holds its component's key alone, so a deploy that changes a
|
|
735
|
+
component's code changes every page that places it, its live version too.
|
|
661
736
|
|
|
662
737
|
### Studio pages — `Studio.Types.Pages`
|
|
663
738
|
|
|
@@ -665,11 +740,15 @@ The Studio exports the pages record's type; a solution declares its storage like
|
|
|
665
740
|
any record, with one write, a patch:
|
|
666
741
|
|
|
667
742
|
```ts
|
|
668
|
-
export const pages
|
|
669
|
-
|
|
743
|
+
export const pages = e3.record("pages", Studio.Types.Pages, {
|
|
744
|
+
initial: new Map(),
|
|
745
|
+
mutations: (mutation) => ({
|
|
746
|
+
patch: mutation.patch(),
|
|
747
|
+
}),
|
|
748
|
+
});
|
|
670
749
|
```
|
|
671
750
|
|
|
672
|
-
The builder and the page library bind it with `Record.bind(pages
|
|
751
|
+
The builder and the page library bind it with `Record.bind(pages)`;
|
|
673
752
|
a published page reads it with `Data.bind(pages)`. The type never depends on the
|
|
674
753
|
components, so a deploy that adds, changes or removes one runs no migration.
|
|
675
754
|
|
|
@@ -677,15 +756,13 @@ components, so a deploy that adds, changes or removes one runs no migration.
|
|
|
677
756
|
`Entry` is a `page` — `{ draft, live }`, `live` being `none` until the first
|
|
678
757
|
publish, else `{ version, page }` — or a `template`, a layout new pages start from.
|
|
679
758
|
- A `Page` is `{ title, cells }`, and a `Cell` is `{ key, row, span, height,
|
|
680
|
-
align, title, component
|
|
681
|
-
SnapGrid (rows in the order their keys first appear)
|
|
682
|
-
fingerprint when it was placed, stamped anew each time the page publishes — so
|
|
683
|
-
a live version's cells hold the code each went live with.
|
|
759
|
+
align, title, component }`: a component's key, and where it sits on the
|
|
760
|
+
SnapGrid (rows in the order their keys first appear).
|
|
684
761
|
|
|
685
762
|
Every operator action is one patch commit, computed in East as the diff of the
|
|
686
763
|
one entry it writes: the builder's Save (the page's draft cells), its Publish
|
|
687
|
-
(the live version becomes the draft, numbered one up — 1 the first time
|
|
688
|
-
|
|
764
|
+
(the live version becomes the draft, numbered one up — 1 the first time) and
|
|
765
|
+
its Save as template;
|
|
689
766
|
the page library's new page (a template's cells, or none for Blank grid). A
|
|
690
767
|
patch carries what it changes as it was, so a write drafted on a stale page is a
|
|
691
768
|
`conflict`, refused in the words of the screen that wrote it, and nothing is
|
|
@@ -702,7 +779,7 @@ takes the record bound with its patch:
|
|
|
702
779
|
export const builder = ui("builder", [], East.function([], UIComponentType, _$ => (
|
|
703
780
|
<Reactive>{$ => {
|
|
704
781
|
const components = $.let([kpiRail, revenueTrend, breakdownBars]);
|
|
705
|
-
const record = $.let(Record.bind(pages
|
|
782
|
+
const record = $.let(Record.bind(pages));
|
|
706
783
|
return <Studio.Builder pages={record} components={components} project="Ops console"
|
|
707
784
|
env="Staging" audience="Field ops · 24 users" rollout="Immediate" />;
|
|
708
785
|
}}</Reactive>
|
|
@@ -715,9 +792,11 @@ export const builder = ui("builder", [], East.function([], UIComponentType, _$ =
|
|
|
715
792
|
layout edit the inspector asks for, is a draft; the history item undoes,
|
|
716
793
|
redoes, discards and saves, and ⌘Z / Ctrl+Z undo from anywhere in the
|
|
717
794
|
builder. Save is one patch commit on the page; a save another write beat is
|
|
718
|
-
refused, and the
|
|
719
|
-
|
|
720
|
-
|
|
795
|
+
refused, and the drafts are carried over to the newer page, a part both
|
|
796
|
+
changed waiting to be chosen before Save — with none, the Save goes again
|
|
797
|
+
by itself. The selection bar
|
|
798
|
+
names the selected placement: its component's icon and name, and what its
|
|
799
|
+
code reads, never its key.
|
|
721
800
|
- **The toolbar**, one row: the page's status — `Draft` with an open ring until
|
|
722
801
|
its first publish, `Live` while its draft is its live layout, `Live · edited`
|
|
723
802
|
once they differ, `Template` for a template — the grid chip and the time of
|
|
@@ -731,16 +810,15 @@ export const builder = ui("builder", [], East.function([], UIComponentType, _$ =
|
|
|
731
810
|
- **The palette**, a pane of two tabs before the canvas. Components: every
|
|
732
811
|
listed component but the deprecated, by category, with a search and a Filter
|
|
733
812
|
menu over category, tags and collections; a card shows the component's icon,
|
|
734
|
-
its name, the
|
|
735
|
-
a placement at its component's span
|
|
813
|
+
its name, the data its code reads in words and a lock, and drags onto the canvas as
|
|
814
|
+
a placement at its component's span. The component
|
|
736
815
|
the canvas has selected is drawn placed, `ON CANVAS · ×N`, unsaved drafts
|
|
737
816
|
counted; a click selects its first placement. Pages: the project's pages in
|
|
738
817
|
key order with their status (`LIVE · Vn`, `DRAFT · Vn LIVE` or `DRAFT`); a
|
|
739
818
|
click opens one.
|
|
740
819
|
- **The inspector**, the pane after the canvas: the selected placement's name
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
(`.inputs.sales_daily`); its description, under a lock; and its layout — its
|
|
820
|
+
(never its component's key); what its code reads, each named in words ("Sales
|
|
821
|
+
daily"), never a keypath; its description, under a lock; and its layout — its
|
|
744
822
|
span, held to its row's room, its row, its height (Auto or a preset in px)
|
|
745
823
|
and where it sits in a taller row. Each edit is one gesture of the canvas's
|
|
746
824
|
session. With nothing selected it says so.
|
|
@@ -752,9 +830,8 @@ export const builder = ui("builder", [], East.function([], UIComponentType, _$ =
|
|
|
752
830
|
and Exit, back to the canvas with its drafts as they were; the page as it will
|
|
753
831
|
publish, its unsaved drafts in place; and an aside — "Ready to publish", the
|
|
754
832
|
version it replaces and the one it becomes (`v3 → v4`), the changes since,
|
|
755
|
-
each placement added, removed, moved or resized;
|
|
756
|
-
|
|
757
|
-
live version; the Audience and Rollout rows; Save as draft, which saves the
|
|
833
|
+
each placement added, removed, moved or resized; the Audience and Rollout
|
|
834
|
+
rows; Save as draft, which saves the
|
|
758
835
|
canvas's drafts; and Publish, which saves them first, then commits the
|
|
759
836
|
publish. A page never published is its first version, one live as it stands
|
|
760
837
|
is up to date, and a template is not published.
|
|
@@ -765,7 +842,7 @@ draws no border around itself, so a host frames it or places it bare.
|
|
|
765
842
|
|
|
766
843
|
| Prop | Meaning |
|
|
767
844
|
|---|---|
|
|
768
|
-
| `pages` | **required** — the record bound with its patch: `Record.bind(pages
|
|
845
|
+
| `pages` | **required** — the record bound with its patch: `Record.bind(pages)` |
|
|
769
846
|
| `components` | **required** — the components the surface lists, in palette order |
|
|
770
847
|
| `project` | **required** — the project whose pages it opens |
|
|
771
848
|
| `env` | where the preview publishes to — its Env pill, and "Publish vN to <env>"; omitted, neither names one |
|
|
@@ -781,7 +858,7 @@ commit, so it takes the record bound with its patch:
|
|
|
781
858
|
```tsx
|
|
782
859
|
<Reactive>{$ => {
|
|
783
860
|
const components = $.let([kpiRail, revenueTrend, breakdownBars]);
|
|
784
|
-
const record = $.let(Record.bind(pages
|
|
861
|
+
const record = $.let(Record.bind(pages));
|
|
785
862
|
return <Studio.Library pages={record} components={components} project="Ops console" />;
|
|
786
863
|
}}</Reactive>
|
|
787
864
|
```
|
|
@@ -810,7 +887,7 @@ draws no border around itself.
|
|
|
810
887
|
|
|
811
888
|
| Prop | Meaning |
|
|
812
889
|
|---|---|
|
|
813
|
-
| `pages` | **required** — the record bound with its patch: `Record.bind(pages
|
|
890
|
+
| `pages` | **required** — the record bound with its patch: `Record.bind(pages)` |
|
|
814
891
|
| `components` | **required** — the components the surface lists — what a template's line names |
|
|
815
892
|
| `project` | **required** — the project it shows first |
|
|
816
893
|
| `onOpen` | told when a page opens in the builder, with its key — the host shows the builder |
|
|
@@ -859,8 +936,12 @@ result, and save them. A solution declares one record of saved queries, with
|
|
|
859
936
|
one write, a patch:
|
|
860
937
|
|
|
861
938
|
```ts
|
|
862
|
-
export const queries
|
|
863
|
-
|
|
939
|
+
export const queries = e3.record("queries", Query.Types.Saved, {
|
|
940
|
+
initial: new Map(),
|
|
941
|
+
mutations: (mutation) => ({
|
|
942
|
+
patch: mutation.patch(),
|
|
943
|
+
}),
|
|
944
|
+
});
|
|
864
945
|
```
|
|
865
946
|
|
|
866
947
|
`Query.Types.Saved` is `Dict<String, SavedQuery>`, by name. A saved query is
|
|
@@ -878,11 +959,16 @@ builds, as the builder checks a query it saves. The sources are named as a
|
|
|
878
959
|
query reads them, each the dataset or task `Data.bind` takes:
|
|
879
960
|
|
|
880
961
|
```ts
|
|
881
|
-
export const queries = e3.record("queries", Query.Types.Saved,
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
962
|
+
export const queries = e3.record("queries", Query.Types.Saved, {
|
|
963
|
+
initial: Query.value({ orders, customers }, [
|
|
964
|
+
{ name: "Big orders", jq: ".orders | map(select(.total >= 1000))", savedAt: new Date("2026-10-01T09:00:00Z") },
|
|
965
|
+
{ name: "Orders by region", jq: ".customers as $c | .orders | group_by($c[.customer_id].region) | map(length)",
|
|
966
|
+
description: "How many orders each region placed.", savedAt: new Date("2026-10-01T09:00:00Z") },
|
|
967
|
+
]),
|
|
968
|
+
mutations: (mutation) => ({
|
|
969
|
+
patch: mutation.patch(),
|
|
970
|
+
}),
|
|
971
|
+
});
|
|
886
972
|
```
|
|
887
973
|
|
|
888
974
|
A query that does not check fails the build with the checker's words, as do two
|
|
@@ -894,7 +980,7 @@ export const builder = ui("query_builder", [], East.function([], UIComponentType
|
|
|
894
980
|
<Reactive>{$ => {
|
|
895
981
|
const orders = $.let(Data.bindPaged(d.orders)); // large: paged, never loaded whole
|
|
896
982
|
const customers = $.let(Data.bind(d.customers));
|
|
897
|
-
const saved = $.let(Record.bind(d.queries
|
|
983
|
+
const saved = $.let(Record.bind(d.queries));
|
|
898
984
|
return <Query.Builder queries={saved} datasets={{ orders, customers }} />;
|
|
899
985
|
}}</Reactive>
|
|
900
986
|
)));
|
|
@@ -903,7 +989,7 @@ export const library = ui("query_library", [], East.function([], UIComponentType
|
|
|
903
989
|
<Reactive>{$ => {
|
|
904
990
|
const orders = $.let(Data.bindPaged(d.orders));
|
|
905
991
|
const customers = $.let(Data.bind(d.customers));
|
|
906
|
-
const saved = $.let(Record.bind(d.queries
|
|
992
|
+
const saved = $.let(Record.bind(d.queries));
|
|
907
993
|
return <Query.Library queries={saved} datasets={{ orders, customers }} />;
|
|
908
994
|
}}</Reactive>
|
|
909
995
|
)));
|
|
@@ -939,7 +1025,7 @@ beside it, a Table or a tree with Download (CSV, BEAST2); and a status line.
|
|
|
939
1025
|
|
|
940
1026
|
| Prop | Meaning |
|
|
941
1027
|
|---|---|
|
|
942
|
-
| `queries` | **required** — the record bound with its patch: `Record.bind(queries
|
|
1028
|
+
| `queries` | **required** — the record bound with its patch: `Record.bind(queries)` |
|
|
943
1029
|
| `datasets` | **required** — the data sources a query may read, by the names it reads them: `{ orders, customers }` |
|
|
944
1030
|
| `query` | the saved query it opens first, by name; omitted, a new query on the first data source |
|
|
945
1031
|
| `id` | names the builder — needed only when one surface holds two, and then given to the library that opens queries in it |
|
|
@@ -1008,13 +1094,13 @@ return <Plan axis={axis} data={rows} series={series} />;
|
|
|
1008
1094
|
|
|
1009
1095
|
```
|
|
1010
1096
|
<Plan axis={Plan.axis({…})} data={rows} series={[Plan.series.span(Row, {…}), …]} /> — the planning canvas: ONE shared axis — { time | number | ordinal } (#631): a UTC window ÷ resolution, a numeric window ÷ step, or an ordinal list = n bucket columns — over heterogeneous rows — the eight row kinds: span state-runs, bucket allocation lanes, chart measures, heat cells, bucketed table numerals, cards shift chips, event marks, and group strips — sliced, searched and picked as one surface. Rows derive from `data` through `series` as an ordered STREAM (#822): the series list IS the layout (one contiguous block per series, top to bottom), a parent is followed by its subtree, and hierarchy comes only from the data's own nesting — never from a field value. Every row carries a typed id, `Plan.Types.RowId` = { series, path } (the series that made it and the entry keys that lead to it; `Plan.ref` builds one), which every callback reports and `links` address. Nothing behaves differently because `data` is inline or paged
|
|
1011
|
-
├─ The frame (#1193): ONE toolbar holding every control the Plan draws outside its canvas, in this order — the rail of its one `Slice.bindRecords` (each source's field chips and the presets, #1344), the key search, Expand/Collapse all, the shared date navigator (on a number axis the leading source's range is a chip of the rail), the configured resolution, the diagnostics; at its end the rail's summary, naming each source a narrowing hides rows of in its own words (#1358), the overlaps chip and the history item (a Plan with none of them has no toolbar). It is one row at every width: the rail folds first, its summary after every step of it, then the date navigator compacts, the resolution folds into a menu and Expand/Collapse all keeps its icon, the key search folds into its icon (which opens the box in a popover, the focus in it), then the time-axis view controls — the fold toggle, the resolution and the overlaps chip — share the date popover, one move, and the history item folds last, to its buttons — below that, where the row is still too wide, the rail's icon drops its caret, so the history and Save stay in the row at a phone's width; on a touch screen every control there is a 44px tap target. The banners — each session's, an event kind's titled with its name (#1194): a Save's conflict, a refusal, an unknown outcome with Retry
|
|
1097
|
+
├─ The frame (#1193): ONE toolbar holding every control the Plan draws outside its canvas, in this order — the rail of its one `Slice.bindRecords` (each source's field chips and the presets, #1344), the key search, Expand/Collapse all, the shared date navigator (on a number axis the leading source's range is a chip of the rail), the configured resolution, the diagnostics; at its end the rail's summary, naming each source a narrowing hides rows of in its own words (#1358), the overlaps chip and the history item (a Plan with none of them has no toolbar). It is one row at every width: the rail folds first, its summary after every step of it, then the date navigator compacts, the resolution folds into a menu and Expand/Collapse all keeps its icon, the key search folds into its icon (which opens the box in a popover, the focus in it), then the time-axis view controls — the fold toggle, the resolution and the overlaps chip — share the date popover, one move, and the history item folds last, to its buttons — below that, where the row is still too wide, the rail's icon drops its caret, so the history and Save stay in the row at a phone's width; on a touch screen every control there is a 44px tap target. The banners — each session's, an event kind's titled with its name (#1194): changes that conflict with newer ones, whose Choose opens the history item's conflicts, a Save's conflict, a refusal, an unknown outcome with Retry. Main — the canvas: the horizon, the ruler, pinned rows and the rows. The footer — the counts: the event kinds' events in the window, their backlog, the changes pending and when an event kind's record was last saved; then the author's `footer`, the one loading line — what the view waits for (`Loading rows 201–400`), a source's new narrowing, or a read that failed with its reason and Retry; nothing while nothing is in flight — and after a Save what left each source's narrowing (`1 print job event left this view`). ⌘Z undoes and ⇧⌘Z redoes from anywhere in the frame; a field being typed into keeps its own undo. The Plan draws no border
|
|
1012
1098
|
├─ Props:
|
|
1013
1099
|
│ ├─ axis (required) — ONE of three kinds; every element instant on the canvas must ride its arm. The kind is also a TYPE: `Plan.axis.number(…)` fixes the tag's `K`, every builder result carries the kind its instants' STATIC types imply (a `DateTimeType` accessor ⇒ `"time"`, a `FloatType` field ⇒ `"number"`, `Plan.at.ordinal(…)` ⇒ `"ordinal"`), a series collects its elements' kinds, and a series (or literal row) on another arm FAILS TO COMPILE at the tag — no wire change, the brand is phantom. Kind-erased values (a stored record, an `Expr<Plan.Types.Instant>`, an East-mapped element list, a `$.let`-bound axis or `$.const`-bound series list, chart rows) constrain nothing at compile time and are held to the axis at render — a row on another arm is a render-time diagnostic naming the row and the arm, never a silent misplacement. Plan.axis({ window?, resolution, resolutions?, now?, format? }) — the TIME shorthand (= Plan.axis.time): half-open [min, max) UTC window; omit window ⇒ the range the sources of its `slice` holding the window hold — an event kind's, a tab's, never the `rows` source's or a resource kind's (#1344) — there is NO fit to the data (#822): a canvas that neither states a window nor has a source holding a range is refused, at build when the axis is written in the tag, else as the render-time NO WINDOW diagnostic (a bound or stored axis); `resolutions` lists the WEEK/DAY segment options; `now` draws the observed/plan divider; `format` overrides tick labels (date tokens; defaults: week ⇒ "W27", day ⇒ "MON"); at a resolution coarser than a row's data a bucket shows ONE value per row — the fold of those in it (#824) · Plan.axis.number({ window?, step, now?, format? }) — a numeric window ÷ `step` (bucket edges on whole steps; omit window ⇒ the leading source's float / integer range, under the same no-fit rule; `format` a Chart.format.* spec; no resolution segment — step IS the declaration) · Plan.axis.ordinal({ values, now? }) — the declared values ARE the buckets, in order (the list is the window: no source's range holds it, no brush, the window keys idle; an unlisted value positions nowhere; an interval END names its LAST bucket, inclusive)
|
|
1014
1100
|
│ ├─ data (required unless `events`, `rows` or `slice`'s `rows` source, which gives the rows and refuses `data` beside it — a Plan with no rows from any source is refused at build) — a KEYED collection: a `Dict<K, R>` value/expression for the inline arm, or a `$.let`-bound `Data.bindPaged(…)` handle for a WINDOWED arm, recognised by its East type: `pinned` when it names its snapshot, which `editing.onApply` needs; `paged` when it is the shape a UI exported before `revision` / `refresh`, which names none. Any key type `K` — a row's path starts with its entry's key, a String as it is, any other key as its `.east` text (`3`, `(hall="H1", bin=3)`) — and any entry type `R`: a struct, a `RecursiveType` node, or a collection (a `groupToDicts` group). A positional collection is refused — key it at the call site with `rows.toDict((_$, r) => r.id)`. A paged canvas pages by PARENTS (#823): a window is N top-level entries, each with its whole subtree, so a parent never straddles two windows and its bands, means, subtotals and member count are exact — nest children in the source, and group a flat paged source in its dataflow (a group is one entry holding its members; a parent is bounded by a page). Each top-level series is a BLOCK, exactly as inline: a paged canvas pages every block on its own over the same windows (its own bands and resident run; one read of a window serves every block), draws a section's header and hand-built rows once (FIXED blocks), and rebases only the block a far jump lands in; a window landing above the rows in view at a height its estimate missed moves the scroll, never the rows. A paged source may serve SHORT windows (e3 trims pages of wide entries to a byte budget) and the canvas still reads WHOLE ones — its derived source re-requests what a trimmed page left out. When the source's `revision()` moves (`Data.bindPaged` follows each write to its dataset), the canvas re-reads its resident windows at the new snapshot IN PLACE: the rows on screen stay until theirs land (no remount, no empty frame), the scroll position holds, and a standing key search is asked again at the new snapshot, the view where it is (its matches named rows of the old one, #1199) — a match its box shows kept while the new snapshot holds it in place, else its query shown again
|
|
1015
|
-
│ ├─ series (required unless `pick`) — the row recipes over `data`; the list IS the layout (#822): each series contributes one contiguous block, top to bottom in declared order, its rows in source order, each parent followed by its subtree. Data series: Plan.series.span/buckets/chart/heat/table/cards/events(Row, { key, title, subtitle?, icon?, keyType?, match?, label, id?, stacked?, sub?, value?, status?, expand?, children?, collapsed?, edit?, …kind fields }) — every accessor receives (entry, key); a `RecursiveType` entry arrives as its node.
|
|
1016
|
-
│ ├─ pick (optional, exclusive with `series`) — Plan.pick(key, allSeries, { hidden? }): the bound series library — the toolbar has no Series button (#1193): the library pane's Series tab (#1195, when `library` lists `Plan.library.series()`) lists the series by title, subtitle and kind icon in the list's order (that order is the canvas layout); hidden series drop from the canvas (a hidden section, group or views takes its whole block); there are no row counts (#822 — a count means something only with every entry in hand); a pick is STATE, so the Plan must sit inside a <Reactive>
|
|
1017
|
-
│ ├─ resources + events (optional) — event kinds over records (#1191), `Schedule`'s — the very values a Calendar takes: `resources={{ presses: Schedule.resources(rows, { name, icon, label, … }) }}` are the rows events are placed on, in this order — `rows` a `Dict`, or a `Data.bindPaged` read of one, which pages the kind (#1199, see Large records, a window at a time) — and `events={{ job: Schedule.events(Record.bind(jobs
|
|
1101
|
+
│ ├─ series (required unless `pick`) — the row recipes over `data`; the list IS the layout (#822): each series contributes one contiguous block, top to bottom in declared order, its rows in source order, each parent followed by its subtree. Data series: Plan.series.span/buckets/chart/heat/table/cards/events(Row, { key, title, subtitle?, icon?, keyType?, match?, label, id?, stacked?, sub?, value?, status?, expand?, children?, collapsed?, edit?, …kind fields }) — every accessor receives (entry, key); a `RecursiveType` entry arrives as its node. A series reads the collections its row holds BY NAME (#1436), each a `Dict` field of the row: `Plan.items(field, times, draw)` reads things people drag — runs, decisions, tiles, chips, marks — keyed by an id (a `String`), each item's instants the fields `times` names (`{ from, to }` for a run or a chip, `{ at }` for the rest; a `DateTime` or an `Option` of one — no start draws nowhere, no end is an instant —, a `Float` / `Integer` on the number axis, a `String` on the ordinal) and its look `draw(item, id, row)`'s, everything its element takes but its key and instants (`Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.ticket, state: job.state }))`); `Plan.values(field, draw)` reads values keyed by their instant (a `DateTime`; a `String` on the ordinal axis), `draw(value, at, row)` giving what each takes besides its instant — a heat, weight, segment or table row's cells (`Plan.heatCells(Plan.values("load", (pct) => ({ value: some(pct) })), { min: 0, max: 100 })`), a table series' cells, a chart layer's points (`Plan.line(Plan.values("ontime", (pct) => ({ y: pct })))`), a span's `ports`, a bucket row's `markers`, a group's `summary`. Each read brands the series with what it reads (phantom: a misnamed field, a time field the item lacks or a look missing a required field fails to compile), a read written outside a series is refused, and a function of the row stays for anything that reads no collection. What a gesture WRITES is declared on the series (#880), through the collection its elements' `Plan.items` names: `edit: true` (span / buckets / cards / events) makes its runs, chips, tiles and marks MOVE — along their row, or to another, the item leaving one row's collection under its id and joining the other's under the same id — and its runs and chips RESIZE (#825), each gesture written into the item's named times; `edit: { create: (drop, entry, key) => item }` takes dropped cards as well, `create`'s item joining the row's collection under the id the canvas mints for it (`drop.id`) — see `editing`. An element's key is its item's id, its identity across every row it can move to: a row's collection keeps its ids unique, so a move onto a row holding the id is refused (the drag showing ⊘). A table's values take typed numbers with `edit: true` — on `Plan.series.table` for its `cells`, or on a position, `Plan.tableSeries({ cells, edit: true })` — its cells a `Plan.values` read drawing each value as stored (`value: some(v)`; `some(v.toFloat())` for an Integer collection): at the Plan's finest zoom, a number typed into a cell replaces the one value its bucket holds, at that value's date, adds one at the bucket's start where none is, and cleared deletes it; at a coarser zoom each cell is a summary and takes none, a double click, Enter or a tap zooming to the finest about it — the zooms are the slice's, so a Plan without one types at the resolution it shows (`Plan.Types.ValueEdit`; each row says which positions take a number, and whether whole numbers only, in `edits.values` — `Plan.Types.TypedPosition`). `children` nests, to any depth, by field: `children: "parts"` names a `Dict` field of the row holding more of THIS series' entries (a `RecursiveType` entry's own children, keyed by an id), `Plan.children("members", [series…])` steps down to a `Dict` field holding child rows of another type, keyed by an id, its series laid out like a top-level list, and an array of step-downs gives several child collections in order (a gesture on a nested row writes back through them); `collapsed: true | (r, k) => …` is a parent's initial state. A parent derives exactly, because its whole subtree rides in its entry: span bands over the subtree's runs (`rollup` "union" | "byStatus" | "sum"; a band sums its runs' quantities unit by unit — the unit rides each `Plan.quantity`), a heat parent with no cells of its own the children's per-bucket `aggregate` (default "mean") painted on its `scale` ({ min?, max?, warnAt? }; default the scale its members share), a table parent its children's subtotal at each position where its own collection holds no values for the row (`aggregate` default "sum" — "count" counts the leaf rows beneath that draw a value in the bucket —, `format`) — each summarising what its children SHOW, their values already folded to the resolution · Plan.series.group(Row, { key, title, label, children, summaryAggregate?, summary?, collapsed? }) — one strip PER ENTRY, its members the entry's children (derived member count; collapsed it rests as its summary strip — explicit `summary` cells OR a `summaryAggregate` of the members' heat cells, never both) · Plan.series.section(Row, { key, title, collapsed?, meta?, value?, status?, summary?, summaryAggregate? }, [series…]) — a fixed titled block over series (it adds no path segment; its header's id is `Plan.sectionRef(key, …parentPath)`) · Plan.series.views(Row, { key, title, match?, children?, collapsed? }, [series…]) — ONE entity shown several ways: one row per member series per entry, ADJACENT and in declared order (a member's own `match` decides whether its row shows; members declare no `children`), the entry's children following its view rows under the first — more of the views' own entries too (a bare accessor), each drawn as the members' rows, to any depth, a span member's `rollup` rolling them up — and a seek on the entry landing on its first view row · Plan.series.rows(Row, identity, rows) — hand-built rows (the kind factories below) placed as one block. Series keys are unique across the WHOLE series tree — a repeat is a build-time Error naming both sites (a list bound as an East value is checked at render: a repeated id draws as a row diagnostic, never a silent drop). There is no `groupBy`: to group a flat source, reshape it first — inline, one `groupToDicts` in the canvas function, each group an entry holding its rows in a field its series step down into (`rows.groupToDicts(($, r) => r.hall, ($, _r, k) => k).map(($, members) => ({ members }))`, entries `{ members: Dict<String, Row> }` keyed by the group); a paged source in the dataflow. A source keyed by another type (`Dict<Integer, R>`) works with series whose accessors ignore the key; one that reads it declares `keyType` and, bound as a list, types it `Plan.Types.Series(R, KeyType)`
|
|
1102
|
+
│ ├─ pick (optional, exclusive with `series`) — Plan.pick(key, allSeries, { hidden? }): the bound series library — the toolbar has no Series button (#1193): the library pane's Series tab (#1195, when `library` lists `Plan.library.series()`) lists the series by title, subtitle and kind icon in the list's order (that order is the canvas layout); hidden series drop from the canvas (a hidden section, group or views takes its whole block); what a viewer switches there is kept under `key` in the browser's storage, so a reload opens the series as they were left (an id the list no longer holds dropped; a pick the tab never wrote opens with `hidden`); there are no row counts (#822 — a count means something only with every entry in hand); a pick is STATE, so the Plan must sit inside a <Reactive>
|
|
1103
|
+
│ ├─ resources + events (optional) — event kinds over records (#1191), `Schedule`'s — the very values a Calendar takes: `resources={{ presses: Schedule.resources(rows, { name, icon, label, … }) }}` are the rows events are placed on, in this order — `rows` a `Dict`, or a `Data.bindPaged` read of one, which pages the kind (#1199, see Large records, a window at a time) — and `events={{ job: Schedule.events(Record.bind(jobs), { name, icon, title, start, end | at, resource: { field, of }, … }) }}` the kinds, each a record of its own committed through its patch mutation. Their rows come first, then `data`'s, then `rows`'. Each resource kind is a block (#1192): every resource a row per way the kinds placed on it draw — bars, tiles, chips, marks, in that order — then its `measures`, under its `group` strips and nested by its `parent`; a row's id is `entry { series: "<slot>.<draw>", path: [group?, …parents, key] }`, an element's key its event's `Schedule.Types.EventRef` as East prints it, and an event no resource holds draws on its kind's Unassigned row after every resource kind. The rows are read over the window the canvas draws, and read again when a record commits. Each kind is a session over its record, under ONE history with `data`'s (#1194): every gesture one transaction, drawn as Save would leave it; Undo and Redo (the history item, ⌘Z / ⇧⌘Z / ⌘Y anywhere in the frame but a field being typed into) step through the gestures in the order they were made, whatever their kind; Discard drops every kind's drafts; Save commits each kind with a change as one patch through its patch mutation, checked against what its drafts began from — one kind's conflict, refusal or unanswered write (Save turns into Retry, the same request) leaving the others' commits, each kind's banner titled with its name. Their events drag (#1196), each gesture one step of that history: a template from the library's Events tab, or an unscheduled event from its Backlog tab, drops on a resource's row at the bucket under the pointer — the template's event made under a new key for its duration (from its `at` on a day or longer) and selected, the backlog event scheduled for its own; an element moves along its row or onto another resource's row, writing the resource field, a selected element moving the selection with it, and a bar's or a chip's edge resizes it, never shorter than one snap; an event dropped on the Backlog tab is unscheduled (a kind whose times are Options); the ghost says what lands where (`Brochure run · Press A1 · Mon, Oct 12, 2026`) or, in red, why not (`Needs presses`, `canDrop`'s message); and an element of an event its drafts changed wears the brand tint in a brand border. An event is scheduled in time, so event kinds on a number or ordinal axis are refused at build, naming the axis and the kinds (an axis held in a variable is refused in the same words as the Plan is evaluated); `resources` without `events`, and a kind's `resource` naming a slot `resources` has not, are refused too. `applyMode` ("batch" default | "auto") says when the kinds' drafts go — on Save, or each ready gesture as it lands; `data`'s session takes `editing.mode` — and `date` the date brought into view first
|
|
1018
1104
|
│ ├─ rows (optional) — read-only rows after the event kinds' and `data`'s: hand-built rows (`Plan.chart({ … })`, `Plan.span({ … })`, …) and `Plan.over(data, [series…])`, series over a dataset — a keyed collection, or a `Data.bind` handle over one — which refuses a series declaring `edit` at build (a Plan's edits go through its event kinds and `data`'s session). Each is a FIXED block, drawn once on a paged canvas; a `pinned` row sits under the ruler. Their instants ride the axis's arm, or the tag fails to compile
|
|
1019
1105
|
│ ├─ library (optional, #1195) — the start pane's tabs, in order, each a `Plan.library.*` call: `events()` — every event kind's templates, under the kind's name and then the template's group; `backlog()` — every event kind's unscheduled events, grouped by when they are due (this week, overdue included; next week; later; no date — UTC weeks from the axis's `now`, else from when the Plan mounted); `series()` — `Pick.Panel`'s list of what a viewer can hide, in the order the canvas draws it: each resource kind and its measures, the event kinds, `data`'s picked series and the Plan's `rows` — hidden per viewer under the Plan's `id`, and only while the tab is listed; `tab(rows, { name, icon?, label, meta?, group?, drop?, source? })` — a card per row of a `Dict<K, R>` (accessors `(row, key)`), searched by key, label and meta, a click selecting one, following the rail's `source` it names by its rows' keys (#1344); each card drags onto the rows of `data` whose series makes an item of a card (`edit.create`), the Plan taking its own panel's cards with no `id` or `sources` (#1259), and with `drop: (row, key) => Schedule.patch(KindRowType, …)` onto an event, the patch's type naming the event kind it lands on — the patch set on the event under the pointer as one step (#1196), refused between events and on another kind's. The Backlog tab reads every kind's drafts, so a card scheduled leaves it and an event unscheduled joins it at once. Left out, or empty, no library pane. Collapsed, the pane is a rail with the backlog's count, or the first tab's. A tab listed twice, the Events or Backlog tab with no event kinds, the Series tab with nothing to hide, and a `drop` over no event kind's row type (or one two kinds share) are refused at build, naming the tab
|
|
1020
1106
|
│ ├─ inspector (optional, #1197) — the end pane: what is selected on the canvas. An event kind's element — a bar, tile, chip or mark — selects its event and its row on a click, Shift / ⌘ / Ctrl adding it to the selection or taking it out, and wears the brand's ring while it is selected; a row's name or plot selects the row, the plot's click naming the bucket under the pointer; Esc clears it all. One event shows its kind, title, when it runs and status, its facts (resource, start and end or its instant, lane, state, quantity), its kind's form over `fields` — or the kind's own inspector, `Schedule.events(record, { …, inspector: (row, update) => UIComponentType })`, in its place — then Duplicate and Delete; several, how many, each kind's count, the list and the bulk edit (state, resource, a shift of a day or an hour); a row, its resource's name, lines and group, its events in the window and its measures at the named bucket; nothing, the window's counts and three hints. Its edits are each one transaction of the Plan's history (#1194): a field of the form — tinted while the record holds it otherwise — the kind's own inspector's `update`, Duplicate (copies under new keys, then selected), Delete, and the bulk edit over every selected event that takes it, across kinds. Refused on a Plan with no event kinds
|
|
@@ -1022,10 +1108,10 @@ return <Plan axis={axis} data={rows} series={series} />;
|
|
|
1022
1108
|
│ ├─ popover / hover (optional) — generalized element resolvers fn(Plan.Types.ElementRef) => Option<UIComponent> over EVERY element (run / event / chip / mark / cell refs, each carrying the row's id — compare with `East.equal(ev.row, Plan.ref(…))` — and link refs { key, from, to }, a ribbon belonging to no one row); resolved lazily at click/hover time — a `none` result opens nothing
|
|
1023
1109
|
│ ├─ expandRender / expandGutter (optional) — the R2 expand-in-place renders fn(Plan.Types.RowId) => UIComponent for rows declaring `expand` (per-row DATA: `{ height?: "168px", axis?: "keep" | "dim" | "off" }` — the render's height, and whether the shared grid + now-line run through it, wash to 40%, or hide inside that row); the focused row GROWS to hold the render (its marks keep their band at the top, its gutter cell grows with it and takes `expandGutter`); every other data row compresses to a 16px strip — never removed: bars / tiles / chips shrink to 7px marks, chart and table rows re-encode as a tone strip, event marks keep their silhouette; a strip click or Esc returns
|
|
1024
1110
|
│ ├─ editing (optional) — the editing session (#880), the Sheet's (#879) over the source's TOP-LEVEL entries: { onApply? | onUpdate?, onPatch?, mode?, ready? }. Every dropped card, move and resize drafts the entry its row came from (a gesture on a nested row drafts the entry its whole subtree rides in), drawn at once where it was made — the entry's rows derived again, marked pending, or incomplete / invalid while a check refuses that entry — each gesture ONE undoable transaction. The history item (issues · Undo · Redo · Discard · Save, and its status line) ends the frame's toolbar; ⌘Z / Ctrl+Z undo, ⌘⇧Z / Ctrl+Shift+Z / Ctrl+Y redo. `onApply` fn(Editing.Types.ChangeSet(R, K)) => Editing.Types.ApplyResult (sync or async) commits one checked batch against the base the drafts began from — the inline `Dict`'s snapshot, or a paged source's revision (a paged `onApply` needs the source's `revision` and `refresh`): a batch against a source that moved is refused, never rebased, and a banner says "Source changed — review or discard these drafts"; a Save that throws leaves the outcome unknown, and the banner's "Retry request" resends the SAME request (dedupe by its `requestId`); drafts retire only when the source reads back at the committed revision. `onUpdate` fn(Dict<K, R>) => Null is the inline adapter instead (exclusive with `onApply`): pass `data={handle}` and `onUpdate={handle.write}`, and each batch applies with `Editing.apply` over the handle's LATEST `Dict` and writes the whole result once — idempotent across retries. `onPatch` fn(Plan.Types.PatchEvent(R)) => Null hears every gesture (origin drop / move / resize / undo / redo / discard). `mode` "batch" (default — Save sends) | "auto" (each ready gesture goes at once). `ready` fn(R, K) => Editing.Types.Readiness checks a drafted entry — a refusal names the entry and holds Save (a check that throws refuses its own entry only). Without `editing` the canvas takes no gesture
|
|
1025
|
-
│ ├─ slice (optional, #1344) — slice={{ slice: Slice.bindRecords(key, { rows?, <an event kind's slot>?, <a resource kind's slot>?, <a templates list's or a tab's source>? }, { presets? }), affordances?, persist? }}: ONE rail on the toolbar (affordances default ["fields","presets","summary","brush"]), each source named after what it narrows. `rows` gives the canvas its rows, paged from it in its index's order — `data` beside it is refused — edits reaching the record through `editing.onApply` (`Record.onApply(record, { keyed: true })`), the session the record's, a Save that takes rows out of the narrowing said in the footer. A source named after an event kind's slot reads that kind's events in the window and its backlog through its interval index (`
|
|
1111
|
+
│ ├─ slice (optional, #1344) — slice={{ slice: Slice.bindRecords(key, { rows?, <an event kind's slot>?, <a resource kind's slot>?, <a templates list's or a tab's source>? }, { presets? }), affordances?, persist? }}: ONE rail on the toolbar (affordances default ["fields","presets","summary","brush"]), each source named after what it narrows. `rows` gives the canvas its rows, paged from it in its index's order — `data` beside it is refused — edits reaching the record through `editing.onApply` (`Record.onApply(record, { keyed: true })`), the session the record's, a Save that takes rows out of the narrowing said in the footer. A source named after an event kind's slot reads that kind's events in the window and its backlog through its interval index (`index.interval({ on?, from: "start", to: "end" })` among the jobs record's indexes, `range: "start"`), the kind giving `entries: Data.bindPaged(record)` for its reads by key; a draft of an event its narrowing does not hold never shows, and Save sends it. A kind placed on a paged resource kind is read a cell at a time (#1039) — the events of the resources around the rows in view over the tiles the window touches, a range a resource a level (#1433), two requests a change — and shaded from the summary past what it draws: its source lists a series index and a summary index beside the interval index, and any other form is refused, naming both. Every other kind is counted before one of its events is read, and past 10,000 events in the range the canvas reads none is read or drawn (see Large records below). One named after a resource kind's slot narrows that kind's rows: of resources read whole and keyed by String, the rows and the bulk edit's Chooser, which keeps the resources the selected events are on; of a paged kind (`Schedule.resources(Data.bindPaged(record), …)`), its windows — the canvas pages the narrowing's rows themselves, the source `Data.bindPaged(record, { indexes })` over the kind's own record, in its index's order, the counts the narrowing's, so a choice reads only the narrowed resources in view and their cells (`planSeriesByHall`, `planRecordIndexes`). One a templates list (`Schedule.templates(rows, { …, source })`) or a library tab names narrows that tab's cards: each tab counts what it shows (`Customers 6 of 16`) and, emptied, says so with Clear filters; the Backlog counts `N of M`, its Clear filters keeping the window. Every source holding a range of the axis's arm — an event kind's, a tab's that lists what is in the dates on screen — holds ONE window — on a time axis the date navigator's, the leading source's range editor its Range panel and every source's range out of the rail; on a number axis the leading source's range a chip of the rail: the date navigator, the horizon brush, `[` / `]` / `n` and the resolution write each source, and a record's resolution only re-buckets, fetching nothing. The canvas's rows never follow the dates on screen: the `rows` source and a resource kind's take no `range` — refused as the Plan is evaluated, naming the change — so a date field there is a field of the rail the viewer sets, and a pan reads no row; a slice none of whose sources holds the window leaves the canvas its own, which the same controls move, kept for the viewer as the canvas's folds are, so a reload opens on the dates the viewer left. `brush` mounts the horizon band on time and number axes: an OVERVIEW of the sources' range in whole periods (with no source holding the window, the axis's stated window and the window shown, as long again either side, so its lens slides either way), with a lens joining its window to the plot's edges (drag its body to slide the window, an edge to resize it, empty track to draw one; or Tab to its start handle, window and end handle, an arrow moving a period, Shift ten, Home / End the ends, Esc clearing), committing once a gesture — over record sources with no bars yet, over rows read through a nested series index each month's items as bars (see the nested series above); `resolution` the WEEK/DAY segment (time axis); `summary` the rail's counts. Calculations — the footer's counts, the overlaps — read every event the kinds read. Refused at build, each naming the change: the bare handle, `Slice.bind`, a single `Slice.bindRecord`, a source nothing names, `data` beside `rows`, a resource kind keyed otherwise than by String as a source, a paged one's source over another record, a kind's own `slice` or `filter`, and a templates list written in the code or naming no source — and, as the Plan is evaluated, a `range` on the `rows` source or a resource kind's. With a `rows` source there is no key search: the rail narrows the rows — nor over a paged resource kind its source narrows, whose searches run in the source's index's order. On a PAGED `data` the toolbar's KEY SEARCH seeks over the source's `seek` — the box, folding to its icon on a row short of room; the jump REBASES residency at the match (windows in between are never fetched); the query is a whole-key literal, a prefix, leading struct fields, or a `from..to` RANGE over a leading prefix of the key's flattened fields (`late, 3..ok`), which is what a time window or a status band asks for — quoting is the escape: a quoted value is exact and a `..` inside it is text (`"../config"`)
|
|
1026
1112
|
│ ├─ grain (optional) — initial grain "resource" (default) | "group" (root groups collapse to summary strips); the `g` key cycles it, and a canvas with a root group mounts the toolbar's GROUP · RESOURCE segment for it — slice or no slice (#632; the narrow layout's tabs own the grain there); onGrainChange observes
|
|
1027
1113
|
│ ├─ ui (optional) — the interaction state, held by the HOST (#824): State.bind([Plan.Types.UiState], key, Plan.uiState({ selected?, collapsed?, expanded?, charts?, focus? })) — the canvas draws it from its first frame, takes every outside write (a selection, rows folded or opened, charts expanded) and writes the user's own actions back, once per action; `collapsed` / `expanded` list the rows folded / opened AGAINST their declaration (a row in neither follows it; in both, folded); `focus` is a REQUEST — the canvas opens the rows it nests under, scrolls it to the top of the view, makes it the tab stop (DOM focus stays put) and writes `focus: none` back; a paged row it has not loaded is opened by its window, or sought by its entry's key where the source can seek. Bound, the canvas persists no toggles of its own under its `storageKey`
|
|
1028
|
-
│ ├─ id + sources + canDrop (optional) — DnD TARGET (the shared grammar): cards — an author's tab's in the Plan's own library panel (`Plan.library.tab`, #1259), or a Library's beside the Plan, which reaches it by its `id` when `sources` lists the Library — `add` onto rows whose series declares `edit` — span / buckets / events / cards only (chart / heat / table render DERIVED values, and section headers and group strips are wayfinding — they register no cell) — and only with `editing`: a drop is a DRAFT of the session (#880), its item built by the series' `create` from the `Plan.Types.Drop` ({ from: { library, key }, row, at, duplicate } — `at` the start of the bucket it landed in, on the axis arm). `canDrop` sees the drag grammar's `add`: its `into.row` is the row id's canonical TEXT — key host tables by `East.print(Plan.ref(series, …path))`, or read it back with `row.parse(Plan.Types.RowId)`; slot = the bucket start instant per the axis arm — time: Z-less ISO (`slot.parse(DateTimeType)`), number: a decimal (`slot.parse(FloatType)`), ordinal: the value; drop verdicts resolve LIVE where the drag rests — `canDrop` is asked of the event a drop there would deliver (an `add`'s `duplicate` is whether Alt is held), renders the ⊘ stage, and is asked again before the drop becomes a draft — and the landing band previews WHERE the card lands. A moved or resized element (#825) is vetoed the same way: `canDrop` sees the grammar's `move` ({ from, to } cells, `from.event` the element's key) or `resize` ({ event, edge }). The canvas's own elements, and its own panel's cards, need no `id` — only a Library beside the Plan does. A card held at a bounded canvas's edge scrolls it to the rows beyond; by keyboard (#608) → / ← step onto a row and along its buckets and ↑ / ↓ between rows, each bucket announced ("Poster run is over H1-P03, Week of Jul 6, 2026."). `onDrag` is removed (#880) and throws at build. With event kinds, `canDrop` may instead be fn(Schedule.Types.Candidate) => Option<String>, vetting an event kind's drop with its message on the ghost, as the Calendar's — the arm is read from the function's type; it is asked where a drag rests and again of the drop, `from` the template's key, the backlog event's or the moved event's (#1196)
|
|
1114
|
+
│ ├─ id + sources + canDrop (optional) — DnD TARGET (the shared grammar): cards — an author's tab's in the Plan's own library panel (`Plan.library.tab`, #1259), or a Library's beside the Plan, which reaches it by its `id` when `sources` lists the Library — `add` onto rows whose series declares `edit` — span / buckets / events / cards only (chart / heat / table render DERIVED values, and section headers and group strips are wayfinding — they register no cell) — and only with `editing`: a drop is a DRAFT of the session (#880), its item built by the series' `create` from the `Plan.Types.Drop` ({ from: { library, key }, row, at, duplicate, id } — `at` the start of the bucket it landed in, on the axis arm; `id` the key the canvas mints for the new item, unique among every element it holds) and joining the collection the series' `Plan.items` names under that id. `canDrop` sees the drag grammar's `add`: its `into.row` is the row id's canonical TEXT — key host tables by `East.print(Plan.ref(series, …path))`, or read it back with `row.parse(Plan.Types.RowId)`; slot = the bucket start instant per the axis arm — time: Z-less ISO (`slot.parse(DateTimeType)`), number: a decimal (`slot.parse(FloatType)`), ordinal: the value; drop verdicts resolve LIVE where the drag rests — `canDrop` is asked of the event a drop there would deliver (an `add`'s `duplicate` is whether Alt is held), renders the ⊘ stage, and is asked again before the drop becomes a draft — and the landing band previews WHERE the card lands. A moved or resized element (#825) is vetoed the same way: `canDrop` sees the grammar's `move` ({ from, to } cells, `from.event` the element's key) or `resize` ({ event, edge }). The canvas's own elements, and its own panel's cards, need no `id` — only a Library beside the Plan does. A card held at a bounded canvas's edge scrolls it to the rows beyond; by keyboard (#608) → / ← step onto a row and along its buckets and ↑ / ↓ between rows, each bucket announced ("Poster run is over H1-P03, Week of Jul 6, 2026."). `onDrag` is removed (#880) and throws at build. With event kinds, `canDrop` may instead be fn(Schedule.Types.Candidate) => Option<String>, vetting an event kind's drop with its message on the ghost, as the Calendar's — the arm is read from the function's type; it is asked where a drag rests and again of the drop, `from` the template's key, the backlog event's or the moved event's (#1196)
|
|
1029
1115
|
│ ├─ optimise (optional, #1409) — `Func.bind` of the app's `e3.function` from `Plan.Types.OptimiseRequest(E, S)` — the plan window in view, the run's number, every event kind's rows in the window and the backlog by key as the session holds them (base plus every unapplied draft, every row whatever the rail shows), and every resource kind's rows read whole as context (a kind paged a window at a time carries none) — to `Plan.Types.OptimiseResult(E)`, the events complete. The Elara eye in the toolbar, immediately before the history item: one call per run; while it is out the history and every gesture are locked, a change of window stops it (Stop too), and a late answer is ignored. Its answer is diffed by key and recorded as ONE step of the history — Undo in one, Redo replays it, Save with everything else — each event it placed from the backlog an add on the canvas, one it moved a change with a ghost where it stood, one it took back a remove; each marked until Apply with its provenance on hover (`Added by Elara · run #7`), the Backlog count and the footer's counts flashing; a result equal to the request records nothing and says so
|
|
1030
1116
|
│ ├─ onSelect / onElementClick / onGroupToggle (optional) — row selection, ONE element callback and section toggles; payloads are row ids + element keys + instants, never indices: onSelect the `Plan.Types.RowId`; onElementClick a `Plan.Types.ElementRef` — the SAME ref the popover resolver receives, its arm the element kind (run { row, run } · event { row, event } · mark { row, mark } · chip { row, chip } · cell { row, at } — `at` a Plan.Types.Instant on the axis arm, a folded bucket's start · link { key, from, to }), so `ref.match({ run: …, link: … }, …)` handles the kinds it cares about; onGroupToggle { row, expanded } for any row with children
|
|
1031
1117
|
│ ├─ footer (optional) — [{ text, tone?, end? }] status-footer items, in the frame's footer after its counts; a paged canvas adds the one loading line (what the view waits for, or why a window could not be read, with Retry); rows not read draw as the shared run — label bars and the plot washed, at the rows' pitch — and until the source is exhausted a TOP-LEVEL section's member count is a placeholder bar (its members are entries the windows share out) — every other parent's numbers are exact, its subtree riding whole in one entry
|
|
@@ -1037,7 +1123,8 @@ return <Plan axis={axis} data={rows} series={series} />;
|
|
|
1037
1123
|
└─ Factories:
|
|
1038
1124
|
├─ Plan.axis({ … }) = Plan.axis.time / Plan.axis.number({ window?, step, now?, format? }) / Plan.axis.ordinal({ values, now? }) — the shared axis declaration, one of three kinds (Plan.Types.Axis); Plan.at.time(d) / Plan.at.number(n) / Plan.at.ordinal(s) build ONE INSTANT explicitly (Plan.Types.Instant) — needed only for element RECORDS written as data (a Plan.Types.HeatCell array, a stored Plan.Types.Run): every element builder below takes a Date / number / string, or a DateTime / Float / Integer / String expression, and wraps it to the arm by its type, so a `start: r.start` DateTime accessor and a `day: FloatType` field both need nothing written
|
|
1039
1125
|
├─ Plan.series.* — the data-driven row series (see `series` above); Plan.Types.Series(Row) is one series' East type over `Dict<String, Row>` entries, Plan.Types.Series(Row, KeyType) over another key type
|
|
1040
|
-
├─ Plan.
|
|
1126
|
+
├─ Plan.items(field, times, draw) / Plan.values(field, draw) — a series' reads of the collections its row holds, by name (#1436, see `series`): things people drag keyed by an id, and values keyed by their instant
|
|
1127
|
+
├─ Plan.children(field, [series…]) — a step down from an entry to a `Dict` field of child rows of another type, keyed by an id, for a series' `children`
|
|
1041
1128
|
├─ Plan.ref(series, …path) / Plan.sectionRef(series, …path) — a row's id (Plan.Types.RowId = entry { series, path } | section { series, path }): `Plan.ref("presses", "H1-P03")`, a nested row `Plan.ref("rollup", "Contract A", "H1-P03")`, a `views` member's row by the MEMBER's key; for links, `East.equal` comparisons in resolvers, and host tables keyed by `East.print(id)` (an id is a variant — it cannot be a Dict key itself)
|
|
1042
1129
|
├─ Plan.run({ key, start, end, label, quantity?, state, status?, moved?, icon? }) — one state-run bar (state: "actual"|"in-progress"|"confirmed"|"estimated"|"added"|"recommended"|"removed"|"rejected"; `quantity` is ONE Plan.quantity — the bar prints its caption after the label and a parent's rollup band sums it with its siblings' in the same unit; status "warning" draws the stuck ring; runs past the window mask-fade, never fabricate an end)
|
|
1043
1130
|
├─ Plan.quantity(value, { unit?, format?, text? }) — a quantity (Plan.Types.Quantity): `value` is what sums, weighs and compares, `unit` what a rollup sums by (sheets never add to hours), `format` a Format.* spec printing the value in the viewer's locale, `text` a caption printed instead (the value still sums) — a run's and a link's `quantity`
|
|
@@ -1045,8 +1132,9 @@ return <Plan axis={axis} data={rows} series={series} />;
|
|
|
1045
1132
|
├─ Plan.lane({ key, label? }) / Plan.marker({ at, lane?, status?, message }) — bucket sub-slot lanes + cell status rings (status defaults "danger"; message = the tooltip)
|
|
1046
1133
|
├─ Plan.chip({ key, from, to, label, state, icon? }) — one cards shift chip; Plan.mark({ key, at, kind, icon?, label? }) — one event mark (kind: "milestone" | "exception" | Plan.markKind.decision(applied))
|
|
1047
1134
|
├─ Plan.decision({ key, at, applied }) / Plan.port({ at, label? }) — span-row decision diamonds (◇ pending / ◆ applied) + quantity in/out ports
|
|
1048
|
-
├─ Plan.heatCells(cells, { min?, max?, warnAt?, fold?, format? }) / Plan.weightCells(cells, { fold?, format? }) / Plan.segmentCells(cells, { fold?, format? }) + Plan.segment({ fill, weight, label? }) — the three heat-row cell arms (colour depth / booked-vs-free bars / compositions); `fold` is what a coarser bucket shows of the cells in it (heat and weight "mean", segments "sum"; "sum" | "mean" | "min" | "max" | "last" | "count"), `format` a Format.* spec for the values the arm prints — a heat cell without a `label` prints its value only through a declared `format`, and the words a reader hears for a weight or a segment share follow it
|
|
1049
|
-
├─ Plan.tableCells(rawCells) / Plan.tableSeries({ cells, format?, tone?, strong?, rollup?, fold? }) — bucketed numerals (a raw cell's `at` wraps by its field type — DateTime / Float / Integer / String / an instant) (multi-series per row, style declared once per position; explicit text/tone overrides via PlanTableCellType values); a series folds by `fold` (default "sum"), and `Plan.series.table`'s `fold` declares it for its `cells`
|
|
1135
|
+
├─ Plan.heatCells(cells, { min?, max?, warnAt?, fold?, format? }) / Plan.weightCells(cells, { fold?, format? }) / Plan.segmentCells(cells, { fold?, format? }) + Plan.segment({ fill, weight, label? }) — the three heat-row cell arms, `cells` a `Plan.values` read of the row's collection (`Plan.heatCells(Plan.values("load", (pct) => ({ value: some(pct) })))`; `{ fraction, planned }` for weights, `{ segments }` for compositions) or an array of cells (colour depth / booked-vs-free bars / compositions); `fold` is what a coarser bucket shows of the cells in it (heat and weight "mean", segments "sum"; "sum" | "mean" | "min" | "max" | "last" | "count"), `format` a Format.* spec for the values the arm prints — a heat cell without a `label` prints its value only through a declared `format`, and the words a reader hears for a weight or a segment share follow it
|
|
1136
|
+
├─ Plan.tableCells(Plan.values(field, (v) => ({ value: some(v) })) | rawCells) / Plan.tableSeries({ cells, format?, tone?, strong?, rollup?, fold?, edit? }) — bucketed numerals (`edit: true` over a values read: the position takes typed numbers, #1436) (a raw cell's `at` wraps by its field type — DateTime / Float / Integer / String / an instant) (multi-series per row, style declared once per position; explicit text/tone overrides via PlanTableCellType values); a series folds by `fold` (default "sum"), and `Plan.series.table`'s `fold` declares it for its `cells`
|
|
1137
|
+
├─ Plan.line / Plan.area / Plan.column / Plan.scatter / Plan.band(Plan.values(field, draw), { axis?, breach?, series?, fold? }) — a chart row's layer over the row's values, a point at each value's instant (`draw` gives `{ y }`, a band's `{ lo, hi }`; columns stack by `series`, `breach` on lines and columns, scatter and band draw every point) (#1436)
|
|
1050
1138
|
├─ Plan.layer(chartLayer, { axis?, breach?, series?, fold? }) + Plan.fixed("120px") — chart rows consume Chart.Line/Column/Area/Scatter/Band/ref* builder results AS DATA on the shared scale (the x accessor's static type picks the arm — DateTimeType ⇒ time, Float/Integer ⇒ number, String ⇒ ordinal — and must match the canvas axis at render; Chart.Bar is a build-time error on every kind); Plan.layer adds the y-axis side, breach threshold, stack series and the fold a coarser bucket shows (columns "sum", lines and areas "mean"; scatter and band layers draw every point)
|
|
1051
1139
|
├─ Plan.span/buckets/chart/heat/table/cards/events/group({ key, label, id?, sub?, value?, meta?, stacked?, swatches?, collapsed?, pinned?, height?, status?, expand?, …kind fields, rows? }) — literal kind factories returning row STREAMS (the row, then its nested `rows:` in order; parents DECLARE rollup/aggregate and the renderer derives the numbers); ride them beside data-driven series via Plan.series.rows — a hand-built row's id is that series' key plus the factory `key`s that lead to it (`Plan.ref("works", "shutdown", "elec")`), so keys must be unique among siblings (a repeat draws as a DUPLICATE ID row diagnostic)
|
|
1052
1140
|
├─ Plan.link({ key, from, fromRun, to, toRun, quantity? }) — one link-graph edge; `key` is what a ribbon click's `link` ref names it by, `from` / `to` are row ids (`Plan.ref`) with their runs, or events (`Plan.eventRef`) with none, `quantity` a Plan.quantity
|
|
@@ -1054,7 +1142,7 @@ return <Plan axis={axis} data={rows} series={series} />;
|
|
|
1054
1142
|
├─ Plan.library.events() / backlog() / series() / tab(rows, config) — the library pane's tabs (#1195, see `library`) · Schedule.patch(R, { field: value, … }) — a patch over an event kind's row type (Schedule.Types.Patch(R): every field an `Option`, the fields left out `none`), what an author's card dropped on an event sets
|
|
1055
1143
|
├─ Plan.uiState({ selected?, collapsed?, expanded?, charts?, focus? }) — the seed of a bound `ui` state (Plan.Types.UiState): rows named by id (`Plan.ref`), omitted fields empty
|
|
1056
1144
|
├─ Plan.Types.OptimiseRequest(E, S) / Plan.Types.OptimiseResult(E) — what the app's optimiser is asked and answers (#1409): `E` and `S` the event and resource kinds' row types by slot (`Plan.Types.OptimiseRequest({ job: Job }, { presses: Press })`, a kind keyed otherwise than by String `{ key, row }`); the request `{ from, to, run, events, resources }`, each kind's rows by key; the result `{ events }`, the events complete, a new event's key the optimiser's own
|
|
1057
|
-
└─ Plan.Types.PatchEvent(R) — what `editing.onPatch` receives for entries of `R` · Plan.Types.Drop ({ from: { library, key }, row: RowId, at: Instant, duplicate }) — what an `edit.create` builds its item from · Plan.Types.Gesture (drop(Drop) | move(Move)) and Plan.Types.RowEdits ({ drop, move: Option<MoveEdits> } — the gestures a row takes) · Plan.Types.Move ({ key, from: RowId, to: RowId, start, end } — a moved or resized element, #825) — the editing wire (#880); the session's contracts are the shared `Editing.Types.*` (ChangeSet(R, K), ApplyResult, Readiness)
|
|
1145
|
+
└─ Plan.Types.PatchEvent(R) — what `editing.onPatch` receives for entries of `R` · Plan.Types.Drop ({ from: { library, key }, row: RowId, at: Instant, duplicate, id: String }) — what an `edit.create` builds its item from, `id` the key its item goes in under · Plan.Types.Gesture (drop(Drop) | move(Move) | value(ValueEdit)) and Plan.Types.RowEdits ({ drop, move: Option<MoveEdits>, values: Array<Integer> } — the gestures a row takes, `values` the table positions taking typed numbers) · Plan.Types.ValueEdit ({ row, position, at, value: Option<Float> } — a number typed into a table cell, `at` the date of the one value its bucket holds, else its bucket's start; `none` when cleared, #1436) · Plan.Types.Move ({ key, from: RowId, to: RowId, start, end } — a moved or resized element, #825) — the editing wire (#880); the session's contracts are the shared `Editing.Types.*` (ChangeSet(R, K), ApplyResult, Readiness)
|
|
1058
1146
|
```
|
|
1059
1147
|
|
|
1060
1148
|
#### The series list is the layout, the data is the hierarchy (#822)
|
|
@@ -1066,32 +1154,40 @@ step down into a child collection — so a parent's bands, means and subtotals
|
|
|
1066
1154
|
are exact, and the canvas reads the same inline or paged. There is no
|
|
1067
1155
|
`groupBy`: to group a flat source, reshape it first.
|
|
1068
1156
|
|
|
1157
|
+
A row's collections are `Dict`s it holds, read by name (#1436): things people
|
|
1158
|
+
drag keyed by an id, with their times as fields, through `Plan.items`; values
|
|
1159
|
+
keyed by their instant through `Plan.values`; child rows keyed by an id,
|
|
1160
|
+
through `Plan.children` or a tree's own field.
|
|
1161
|
+
|
|
1069
1162
|
```tsx
|
|
1070
|
-
//
|
|
1071
|
-
const
|
|
1072
|
-
const
|
|
1163
|
+
// A press's jobs by id, its load by the instant of each reading.
|
|
1164
|
+
const Job = StructType({ ticket: StringType, start: DateTimeType, end: DateTimeType, state: EventStateType });
|
|
1165
|
+
const HallRow = StructType({ label: StringType, hall: StringType, jobs: DictType(StringType, Job), load: DictType(DateTimeType, FloatType) });
|
|
1166
|
+
// Grouping is a DATA step: each hall an entry holding its rows, in the field its series step down into.
|
|
1167
|
+
const halls = $.let(rows.groupToDicts(($, r) => r.hall, ($, _r, k) => k).map(($, members) => ({ members })));
|
|
1168
|
+
const Hall = StructType({ members: DictType(StringType, HallRow) });
|
|
1073
1169
|
const series = $.const([
|
|
1074
1170
|
// One strip PER HALL; its rows stepped down into, laid out like a top-level list.
|
|
1075
|
-
Plan.series.group(
|
|
1171
|
+
Plan.series.group(Hall, {
|
|
1076
1172
|
key: "halls", title: "Halls",
|
|
1077
|
-
label: (
|
|
1173
|
+
label: (_h, hall) => hall,
|
|
1078
1174
|
summaryAggregate: "mean",
|
|
1079
|
-
children: Plan.children(
|
|
1175
|
+
children: Plan.children("members", [
|
|
1080
1176
|
Plan.series.span(HallRow, {
|
|
1081
1177
|
key: "hall-jobs", title: "Jobs",
|
|
1082
1178
|
match: r => r.jobs.size().greater(0n),
|
|
1083
1179
|
label: r => r.label,
|
|
1084
|
-
runs:
|
|
1180
|
+
runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.ticket, state: job.state })),
|
|
1085
1181
|
}),
|
|
1086
1182
|
Plan.series.heat(HallRow, {
|
|
1087
1183
|
key: "hall-load", title: "Load",
|
|
1088
|
-
match: r => r.
|
|
1184
|
+
match: r => r.load.size().greater(0n),
|
|
1089
1185
|
label: r => r.label,
|
|
1090
|
-
cells:
|
|
1186
|
+
cells: Plan.heatCells(Plan.values("load", (pct) => ({ value: some(pct) })), { min: 0, max: 100 }),
|
|
1091
1187
|
}),
|
|
1092
1188
|
]),
|
|
1093
1189
|
}),
|
|
1094
|
-
], ArrayType(Plan.Types.Series(
|
|
1190
|
+
], ArrayType(Plan.Types.Series(Hall)));
|
|
1095
1191
|
// <Plan axis={axis} data={halls} series={series} />
|
|
1096
1192
|
```
|
|
1097
1193
|
|
|
@@ -1100,12 +1196,12 @@ derives its numbers from its subtree:
|
|
|
1100
1196
|
|
|
1101
1197
|
```tsx
|
|
1102
1198
|
const OrderRow = RecursiveType((self) => StructType({
|
|
1103
|
-
name: StringType, act:
|
|
1199
|
+
name: StringType, act: DictType(DateTimeType, FloatType), children: DictType(StringType, self),
|
|
1104
1200
|
}));
|
|
1105
1201
|
Plan.series.table(OrderRow, {
|
|
1106
1202
|
key: "orders", title: "Orders", label: r => r.name,
|
|
1107
|
-
cells:
|
|
1108
|
-
children:
|
|
1203
|
+
cells: Plan.tableCells(Plan.values("act", (n) => ({ value: some(n) }))), // where a parent holds no values of its own…
|
|
1204
|
+
children: "children", aggregate: "sum", // …it shows its subtree's subtotal, at every depth
|
|
1109
1205
|
format: Format.Number({ maximumFractionDigits: 0n }),
|
|
1110
1206
|
})
|
|
1111
1207
|
```
|
|
@@ -1116,9 +1212,11 @@ member series, side by side, and each row has its own id:
|
|
|
1116
1212
|
```tsx
|
|
1117
1213
|
Plan.series.views(OpsRow, { key: "presses", title: "Presses", match: r => r.pick.equal("presses") }, [
|
|
1118
1214
|
Plan.series.span(OpsRow, { key: "press-jobs", title: "Press jobs", label: r => r.label,
|
|
1119
|
-
runs:
|
|
1120
|
-
Plan.series.chart(OpsRow, { key: "press-util", title: "Press · utilisation", label: r => r.label,
|
|
1121
|
-
|
|
1215
|
+
runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })) }),
|
|
1216
|
+
Plan.series.chart(OpsRow, { key: "press-util", title: "Press · utilisation", label: r => r.label,
|
|
1217
|
+
layers: [Plan.line(Plan.values("util", (pct) => ({ y: pct })))] }),
|
|
1218
|
+
Plan.series.table(OpsRow, { key: "press-sheets", title: "Press · sheets", label: r => r.label,
|
|
1219
|
+
cells: Plan.tableCells(Plan.values("sheets", (n) => ({ value: some(n) }))) }),
|
|
1122
1220
|
])
|
|
1123
1221
|
// a click on p03's utilisation row reports Plan.ref("press-util", "p03")
|
|
1124
1222
|
```
|
|
@@ -1127,10 +1225,12 @@ Plan.series.views(OpsRow, { key: "presses", title: "Presses", match: r => r.pick
|
|
|
1127
1225
|
| --- | --- | --- |
|
|
1128
1226
|
| **Layout and nesting** | | |
|
|
1129
1227
|
| `series={[s1, s2, …]}` | The list is the layout: one block per series, top to bottom; a pick's list order is the same layout. | `planTargetState` |
|
|
1130
|
-
| `
|
|
1131
|
-
| `
|
|
1228
|
+
| `Plan.items(field, { from, to } \| { at }, draw: (item, id, row) => look)` | Things people drag — runs, decisions, tiles, chips, marks — read from a `Dict` field of the row keyed by an id, each item's instants its named fields; `draw` gives everything its element takes but its key and instants. Over a collection keyed by an instant, or a field that is no `Dict`, it is refused in words naming the field. | `planTargetState`, `planRowDrop` |
|
|
1229
|
+
| `Plan.values(field, draw: (value, at, row) => look)` | Values read from a `Dict` field keyed by their instant: cells, a table series' cells, chart layers (`Plan.line` … `Plan.band`), a span's `ports`, a bucket row's `markers`, a group's `summary`. A collection keyed by `String` rides only an ordinal axis. | `planTargetState`, `planMeasures` |
|
|
1230
|
+
| `children: "children"` | More of this series: a `Dict` field of a `RecursiveType` entry, keyed by an id, to any depth. A parent's span bands (`rollup`, summed per quantity unit), heat `aggregate` (on its `scale`) or table subtotals (`aggregate`, `format`) derive from its subtree. | `planTargetState`, `planMeasures` |
|
|
1231
|
+
| `children: Plan.children(field, series: Series[])` | A step down to a `Dict` field of child rows of another type, keyed by an id, laid out like a top-level list; an array of step-downs gives several collections, in order. | `planTargetState` |
|
|
1132
1232
|
| `Plan.series.group(G, { key, title, label, children, summaryAggregate?, summary?, collapsed? })` **❗** | One strip per entry, its members the entry's children; the removed `by` form throws. | `planTargetState` |
|
|
1133
|
-
| `rows.groupToDicts(($, r) => r.hall, ($, _r, k) => k)` | Grouping is a data step; a paged source is grouped in its dataflow. | `planTargetState` |
|
|
1233
|
+
| `rows.groupToDicts(($, r) => r.hall, ($, _r, k) => k).map(($, members) => ({ members }))` | Grouping is a data step, each group an entry holding its rows in a field; a paged source is grouped in its dataflow. | `planTargetState` |
|
|
1134
1234
|
| `Plan.series.section(R, { key, title, collapsed?, meta?, value?, status?, summaryAggregate? }, members: Series[])` | A fixed titled block over series; it adds no path segment. | `planTargetState` |
|
|
1135
1235
|
| `Plan.series.views(R, { key, title, match?, children?, collapsed? }, members: Series[])` **❗** | One row per member per entry, adjacent; the entry's children follow under the first view row, and a seek lands there. A member may not declare `children`. Its children may be more of its own entries (a bare accessor), each drawn as the members' rows; a span member's `rollup` rolls them up. | `planTargetState` |
|
|
1136
1236
|
| `Plan.series.rows(R, { key, title }, [Plan.span({ key, label, rows? }), …])` | Hand-built rows as one block; a row's id is the series key plus the factory keys to it. | `planTargetState` |
|
|
@@ -1166,9 +1266,9 @@ element click is one callback over the same ref the popover resolver receives,
|
|
|
1166
1266
|
and the interaction state can live with the host.
|
|
1167
1267
|
|
|
1168
1268
|
```tsx
|
|
1169
|
-
Plan.
|
|
1170
|
-
quantity: Plan.quantity(
|
|
1171
|
-
Plan.heatCells(
|
|
1269
|
+
runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.ticket, state: job.state,
|
|
1270
|
+
quantity: Plan.quantity(job.sheets, { unit: "k sheets", format: Format.Number({ maximumFractionDigits: 0n }) }) })),
|
|
1271
|
+
cells: Plan.heatCells(Plan.values("load", (pct) => ({ value: some(pct) })), { min: 0, max: 100, fold: "max", format: Format.Number({ maximumFractionDigits: 0n }) }),
|
|
1172
1272
|
|
|
1173
1273
|
// The host holds what is selected, folded, opened and expanded; a write brings a row into view.
|
|
1174
1274
|
const ui = $.let(State.bind([Plan.Types.UiState], "ops.plan.ui", Plan.uiState({ collapsed: [Plan.ref("halls", "Hall 3")] })));
|
|
@@ -1237,15 +1337,15 @@ const onPatch = $.const(East.function([Plan.Types.PatchEvent(Hall)], NullType, (
|
|
|
1237
1337
|
library={[Plan.library.tab(templates.read(), { name: "Jobs", label: t => t.name })]} // a card drags onto a press
|
|
1238
1338
|
series={[Plan.series.span(Hall, {
|
|
1239
1339
|
key: "halls", title: "Halls", label: h => h.name, runs: _h => [],
|
|
1240
|
-
// A gesture on a press writes back into its hall through this
|
|
1241
|
-
children: Plan.children(
|
|
1340
|
+
// A gesture on a press writes back into its hall through this field.
|
|
1341
|
+
children: Plan.children("presses", [
|
|
1242
1342
|
Plan.series.span(Press, {
|
|
1243
1343
|
key: "presses", title: "Presses", label: (_p, k) => k,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
create: (drop
|
|
1248
|
-
|
|
1344
|
+
// Its jobs, by id: a run each, from its start to its end.
|
|
1345
|
+
runs: Plan.items("jobs", { from: "start", to: "end" }, (job) => ({ label: job.label, state: job.state })),
|
|
1346
|
+
edit: { // a run moves and resizes; a dropped card joins `jobs`
|
|
1347
|
+
create: (drop) => ({ // …under the id the canvas mints, drop.id
|
|
1348
|
+
label: drop.from.key,
|
|
1249
1349
|
start: drop.at.unwrap("time"), end: drop.at.unwrap("time").addWeeks(2n),
|
|
1250
1350
|
state: variant("proposed", variant("added", null)),
|
|
1251
1351
|
}),
|
|
@@ -1259,9 +1359,10 @@ const onPatch = $.const(East.function([Plan.Types.PatchEvent(Hall)], NullType, (
|
|
|
1259
1359
|
| Signature | Description | Example |
|
|
1260
1360
|
| --- | --- | --- |
|
|
1261
1361
|
| **What a gesture writes (on the series)** | | |
|
|
1262
|
-
| `edit: {
|
|
1263
|
-
| `edit:
|
|
1264
|
-
| `
|
|
1362
|
+
| `edit: { create: (drop: Plan.Types.Drop, entry, key) => Item }` beside `Plan.items(F, …)`; span / buckets / cards / events **❗** | A library card dropped on a row joins `F` as `create`'s item, under the id the canvas mints (`drop.id`, unique among every element it holds): `drop.from` is the card, `drop.at` the start of the bucket it landed in, on the axis arm. Elements given any other way than `Plan.items` fail the build, in words naming the read to give. | `planRowDrop` |
|
|
1363
|
+
| `edit: true` beside `Plan.items(F, { from, to } \| { at }, draw)` (span / buckets / cards / events) **❗** | A run, chip, tile or mark moves by its body, and a run or chip resizes by either end, with the pointer, a touch or the keyboard (#825), each gesture written into the item's named times. It moves in whole buckets; Shift moves one finer unit (a day under a week, an hour under a day, 15 minutes under an hour). Onto another row whose items are the same type, the item leaves its row's collection under its id and joins that row's under the same id, as one gesture over both entries. An element's key is its item's id — its identity across every row it can move to: a row's collection keeps its ids unique, so a move onto a row holding the id is refused (⊘ while dragging). Over a read of values, `edit` is refused: what people move is keyed by an id. | `planRowDrop` |
|
|
1364
|
+
| `edit: true` on `Plan.series.table` (its `cells`) or `Plan.tableSeries({ cells, edit: true })` — its cells `Plan.tableCells(Plan.values(F, draw))` **❗** | A number typed into a cell is the collection's value at the bucket's start: set, added where none is, deleted when cleared; an Integer collection takes a whole number. Its draw gives each value as stored (`value: some(v)`) — show it another way through `format`; any other draw, or cells given as a function, fails the build in words naming the series. On the canvas (#1438), Enter, F2, a double click or a digit opens an input over the cell: the Sheet's number field at the cell's width, with no stepper — below 480px of main, a tap opens it in a popover anchored to the cell. Enter writes, Esc leaves the cell as it was, ↑ and ↓ step the number, and Tab and Shift+Tab move along the row. Its text reads through the position's `format` (a percent's `82` or `82%` writes `0.82`), and an Integer collection's input refuses a fraction. A cell takes a number where its bucket holds one value of the collection, or none (every bucket in the window draws a cell then); a parent's cell drawing its children's subtotals takes none, and a bucket folding several values takes none, its tooltip naming the resolution its values take one at. Over rows read lean beside a nested series index, the first number typed on a row reads it whole, and every subtotal above it moves as e3 previews it. | — |
|
|
1365
|
+
| `children: "kids"` / `Plan.children("kids", …)` **❗** | A gesture below an entry writes back through its `children`, each a field of the entry; a field that is no `Dict` of rows keyed by an id, or a tree's field holding another type than the row's, fails the build. | `planRowDrop` |
|
|
1265
1366
|
| **The session (on the root)** | | |
|
|
1266
1367
|
| `editing={{ onUpdate: handle.write }}` with `data={handle}` **❗** | The inline adapter: Save applies the batch with `Editing.apply` over the handle's latest `Dict` and writes it once — idempotent across retries. It needs the handle itself as `data`. | Inline adapter shown above; the record-backed `planRowDrop` uses `Record.onApply` |
|
|
1267
1368
|
| `editing={{ onApply: fn(Editing.Types.ChangeSet(R, K)) => Editing.Types.ApplyResult }}` **❗** | The host's transaction, sync or async, checked against the base the drafts began from. A paged source needs `revision` and `refresh`; the host applies atomically, dedupes by `requestId` and returns the committed revision. | `planRowDrop` (`Record.onApply`) |
|
|
@@ -1275,15 +1376,58 @@ const onPatch = $.const(East.function([Plan.Types.PatchEvent(Hall)], NullType, (
|
|
|
1275
1376
|
pending, or incomplete / invalid while a check refuses its entry — each
|
|
1276
1377
|
entry marked for its own issues. The narrow layout's cards carry the same marks.
|
|
1277
1378
|
- **One gesture, one transaction.** The history item ends the frame's toolbar:
|
|
1278
|
-
issues · Undo · Redo · Discard · Save, and its
|
|
1279
|
-
undo; ⌘⇧Z / Ctrl+Shift+Z / Ctrl+Y redo.
|
|
1379
|
+
issues · conflicts (while one waits) · Undo · Redo · Discard · Save, and its
|
|
1380
|
+
status line. ⌘Z / Ctrl+Z undo; ⌘⇧Z / Ctrl+Shift+Z / Ctrl+Y redo.
|
|
1280
1381
|
- **The base is the source's.** An inline `Dict` is checked by its snapshot, a
|
|
1281
|
-
paged source by its revision.
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1382
|
+
paged source by its revision. When the source moves under the drafts —
|
|
1383
|
+
another person's save, a run's — each drafted entry is carried over to its
|
|
1384
|
+
newer value by East's three-way patch merge (the history item says
|
|
1385
|
+
"Checking your changes against the newest data" meanwhile): changes to
|
|
1386
|
+
different parts combine, and a part both changed to different values is a
|
|
1387
|
+
conflict. The draft keeps its own value there, and Save waits until every
|
|
1388
|
+
conflict is chosen, the draft's value or the source's, each choice one step
|
|
1389
|
+
Undo takes back. While one waits, the history item leads with the
|
|
1390
|
+
conflicts button (≠ and how many, before Issues), Save waits
|
|
1391
|
+
aria-disabled (its tooltip says why), each item holding one wears the warn
|
|
1392
|
+
ring until it is chosen (the optimiser's highlight in the warn colour: 1px
|
|
1393
|
+
at its edge, inside a cell, and a soft glow, over the highlight where an
|
|
1394
|
+
item has both), and the banner band under
|
|
1395
|
+
the toolbar — the ≠ mark, the word Conflict — says how many changes
|
|
1396
|
+
conflict, its Choose opening the same popover: a card for each conflict —
|
|
1397
|
+
the item as the banners name it (an event by its title), with Go to, which
|
|
1398
|
+
shows the item and selects it — the groups folded over it opened, and the
|
|
1399
|
+
view moved to its dates, week or day where it shows others — the popover
|
|
1400
|
+
staying open off it, by a side of the frame that leaves it clear where it
|
|
1401
|
+
would cover it (where neither does, and in a frame under 480px, it
|
|
1402
|
+
closes); the part's label; Yours and Theirs, each
|
|
1403
|
+
value in plain words; who changed it last, where the source said so when
|
|
1404
|
+
it refused a Save, and what both began from — with Keep all mine and Use
|
|
1405
|
+
all theirs at its foot. An event's start and end read as one range: a
|
|
1406
|
+
conflict at either is one card, Dates, the range whole on each side, its
|
|
1407
|
+
two chosen together and counted once — and with them the fields a move
|
|
1408
|
+
writes with the times, which place the event with them: a Roster duty's
|
|
1409
|
+
shift and group, an Allocation bin's bay. While it waits each side's place
|
|
1410
|
+
stands whole, and choosing takes one side's whole: Theirs takes their
|
|
1411
|
+
shift back with their times, never their times in your shift. It never
|
|
1412
|
+
opens by itself.
|
|
1413
|
+
A Save another write got there first is refused, and once its drafts carry
|
|
1414
|
+
over with nothing to choose it is sent again by itself — three times in a
|
|
1415
|
+
row at most, then the person's Save sends it; with a conflict, the person
|
|
1416
|
+
saves once it is chosen. A Save whose outcome is unknown retries the SAME request ("Retry
|
|
1417
|
+
request"), and drafts retire only once the source reads back with the
|
|
1418
|
+
Save in it ("Saved — reading it back…"): at the revision it committed or
|
|
1419
|
+
a later one, so another Save landing just after it holds nothing up.
|
|
1420
|
+
- **Kept through a reload.** The drafts are kept in the tab's storage as
|
|
1421
|
+
each gesture is made, and a reload of the tab brings them back, carried
|
|
1422
|
+
over to what the source holds then: as they were where it has not moved,
|
|
1423
|
+
combined with a change since or a conflict to choose where it has, and
|
|
1424
|
+
none where a Save landed before the reload. They are the workspace's they
|
|
1425
|
+
were made in: the same view over another workspace, repository or server
|
|
1426
|
+
in the tab holds none of them, before a reload or after. Undo starts at
|
|
1427
|
+
them. They go when the tab closes, and at a sign-out; storage that
|
|
1428
|
+
refuses them, or drafts a reload cannot restore, is a banner. The Sheet,
|
|
1429
|
+
the Calendar, the Roster, the Allocation board, the Schematic and the
|
|
1430
|
+
Flowchart keep theirs the same way.
|
|
1287
1431
|
- **Moves (#825).** A move along its row, or a resize, drafts the item's
|
|
1288
1432
|
instants in place. A move to another row takes the item out of its list
|
|
1289
1433
|
and appends it to the target row's, and a batch that takes an item and
|
|
@@ -1348,7 +1492,7 @@ unmapped fields hold their type's default.
|
|
|
1348
1492
|
| Event `ready`, `inspector` | Readiness returns `Editing.Types.Readiness` and can hold Save. A custom inspector receives the typed row and its session writer. | `planPrintWorks` |
|
|
1349
1493
|
| Event source, `entries` | The builder's `Slice.bindRecords` narrows a kind through a source named after its slot, over an interval index: its events in the window and its backlog read through it (the Calendar, #1343; the Plan, #1344), and the Roster's week of duties and the Allocation's day of bins, the range their own navigation writes (#1368, #1371). `entries: Data.bindPaged(record)` supplies original-key reads for editing. A kind's own `slice` and `filter` are refused at build, naming the change. See Indexed records and shared Slice above. | `calendarRecordIndexes`, `calendarOperations`, `planPrintWorks`, `planWindows`, `rosterWindowed`, `allocationWindowed` |
|
|
1350
1494
|
| Event `window`, `backlogWindow`, `entries` | The separate single-index paging form, described under Large records below. A kind narrowed through a source of its builder's rail takes neither `window` nor `backlogWindow`, and a Plan refuses it on a kind placed on a paged resource kind, which is read a cell at a time through its source (#1039). | `calendarWindowed` |
|
|
1351
|
-
| `Schedule.periods(Record.bind(r
|
|
1495
|
+
| `Schedule.periods(Record.bind(r), { every, status: { field, draft, published }, dismissed?, entries? })` **❗** | What a builder publishes (#1367): the app's record of periods, `Dict<DateTime, P>`, a row each keyed by its start — a UTC midnight, a week's first day by the builder's week start for `"week"`, or a `"day"`. `status.field` names a variant field of `P`, not an Option, and `draft` and `published` two of its cases; the published case holds `Null` or `{ at: DateTime }`. `dismissed`, the Roster's, names a `Set<String>` field: the proposals a week dismissed. `entries: Data.bindPaged(r)` reads the periods by key — the period, the one before it, the open picker's span, each one key search and one page — else the record is read whole. Without `periods`, a builder has no status chip and no Publish. | `rosterWarehouse`, `rosterWindowed`, `allocationKilnYard`, `allocationWindowed` |
|
|
1352
1496
|
|
|
1353
1497
|
A period whose status is the published case is read-only in every layout,
|
|
1354
1498
|
phones included, and so is one in a case that is neither, its chip naming the
|
|
@@ -1401,7 +1545,7 @@ key, never its row: a draft can move the event, and the link follows it.
|
|
|
1401
1545
|
|
|
1402
1546
|
```tsx
|
|
1403
1547
|
const presses = $.let(Record.bind(planPrintPresses, []));
|
|
1404
|
-
const jobs = $.let(Record.bind(planLinkJobs
|
|
1548
|
+
const jobs = $.let(Record.bind(planLinkJobs));
|
|
1405
1549
|
const stock = $.let(Data.bind(planLinkStock));
|
|
1406
1550
|
<Plan axis={axis}
|
|
1407
1551
|
resources={{ presses: Schedule.resources(presses.read(), { name: "Presses", icon: "print", label: p => p.name, group: p => p.hall }) }}
|
|
@@ -1418,7 +1562,7 @@ const stock = $.let(Data.bind(planLinkStock));
|
|
|
1418
1562
|
|
|
1419
1563
|
| Signature | Description | Example |
|
|
1420
1564
|
| --- | --- | --- |
|
|
1421
|
-
| `resources: { [slot]: Schedule.resources(rows, { name, icon, label, … }) }` + `events: { [slot]: Schedule.events(Record.bind(r
|
|
1565
|
+
| `resources: { [slot]: Schedule.resources(rows, { name, icon, label, … }) }` + `events: { [slot]: Schedule.events(Record.bind(r), { name, icon, title, start, end \| at, resource: { field, of }, … }) }` **❗** | Event kinds over records, on the resources' rows, ahead of `data`'s. `resources` without `events`, or a `resource` naming a slot `resources` has not, fails the build. | `planEvents`, `planPrintWorks` |
|
|
1422
1566
|
| event kinds on `Plan.axis.number` / `Plan.axis.ordinal` **❗** | Refused at build, naming the axis and the kinds: an event is scheduled in time. An axis held in a variable is refused in the same words as the Plan is evaluated. | — |
|
|
1423
1567
|
| `data`, beside `events` or `rows` | Optional: a Plan of event kinds, or of `rows` alone, needs none. A Plan with no rows from any source fails the build. | `planEvents` |
|
|
1424
1568
|
| `rows: [Plan.chart({ …, pinned? }), Plan.over(…), …]` | Read-only rows after the event kinds' and `data`'s, each a fixed block; a pinned one sits under the ruler. | `planPrintWorks`, `planEventLinks` |
|
|
@@ -1479,7 +1623,7 @@ places it bare.
|
|
|
1479
1623
|
| Signature | Description | Example |
|
|
1480
1624
|
| --- | --- | --- |
|
|
1481
1625
|
| the toolbar | Every control the Plan draws outside its canvas, one row at every width: the rail of its one `Slice.bindRecords` folds first, its summary after every step of it, then the Plan's own steps and the key search into its icon; the fold toggle, the resolution and the overlaps chip into the date navigator's View, one move; the history item folds last, and below that, at a phone's width, the rail's icon drops its caret, the history and Save staying in the row. | `planTargetState`, `planRowDrop`, `planPrintWorks` |
|
|
1482
|
-
| the banners | Each session's — `data`'s, and each event kind's titled with its name (#1194): a Save's conflict, a refusal, an unknown outcome with Retry
|
|
1626
|
+
| the banners | Each session's — `data`'s, and each event kind's titled with its name (#1194): changes that conflict with newer ones, whose Choose opens the history item's conflicts, a Save's conflict, a refusal, an unknown outcome with Retry; each leaves when what it reports does, and one kind's leaves the other kinds' commits standing. | `planRowDrop`, `planPrintWorks` |
|
|
1483
1627
|
| the footer | The counts — the event kinds' events in the window, their backlog, the changes pending and the last save of an event kind's record — then the author's `footer` and the one loading line. | `planPrintWorks` |
|
|
1484
1628
|
| `style={{ height: "fill" }}` / `maxHeight` | The whole Plan's: the canvas fills main and scrolls its own rows. | `planTargetState` |
|
|
1485
1629
|
| a host's height, and no `style.height` | The canvas fills main and scrolls there; a host that gives none lets it grow with its rows. | — |
|
|
@@ -1520,8 +1664,8 @@ const customers = $.let(Data.bind(planPrintCustomers));
|
|
|
1520
1664
|
const jobTemplates = $.let(Data.bind(planPrintJobTemplates));
|
|
1521
1665
|
// One rail (#1344): the templates' and the customers' sources narrow their tabs.
|
|
1522
1666
|
const slice = $.let(Slice.bindRecords("ops.plan", {
|
|
1523
|
-
jobTemplates: { source: Data.bindPaged(planPrintJobTemplates, { indexes: [
|
|
1524
|
-
customers: { source: Data.bindPaged(planPrintCustomers, { indexes: [
|
|
1667
|
+
jobTemplates: { source: Data.bindPaged(planPrintJobTemplates, { indexes: ["plan_print_job_templates_by_group"] }), fields: { group: { label: "Job templates" } } },
|
|
1668
|
+
customers: { source: Data.bindPaged(planPrintCustomers, { indexes: ["plan_print_customers_by_district"] }), fields: { district: { label: "District" } } },
|
|
1525
1669
|
}));
|
|
1526
1670
|
<Plan axis={axis} slice={{ slice }}
|
|
1527
1671
|
resources={{ presses: Schedule.resources(presses.read(), { name: "Presses", icon: "print", label: p => p.name, group: p => p.hall }) }}
|
|
@@ -1550,7 +1694,7 @@ const slice = $.let(Slice.bindRecords("ops.plan", {
|
|
|
1550
1694
|
| --- | --- | --- |
|
|
1551
1695
|
| `Plan.library.events()` **❗** | Every event kind's templates, under the kind's name and then the template's `group` (`Print job · Jobs`). A card is its kind's icon, its name and `Print job · 6 h · presses`: its kind, how long it runs (an instant kind's runs no time) and the resource kinds it is placed on. Dropped on a resource's row it makes its event there (#1196). Each kind's list follows its source of the rail (#1344): the tab counts what it shows (`Events 8 of 12`) and, when the rail leaves a list none, says so with Clear filters for it. Refused on a Plan with no event kinds. | `planEvents`, `planPrintWorks` |
|
|
1552
1696
|
| `Plan.library.backlog()` **❗** | Every event kind's unscheduled events (a kind with Option times and a `backlog`), grouped by when they are due: Due this week (overdue included), Due next week, Later, No date — UTC weeks, from the Monday of the week the axis's `now` is in, else the week the Plan mounted in. A card is `6 h · Press A · due Fri 16` (`Unassigned` on no resource). It is read again as a kind's record commits, with every kind's drafts in place (#1196): a card dropped on a resource's row is scheduled and leaves it, an event dropped on the tab is unscheduled and joins it. Collapsed, the pane's rail counts it. A kind narrowed through its source (#1344) shows its narrowing's backlog, the tab counting `N of M` — what shows beside the stored events the choices hide — and its Clear filters keeping the window. Refused on a Plan with no event kinds. | `planEvents`, `planPrintWorks` |
|
|
1553
|
-
| `Plan.library.series()` **❗** | `Pick.Panel`'s list in the order the canvas draws: each resource kind and its measures, the event kinds, `data`'s series when they are picked (`pick`), and the Plan's `rows` — a hand-built row by its key, a `Plan.over` series by its own. Hiding an event kind hides its elements everywhere, its Unassigned row and any row only it draws on with them (a resource's first row is its own, and stays), and leaves it out of every figure of the canvas's — the footer's counts, the inspector's stats and a row's facts — nothing saying so, its backlog the Backlog tab's still; a resource kind its rows; a measure, or one of `rows`, its rows — a `Plan.over` series' nested series with it. `data`'s series hide through their pick
|
|
1697
|
+
| `Plan.library.series()` **❗** | `Pick.Panel`'s list in the order the canvas draws: each resource kind and its measures, the event kinds, `data`'s series when they are picked (`pick`), and the Plan's `rows` — a hand-built row by its key, a `Plan.over` series by its own. Hiding an event kind hides its elements everywhere, its Unassigned row and any row only it draws on with them (a resource's first row is its own, and stays), and leaves it out of every figure of the canvas's — the footer's counts, the inspector's stats and a row's facts — nothing saying so, its backlog the Backlog tab's still; a resource kind its rows; a measure, or one of `rows`, its rows — a `Plan.over` series' nested series with it. `data`'s series hide through their pick, kept per viewer under its `key`; the rest under the Plan's `id`, and only while the library lists this tab. Refused on a Plan with nothing a viewer can hide. | `planPrintWorks` |
|
|
1554
1698
|
| `Plan.library.tab(rows: Dict<K, R>, { name, icon?, label, meta?, group?, drop?, source? })` **❗** | `source` names the rail's source it follows, by its rows' keys (`Customers 6 of 16`, #1344). A card per row, its rows read as `Schedule.resources` reads them, accessors `(row, key)`: its label, its meta, the tab's icon, grouped by `group`, searched by key, label and meta. A click selects a card, and a click on the selected card lets it go. A key of another type than String is its card's key as East prints it. Its cards drag onto the rows of `data` whose series makes an item of a card (`edit.create`) — the drop's `from.key` the card's key, so `canDrop` and `create` read it as a Library card's — and the Plan takes them with no `id` or `sources` (#1259). | `planEvents`, `planPrintWorks`, `planRowDrop` |
|
|
1555
1699
|
| `drop: (row, key) => Schedule.patch(KindRowType, { … })` **❗** | With it, each card drags onto an event too, while one of the patch's kind is on the canvas. What a card dropped on an event sets (#1196): the patch's type names the event kind it lands on, so a patch over no kind's row type, or over one two kinds share, is refused at build, naming the tab. Dropped on the event under the pointer it sets the patch's fields as one step (`Harbour Arts Society → Spring catalogue` on the ghost); refused between events (`Drop a customer onto an event`) and on another kind's (`Stops take no customer`). | `planEvents`, `planPrintWorks` |
|
|
1556
1700
|
| `Schedule.patch(R, { field: value, … })` / `Schedule.Types.Patch(R)` | A patch over an event kind's row type: every field an `Option`, each field left out `none`; one set to `none` clears an Option field. | `planEvents` |
|
|
@@ -1645,14 +1789,16 @@ fields. Neither record is ever read whole: not to draw, not to edit, not to
|
|
|
1645
1789
|
save.
|
|
1646
1790
|
|
|
1647
1791
|
```tsx
|
|
1648
|
-
// Package side: the jobs record
|
|
1649
|
-
//
|
|
1650
|
-
//
|
|
1792
|
+
// Package side: the jobs record declares its two indexes, each keyed by Schedule's own function.
|
|
1793
|
+
// indexes: (index) => ({
|
|
1794
|
+
// jobs_by_day: index.keys(East.function(… Schedule.days(start, end) …)),
|
|
1795
|
+
// jobs_unscheduled: index.keys(East.function(… Schedule.unscheduled(job.start, job.due) …)),
|
|
1796
|
+
// }),
|
|
1651
1797
|
<Reactive>{$ => {
|
|
1652
1798
|
const presses = $.let(Data.bind(pressesTask)); // read whole: a paged kind's jobs are read a cell at a time (below)
|
|
1653
|
-
const jobs = $.let(Record.bind(jobsRecord
|
|
1654
|
-
const days = $.let(Data.bindPaged(jobsRecord, { index:
|
|
1655
|
-
const backlog = $.let(Data.bindPaged(jobsRecord, { index:
|
|
1799
|
+
const jobs = $.let(Record.bind(jobsRecord));
|
|
1800
|
+
const days = $.let(Data.bindPaged(jobsRecord, { index: "jobs_by_day", join: true }));
|
|
1801
|
+
const backlog = $.let(Data.bindPaged(jobsRecord, { index: "jobs_unscheduled", join: true }));
|
|
1656
1802
|
const entries = $.let(Data.bindPaged(jobsRecord)); // one job by its key
|
|
1657
1803
|
return <Plan axis={axis}
|
|
1658
1804
|
resources={{ presses: Schedule.resources(presses.read(), { name: "Presses", icon: "print", label: p => p.name, sub: p => some(p.hall) }) }}
|
|
@@ -1685,43 +1831,46 @@ presses asks it again, there.
|
|
|
1685
1831
|
An event kind placed on a paged resource kind reads only the events of the
|
|
1686
1832
|
resources around the rows in view: its source lists a **series index** of its
|
|
1687
1833
|
record beside the interval index, and a **summary index** with it
|
|
1688
|
-
(`planWindows`). A series index files each event under its resource
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1834
|
+
(`planWindows`). A series index files each event once under its resource, at
|
|
1835
|
+
the level its length fits — its tile, or a coarser one up to a year — and past
|
|
1836
|
+
a year under each year it touches, carrying its row (#1433); a summary holds,
|
|
1837
|
+
under each resource and tile, how many events touch it and the milliseconds of
|
|
1838
|
+
it they cover (the **e3** skill). A kind on a paged resource kind read any other way — its source
|
|
1692
1839
|
listing its interval index alone, or a summary and no series; no source, its
|
|
1693
1840
|
record read whole or the days in view through its day index; or placed
|
|
1694
1841
|
beside another resource kind — is refused, naming both declarations over the
|
|
1695
|
-
kind's own fields: `
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1842
|
+
kind's own fields: `index.series({ key: "press", from: "start", to: "end",
|
|
1843
|
+
tile: "week" })` and `index.summary({ key: "press", from: "start", to: "end",
|
|
1844
|
+
tile: "month" })` among the jobs record's indexes. Nothing else in the Plan
|
|
1845
|
+
changes:
|
|
1699
1846
|
|
|
1700
1847
|
```tsx
|
|
1701
|
-
// Package side, beside the interval index:
|
|
1702
|
-
//
|
|
1703
|
-
//
|
|
1848
|
+
// Package side, among the jobs record's indexes, beside the interval index:
|
|
1849
|
+
// jobs_by_press_week: index.series({ key: "press", from: "start", to: "end", tile: "week" }),
|
|
1850
|
+
// jobs_by_press_month: index.summary({ key: "press", from: "start", to: "end", tile: "month" }),
|
|
1704
1851
|
const slice = $.let(Slice.bindRecords("ops.plan", {
|
|
1705
|
-
job: { source: Data.bindPaged(jobs, { indexes: [
|
|
1852
|
+
job: { source: Data.bindPaged(jobs, { indexes: ["jobs_by_time", "jobs_by_press_week", "jobs_by_press_month"] }), range: "start" },
|
|
1706
1853
|
}));
|
|
1707
1854
|
```
|
|
1708
1855
|
|
|
1709
|
-
A **cell** is the events of a block of fifty of the paged kind's resources
|
|
1710
|
-
one
|
|
1856
|
+
A **cell** is the events of a block of fifty of the paged kind's resources at
|
|
1857
|
+
one level — of a kind its source of the slice narrows, fifty of the narrowing's,
|
|
1711
1858
|
in its index's order, so only the narrowed resources in view have their events
|
|
1712
1859
|
read, and a choice reads none before its own rows are drawn
|
|
1713
1860
|
(`planSeriesByHall`, #1344). The Plan wants the blocks meeting the rows in view and twenty-five
|
|
1714
|
-
resources either side,
|
|
1715
|
-
and the periods laid out beyond each edge —
|
|
1716
|
-
|
|
1717
|
-
|
|
1861
|
+
resources either side, at each level over the tiles the range it reads touches
|
|
1862
|
+
— its window and the periods laid out beyond each edge — one tile either side,
|
|
1863
|
+
and the tile before those, whose events may run into them: a range a resource
|
|
1864
|
+
a level, whatever the span (#1433). Every wanted cell's searches go in one
|
|
1865
|
+
request, which counts its entries before one is read, and the entries of the
|
|
1866
|
+
tiles it does not hold in one more. A pan or a
|
|
1718
1867
|
scroll within the cells it holds reads nothing, and a commit reads the wanted
|
|
1719
1868
|
cells again, the old standing in until they land. The rail's window and its
|
|
1720
1869
|
exact count stay the interval index's.
|
|
1721
1870
|
|
|
1722
1871
|
| Signature | Description | Example |
|
|
1723
1872
|
| --- | --- | --- |
|
|
1724
|
-
| a source listing `
|
|
1873
|
+
| a source listing `index.series` + `index.summary` beside the interval index **❗** | The kind read a cell at a time, two requests a change: a block of fifty resources, a range a resource a level, whatever the span (#1433). An event past a year is under every year it touches and draws once; the drafts draw where they place their events, read or not; a link's event end the cells do not hold is read by its key, in one read. | `planWindows`, `planRecordIndexes` |
|
|
1725
1874
|
| past 10,000 events in the cells wanted | The summary's cells instead, at the largest of its tiles that divides the canvas's bucket — a day, an ISO week (which divides no month), a month, a quarter or a year — each resource's share of each bucket booked for bars and chips, how many events touch it for tiles and marks. With none that divides it, the events over the window while their count there is within the cap; past it the rows are hatched, and a banner names how many events there are and the resolution that shades them. | `planWindows` |
|
|
1726
1875
|
| a narrowing | A series index led by the narrowed fields serves it exactly; any other field is asked of the cells' events in one read. A summary shades a narrowing only when led by its fields. | `planWindows` |
|
|
1727
1876
|
| the footer and the inspector | The window's events counted exactly from the source, as the rail counts them; their hours and overlaps over the rows drawn — "Hours in view", once the cells have landed — and the backlog read whole through the source's unset start. A resource selected while its cells are not read — scrolled out of the blocks read, or before the first read lands — shows its figures as placeholders until they land, never 0. | `planWindows` |
|
|
@@ -1798,10 +1947,12 @@ and `sheetMessages` — are `@elaraai/e3-ui-components`'.
|
|
|
1798
1947
|
/** @jsxImportSource @elaraai/e3-ui */
|
|
1799
1948
|
import { Box, Reactive } from '@elaraai/east-ui';
|
|
1800
1949
|
import { Record, Sheet } from '@elaraai/e3-ui';
|
|
1801
|
-
// Package side: export const jobs = e3.record("jobs", DictType(StringType, Job),
|
|
1802
|
-
//
|
|
1950
|
+
// Package side: export const jobs = e3.record("jobs", DictType(StringType, Job), {
|
|
1951
|
+
// initial: new Map([…]),
|
|
1952
|
+
// mutations: (mutation) => ({ patch: mutation.patch() }),
|
|
1953
|
+
// });
|
|
1803
1954
|
<Reactive>{$ => {
|
|
1804
|
-
const record = $.let(Record.bind(jobs
|
|
1955
|
+
const record = $.let(Record.bind(jobs));
|
|
1805
1956
|
return (
|
|
1806
1957
|
<Box height="560px">
|
|
1807
1958
|
<Sheet record={record} columns={{
|
|
@@ -1815,10 +1966,10 @@ import { Record, Sheet } from '@elaraai/e3-ui';
|
|
|
1815
1966
|
```
|
|
1816
1967
|
|
|
1817
1968
|
```
|
|
1818
|
-
<Sheet record={Record.bind(r
|
|
1969
|
+
<Sheet record={Record.bind(r)} | data={rows} id="id" | slice={{ slice: Slice.bindRecords(key, { rows, … }) }} columns={{ start: Sheet.column.date(Row, {…}), qty: Sheet.column.quantity(Row, Driver, {…}), … }} library?={[…]} inspector? /> — the planning SPREADSHEET in its builder frame: typed columns over its rows (date · quantity + unit · integer · text · register lookup / reference / enum · a set of register members · a directed `from > to` LINK between register members as a TYPED value · stamped read-only codes · a custom parse / print pair), a blank tail that invites the next row, typed parsing with a docked candidate strip (nothing ever floats over the grid), a copilot that fills cells and proposes whole rows from author East functions, one rail over its Slice.bindRecords with the LENS — the `rows` source's narrowing drawn over the record's rows, only the rows it keeps, each at its row number (#1349) — and saved-view tabs over every source's narrowing, drag and drop, Excel round-tripping, and a key search over a paged source. Declared the way east-ui's Table and the Plan are: every per-row fact is an accessor, the builders take the row type FIRST, and nothing at the author's side is addressed by a string name
|
|
1819
1970
|
├─ The frame: ONE toolbar holding every control the sheet has, in this order — the view tabs, the lens's count line (while the `rows` source narrows the rows, #1349), the key search, the rail over its `Slice.bindRecords` and its summary (each source a narrowing hides rows of, named in its own words, #1358), the scope badge and the history item (a sheet with none of them, read only with no slice, has no toolbar). It is one row at every width: the rail folds first, then its summary, then the sheet's own steps — the tabs into `+n`, the count line, the strip closing up — then the key search into its icon (which opens the box in a popover, the focus in it) and the view tabs into one chip naming the open view, its menu every view, `+ TAB` and the open view's close (#1221); where the sheet has a key search, the chip folds last into the search's popover, its views over the box (#1345); and the history item last, to its buttons — below that, where the row is still too wide, the rail's icon drops its caret, so the history and Save stay in the row at a phone's width; on a touch screen every control there is a 44px tap target. The banners — a Save's conflict naming its rows and who changed the record last, a refusal with its reason, an unknown outcome and a failed confirmation read each with Retry, the out-of-date notice with Discard, each source the library follows whose narrowing could not be read, with Retry, and an `entry` the record does not hold; main — the grid, filling the room the panes leave and scrolling its own rows, the strip docked under it; the footer — the sheet's, over a record its last save, and over the slice's `rows` source the rows a Save took out of the narrowing. ⌘Z undoes and ⇧⌘Z / ⌘Y redo from anywhere in the frame; a field being typed into keeps its own undo. The sheet fills its parent and draws no border: give it a box of its own height
|
|
1820
1971
|
├─ Rows — ONE of three sources (two, or none, is refused):
|
|
1821
|
-
│ ├─ record — `Record.bind(r
|
|
1972
|
+
│ ├─ record — `Record.bind(r)` over a `Dict` record: its entries are the rows, in key order, each row's id its key's text (any key type: a non-String key by its `.east` text). `window={Data.bindPaged(r)}` reads the same record a window at a time. `entry={{ key, rows: F, id: I }}` is one entry's Array field `F`, in its own order, each row identified by the `String` field `I`; each entry keeps its own drafts, and a key the record does not hold opens empty and read only, named in a banner. With `group`, the entry's field holds groups, or `Sheet.Types.Entry(P, "lines")` entries with loose rows between them (`id` then names a field of both types); a record of groups is grouped the same way. Save is ONE patch commit, checked against what each row was when its edit began, confirmed by the rows the record reads back. Over a record `id`, `onApply` and `onUpdate` are refused (the record identifies and commits its rows); `entry` and `window` need a record
|
|
1822
1973
|
│ ├─ data — the host's rows: an `Array<R>` value / expression or a `$.let`-bound whole-value handle (`State.bind` / `Data.bind`) for the INLINE arm; a paged source for a WINDOWED arm — a `$.let`-bound `Data.bindPaged(…)` handle, recognised by its East type: `pinned` when it names its snapshot, which `onApply` needs, `paged` when it is the shape a UI exported before `revision` / `refresh`, which names none — positional (`Array<R>` windows) or keyed (`Dict<String, R>` windows; the key is the row id). When a pinned source's `revision()` moves (`Data.bindPaged` follows each write to its dataset), the sheet re-reads its resident windows at the new snapshot IN PLACE: the rows on screen stay until theirs land (no remount, no empty frame), the scroll position and row heights hold, and a standing key search is cleared. Rows not read draw as the shared run — the sheet's own rows at its pitch, a flat sheet's real row numbers — and a failure stays where it happened: a window the source cannot read is said once, in its run's first row in view (`Couldn't read rows 201–400`, Retry), its reason on the footer's loading line, while the windows around it keep working and the rows after it keep their numbers; a `total()` or `revision()` that throws is said on the footer's loading line with a Retry while the rows stay; only a source that fails before anything lands replaces the grid, with a Retry. A row that throws while it draws is a one-row diagnostic. A `Dict` inline is refused (a sorted map would sit rows in key order, not the planner's) — a record's entries come through `record`. `id` (required on a positional source) names the `String` field that identifies a row; over entries of groups and loose rows, a field of BOTH types
|
|
1823
1974
|
│ └─ the `rows` source of `slice` — slice={{ slice: Slice.bindRecords(key, { rows: { source: Data.bindPaged(r, { indexes }), fields, range?, search? }, … }) }} (#1345): the source's record's rows, every one in the record's order, each row's id its record key, a grouped sheet's groups its records; while the source narrows them, the sheet draws the narrowing over them as the LENS (#1349), a filtered spreadsheet — only the rows the narrowing keeps, one after another in the record's order, each at its row number, its number marked; nothing stands between them for the rows it leaves out. e3 places the narrowing's rows from its index alone, and the lens reads only the record's segments that hold matches near the view — a page and one ask of which of its rows the narrowing holds, each — so rows with no match are never read, whatever the record's size; a segment not read yet is one band of its matches at the rows' pitch, so the sheet is as tall as its matches before their rows land and after, and they land where the band stood. The count line says `n matches`, n e3's count. A segment whose rows disagree with where e3 placed the narrowing's is one failed band saying the record changed, with a Retry — never a match the index does not hold. A grouped sheet's lens narrows its groups: a matching group's every line shows. Edits through `onApply={Record.onApply(record, { keyed: true })}`, a Save that takes rows out of the narrowing said in the footer; the key search seeks in the record's order and lists the rows its key finds wherever they are, a row the narrowing leaves out naming the choices that hide it as the rail words them — `· hidden by Status: Ready — picking it clears that filter`, which of them hide it asked of e3 through the record's indexes, `· hidden by the filters` until it says — and a pick of it clears just those choices, every other one kept (every choice of the source, before e3 has said), and goes to the row at its number; a choice cleared so is a change of the rail like any other, a saved view's tab marked changed and Esc putting it back. `record`, `data`, `window` and `entry` beside it are refused, each naming the change
|
|
1824
1975
|
├─ Props:
|
|
@@ -1890,9 +2041,11 @@ read and shaped before the sheet sees them — commit through `Record.onApply`
|
|
|
1890
2041
|
over that entry's rows:
|
|
1891
2042
|
|
|
1892
2043
|
```tsx
|
|
1893
|
-
// Package side: export const plans = e3.record("plans", DictType(StringType, Plan),
|
|
1894
|
-
//
|
|
1895
|
-
|
|
2044
|
+
// Package side: export const plans = e3.record("plans", DictType(StringType, Plan), {
|
|
2045
|
+
// initial: new Map([["week", { jobs: [] }]]),
|
|
2046
|
+
// mutations: (mutation) => ({ patch: mutation.patch() }),
|
|
2047
|
+
// });
|
|
2048
|
+
const record = $.let(Record.bind(plans));
|
|
1896
2049
|
const jobs = $.let(record.read().get("week").jobs);
|
|
1897
2050
|
const onApply = $.const(Record.onApply(record, {
|
|
1898
2051
|
entry: "week",
|
|
@@ -1960,7 +2113,7 @@ the row projection, so they follow the host's rows, not an open edit.
|
|
|
1960
2113
|
| Signature | Description | Example |
|
|
1961
2114
|
| --- | --- | --- |
|
|
1962
2115
|
| **The rows** | | |
|
|
1963
|
-
| `record: Record.bind(r
|
|
2116
|
+
| `record: Record.bind(r)` **❗** | A `Dict` record bound with its patch door; its entries are the rows, in key order. | `sheetBasic` |
|
|
1964
2117
|
| `window: Data.bindPaged(r)` **❗** | The same record's entries, a window at a time; never with `entry`. | `sheetPaged` |
|
|
1965
2118
|
| `entry: { key, rows: F, id: I }` **❗** | One entry's Array field `F`, in its own order; `I` a `String` field of its rows. | `sheetWeeks` |
|
|
1966
2119
|
| `entry` + `group: Sheet.group(P, "lines", …)` | `F` holds groups, or `Sheet.Types.Entry(P, "lines")` entries, loose rows between the groups. | `sheetBatches`, `sheetLoose` |
|
|
@@ -2028,9 +2181,9 @@ draws no border: give it a box of its own height.
|
|
|
2028
2181
|
Its flows come from one of two sources:
|
|
2029
2182
|
|
|
2030
2183
|
- **`record`** — an e3 record of `Flowchart.Types.Flows`, flows by name, bound
|
|
2031
|
-
with its patch mutation: `Record.bind(r
|
|
2184
|
+
with its patch mutation: `Record.bind(r)`. The usual
|
|
2032
2185
|
case: the canvas shows the flow opened last, else `flow`, else the first flow
|
|
2033
|
-
by name. A record always holds flows by name —
|
|
2186
|
+
by name. A record always holds flows by name — a patch mutation writes
|
|
2034
2187
|
only keyed records — so a lone flow is a record of one entry, or the host's
|
|
2035
2188
|
`data`; a record of one flow is refused at build.
|
|
2036
2189
|
- **`data`** — the host's flows by name or one flow, the value's type picking
|
|
@@ -2081,24 +2234,28 @@ import { Flowchart, Record } from '@elaraai/e3-ui';
|
|
|
2081
2234
|
import e3 from '@elaraai/e3';
|
|
2082
2235
|
|
|
2083
2236
|
// The depot's flows, by name: each written as a literal, checked when the package builds.
|
|
2084
|
-
export const depotFlows = e3.record("depot_flows", Flowchart.Types.Flows,
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2237
|
+
export const depotFlows = e3.record("depot_flows", Flowchart.Types.Flows, {
|
|
2238
|
+
initial: Flowchart.values({
|
|
2239
|
+
"Inbound parcels": {
|
|
2240
|
+
description: "From the trailer to the van",
|
|
2241
|
+
lanes: [{ key: "intake", label: "Intake" }, { key: "sort", label: "Sort" }, { key: "load", label: "Load" }],
|
|
2242
|
+
states: [
|
|
2243
|
+
{ key: "ARV", label: "Arrived", lane: "intake" },
|
|
2244
|
+
{ key: "CH*", label: "Sort chutes", lane: "sort", members: 12n },
|
|
2245
|
+
{ key: "LDD", label: "Loaded", lane: "load" },
|
|
2246
|
+
],
|
|
2247
|
+
links: [{ from: "ARV", to: "CH*", trigger: "route" }, { from: "CH*", to: "LDD" }],
|
|
2248
|
+
triggers: [{ key: "route", label: "route", owner: "sort-planner" }],
|
|
2249
|
+
},
|
|
2250
|
+
}),
|
|
2251
|
+
mutations: (mutation) => ({
|
|
2252
|
+
patch: mutation.patch(),
|
|
2253
|
+
}),
|
|
2254
|
+
});
|
|
2098
2255
|
|
|
2099
2256
|
const flowchart = East.function([], UIComponentType, _$ => (
|
|
2100
2257
|
<Reactive>{$ => {
|
|
2101
|
-
const flows = $.let(Record.bind(depotFlows
|
|
2258
|
+
const flows = $.let(Record.bind(depotFlows));
|
|
2102
2259
|
return (
|
|
2103
2260
|
<Box height="480px">
|
|
2104
2261
|
<Flowchart record={flows} flow="Inbound parcels" library={[Flowchart.library.flows()]} />
|
|
@@ -2160,7 +2317,7 @@ the same request id again, and e3 answers it with the first write's commit
|
|
|
2160
2317
|
rather than writing twice (#1275):
|
|
2161
2318
|
|
|
2162
2319
|
```tsx
|
|
2163
|
-
const flows = $.let(Record.bind(depotFlows
|
|
2320
|
+
const flows = $.let(Record.bind(depotFlows));
|
|
2164
2321
|
const onApply = $.const(East.asyncFunction([StringType, PatchType(Flowchart.Types.Flows)], Editing.Types.ApplyResult, ($2, requestId, patch) => {
|
|
2165
2322
|
const outcome = $2.let(flows.commit.patch(requestId, patch));
|
|
2166
2323
|
const answer = $2.let(variant("rejected", [{ entry: "", row: none, field: none, message: "The write was refused" }]), Editing.Types.ApplyResult);
|
|
@@ -2178,7 +2335,7 @@ const onApply = $.const(East.asyncFunction([StringType, PatchType(Flowchart.Type
|
|
|
2178
2335
|
<Flowchart record={…} | data={…} /> — state-transition flowchart in its builder frame: states as nodes in ORDERED phase lanes (layout derived — no coordinates), H/V-routed transition arrows, optional per-link decision triggers (lettered diamonds); dim-ladder highlight built in; the inspector, on by default, shows what is selected
|
|
2179
2336
|
├─ The frame (#1245): ONE toolbar holding every control the flowchart has — find state (a search over the open flow's states by key and by label; a pick selects the state and scrolls it into view), LR · TD, the freshness chip, at its end the rail of its one `Slice.bindRecords` and its summary, naming each source a narrowing hides rows of in its own words (#1346, #1358), and where it edits the history item (#1246, #1247: the open flow's session — its status, its issues, Undo, Redo, Discard and Save). It is one row at every width: the rail folds first, its summary after every step of it, then the freshness chip goes, LR · TD folds into one chip naming the orientation (its menu both) and find state's box into its icon (which opens the box in a popover, the focus in it) — with a rail, LR · TD then folds into that popover, its View, one move, so a phone's row keeps the rail and Save — and the history item last, to its buttons, and below that, where the row is still too wide, the rail's icon drops its caret, the history and Save staying in the row; on a touch screen every control there is a 44px tap target. The banners — the open flow's session's: a Save's conflict or refusal, an unknown outcome and a failed confirmation read, each with Retry, and the source changed under its drafts, with Discard; a source of the rail whose narrowing could not be read, with Retry; and a `flow` the flowchart doesn't hold, named with the flow shown in its place (`Gone isn't a flow here — showing Inbound parcels`) until the viewer opens a flow or the record gains that name. Main — the canvas, filling it and scrolling both ways inside it, its lanes running main's whole height; over flows by name with none — an empty record — the shared empty state, with "+ New flow" where it edits. The footer — the counts: over many flows the open flow's name first, the transitions and their planned · observed · unresolved split, where it edits the changes waiting on Save (`3 pending` — each lane, state, transition and decision the drafts add, change or remove), and over a record its last save. The start pane is `library`'s, an optional prop; the end pane is the inspector, on by default. The flowchart fills its parent and draws no border: give it a box of its own height
|
|
2180
2337
|
├─ Its flows — ONE of two sources (both, or neither, is refused):
|
|
2181
|
-
│ ├─ record — Record.bind(r
|
|
2338
|
+
│ ├─ record — Record.bind(r): r of Flowchart.Types.Flows, flows by name (the canvas opens the flow the viewer opened last, kept in the UI store under `name`; else `flow`; else the first by name). Each flow keeps its own session (#1246): its drafts stay while another flow is open, and Save sends the open flow's as ONE patch through the record's patch mutation — `Record.onApply(record, { keyed: true })`, the flow's insert, update or delete by name; the drafts retire once the record reads it back. A record always holds flows by name — e3's patch mutation writes only keyed records — so a lone flow is a record of one entry, or `data`; a record of one flow is refused
|
|
2182
2339
|
│ └─ data — a value, an expression or a bind handle of Flowchart.Types.Flows (+ `flow`) or Flowchart.Types.Flow, the value's type picking the arm; read only unless given onApply (async, (String, PatchType(the value's type)) → Editing.Types.ApplyResult — `East.asyncFunction([StringType, PatchType(T)], Editing.Types.ApplyResult, ($, requestId, patch) => …)`), the host's commit: Save hands it the session's request id — the same again on a Retry, so a host that keys its write by it (`commit.patch(requestId, patch)`) writes once (#1275) — and one patch of the value — over flows by name the open flow's insert, update or delete by name, never the whole value replaced; over one flow the flow's own patch — and the drafts retire once the host's value holds what it was handed
|
|
2183
2340
|
├─ Flowchart.over(states, { state?, links, link?, lanes, lane?, triggers?, trigger? }) — one Flowchart.Types.Flow from the app's tables:
|
|
2184
2341
|
│ ├─ states + links + lanes (required) — the three tables (lanes accept a literal [{ key, label? }] array; array order = band order)
|
|
@@ -2294,9 +2451,9 @@ binding is refused, naming the one to use:
|
|
|
2294
2451
|
|
|
2295
2452
|
| Read | Editable | Read only |
|
|
2296
2453
|
| --- | --- | --- |
|
|
2297
|
-
| whole | `Record.bind(record
|
|
2454
|
+
| whole | `Record.bind(record)` | `Data.bind(record)` |
|
|
2298
2455
|
| complete, through its source of the slice | `Record.bind(…)` + `entries: Data.bindPaged(record)` | `Data.bindPaged(record)` |
|
|
2299
|
-
| a window at a time, by area — its source lists an area index | the same, its source `Data.bindPaged(record, { indexes: [
|
|
2456
|
+
| a window at a time, by area — its source lists an area index | the same, its source `Data.bindPaged(record, { indexes: ["by_area", …] })` | the same |
|
|
2300
2457
|
|
|
2301
2458
|
A source of `slice={{ slice: Slice.bindRecords(key, { … }) }}` named after a
|
|
2302
2459
|
kind's slot reads that kind through it — `Data.bindPaged(record, { indexes })`
|
|
@@ -2307,13 +2464,13 @@ read through its source — and beside one read by area, by the runs its
|
|
|
2307
2464
|
window's cells are of the area index, e3 reading the keys from the index so
|
|
2308
2465
|
none is sent, each cell's once, at every zoom, so a dot draws its item's
|
|
2309
2466
|
status as a card does. Bound with their patch door —
|
|
2310
|
-
`Record.bind(record
|
|
2467
|
+
`Record.bind(record)`, with `entries:
|
|
2311
2468
|
Data.bindPaged(record)` beside a kind read through its source — the inspector
|
|
2312
2469
|
edits them beside their kind's row; `Data.bind` or `Data.bindPaged` alone
|
|
2313
2470
|
reads them only.
|
|
2314
2471
|
|
|
2315
|
-
A kind whose source lists an area index — `
|
|
2316
|
-
|
|
2472
|
+
A kind whose source lists an area index — `index.area({ x, y, width, height,
|
|
2473
|
+
at: "centre", cell })` among its record's indexes, the **e3** skill — is read by
|
|
2317
2474
|
area (#1393), a window at a time: the rows whose box meets the view and a
|
|
2318
2475
|
margin about it, read from the index alone. Past 5,000 rows in the view it
|
|
2319
2476
|
draws their density from the index's counts, in the theme's heat steps, the
|
|
@@ -2620,10 +2777,12 @@ import e3 from '@elaraai/e3';
|
|
|
2620
2777
|
|
|
2621
2778
|
// The app's racks: a record keyed by String — each rack's centre, box, name, model and power.
|
|
2622
2779
|
export const FloorRack = StructType({ name: StringType, x: FloatType, y: FloatType, w: FloatType, h: FloatType, model: StringType, kw: FloatType, capacity: FloatType });
|
|
2623
|
-
export const floorRacks = e3.record("floor_racks", DictType(StringType, FloorRack),
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
]
|
|
2780
|
+
export const floorRacks = e3.record("floor_racks", DictType(StringType, FloorRack), {
|
|
2781
|
+
initial: new Map([
|
|
2782
|
+
["A01", { name: "A01", x: 2.4, y: 2.9, w: 3.2, h: 1.2, model: "42U · 8 kW", kw: 5.2, capacity: 8.0 }],
|
|
2783
|
+
["A02", { name: "A02", x: 6.0, y: 2.9, w: 3.2, h: 1.2, model: "42U · 8 kW", kw: 7.4, capacity: 8.0 }],
|
|
2784
|
+
]),
|
|
2785
|
+
});
|
|
2627
2786
|
|
|
2628
2787
|
const floor = East.function([], UIComponentType, (_$) => (
|
|
2629
2788
|
<Reactive>{$ => {
|
|
@@ -2652,15 +2811,15 @@ veto's words (`schematicDataCentre`):
|
|
|
2652
2811
|
|
|
2653
2812
|
```tsx
|
|
2654
2813
|
// Inside Reactive: the racks through their patch door, read by key beside their source of the rail.
|
|
2655
|
-
const racks = $.let(Record.bind(dcRacks
|
|
2814
|
+
const racks = $.let(Record.bind(dcRacks));
|
|
2656
2815
|
const rackEntries = $.let(Data.bindPaged(dcRacks)); // one rack by its key
|
|
2657
|
-
const loads = $.let(Record.bind(dcRackLoads
|
|
2816
|
+
const loads = $.let(Record.bind(dcRackLoads)); // the racks' loads, edited in the inspector
|
|
2658
2817
|
const loadEntries = $.let(Data.bindPaged(dcRackLoads)); // by key, beside a kind read through its source
|
|
2659
2818
|
const frames = $.let(Data.bind(dcFrames));
|
|
2660
|
-
const cooling = $.let(Record.bind(dcCooling
|
|
2819
|
+
const cooling = $.let(Record.bind(dcCooling));
|
|
2661
2820
|
const areas = $.let(Data.bind(dcAreas)); // read only, and read whole
|
|
2662
|
-
const fibre = $.let(Record.bind(dcFibre
|
|
2663
|
-
const trunks = $.let(Record.bind(dcTrunks
|
|
2821
|
+
const fibre = $.let(Record.bind(dcFibre));
|
|
2822
|
+
const trunks = $.let(Record.bind(dcTrunks)); // none yet: a Shift session grows one
|
|
2664
2823
|
const systems = $.let(Data.bind(dcSystems)); // the layers: a record of the app's
|
|
2665
2824
|
const rackTemplates = $.let(Data.bind(dcRackTemplates));
|
|
2666
2825
|
const owners = $.let(Data.bind(dcOwners)); // the tenants: an author's tab whose cards set a rack's owner
|
|
@@ -2678,9 +2837,9 @@ const canConnect = $.const(East.function([Schematic.Types.Candidate], OptionType
|
|
|
2678
2837
|
}));
|
|
2679
2838
|
// One rail: the racks by name, hall and owner, and their templates by group.
|
|
2680
2839
|
const slice = $.let(Slice.bindRecords("ops.floor", {
|
|
2681
|
-
racks: { source: Data.bindPaged(dcRacks, { indexes: [
|
|
2840
|
+
racks: { source: Data.bindPaged(dcRacks, { indexes: ["racks_by_name", "racks_by_hall", "racks_by_owner"] }), search: ["name", "owner"],
|
|
2682
2841
|
fields: { name: { label: "Rack" }, hall: { label: "Hall", hints: ["Cage A"] }, owner: { label: "Owner", hints: ["Harbour Labs"] } } },
|
|
2683
|
-
rackTemplates: { source: Data.bindPaged(dcRackTemplates, { indexes: [
|
|
2842
|
+
rackTemplates: { source: Data.bindPaged(dcRackTemplates, { indexes: ["rack_templates_by_group"] }), fields: { group: { label: "Group", hints: ["Racks"] } } },
|
|
2684
2843
|
}));
|
|
2685
2844
|
<Schematic id="floor" inspector extent={{ width: 28, height: 14 }} scaleUnit="m" slice={{ slice }} narrowed="dim"
|
|
2686
2845
|
onSelect={onSelect} canConnect={canConnect}
|
|
@@ -2754,7 +2913,7 @@ const slice = $.let(Slice.bindRecords("ops.floor", {
|
|
|
2754
2913
|
├─ Drops (#1390) — each card dropped on the canvas ONE step of the history, the ghost's caption saying where it lands or why it can't: an item template adds an item at the point (its key minted, its `x` and `y` the point's at the screen's resolution, every other field the template's `values`), selected; a backlog row is placed at the point, selected; a link or net template gives a link or a net of its kind its `values`, never its ends, sides or key; an author's card sets its `drop`'s `Schematic.patch` on an entity of that patch's kind. ⏎ on a card — on a touch screen a tap on the selected card — drops it on the selection (an author's card on every selected entity of its kind) or a template or backlog row at the view's centre, refused in the footer's words. Refused outside the drawing, with no entity of the card's kind under it, or on a kind bound read only; a locked layer's entities and a hatched zone take none, and an item's card covers the zones and links beneath it. A picked link or net template gives Connect its values
|
|
2755
2914
|
├─ Inspector (#1391) — the end pane, its tabs Details and Issues (Issues with its count), opened on Details by Enter or a double-click on an entity: Details through the shared form, each field by the input its type takes, a field the drafts changed tinted and the head's chip Pending (New for a row its record holds none of) — an item's kind's form over the fields no role writes, or the kind's own `inspector`, its details' form or own Details, its position (`x`, `y`) and Unplace (a kind with a backlog), Delete, and what it is joined to, each selecting it; a zone's form; a link's ends as Choosers over the items of their slots, its form, Delete; a net's sides as tags over its slot's keys (a side never emptied: Delete takes the net), its form, Delete; a net's leg, its item and Remove; several, how many of each kind, and Delete; nothing, the drawing's counts — a kind read by area counted from its source's indexes, never its window, a line naming what no read can count — and each record's last save with who made it — each edit ONE step of the history; read only, every field printed. Issues: an item outside the drawing, a shape past its box, a colour naming no theme colour, a link or net naming an item no kind holds, a drafted row `ready` refuses (holding Save), a Save refused or in conflict — each selecting what it names; over a kind read by area, the rows read around the view and every unsaved change, one line saying its rows out of view are checked only as the view reaches them
|
|
2756
2915
|
├─ Narrowing (#1392) — each source of the rail narrows its kind on the canvas, in the Backlog, the Index and the templates it names, in the inspector's Choosers and what an item is joined to, and in the drawing's counts; a link or a net draws only while its ends do. Hidden by default; dimmed, its kind reads every row and draws what the narrowing leaves out as context — faded, greyed or a bare marker, a link to it and a net all of whose members on a side are it among it — which selects by no path and counts nowhere. A match — a row its source's shown keys hold — wears `emphasis` (a ring on its card or marker, an edge on a zone, a halo under a link's or a net's line), `frame` boxes two or more, and `fit` flies the camera to them when the narrowing changes, once what it flies to is read, never as a zoom or a Move changes what matches — a kind read by area's by the box its narrowing's rows stand in, from its area index's counts. A draft the narrowing leaves out waits in its session, hidden, or draws where its draft has it, dimmed; Save sends it either way
|
|
2757
|
-
├─ Reading by area (#1393) — a kind whose source lists
|
|
2916
|
+
├─ Reading by area (#1393) — a kind whose source lists index.area({ x, y, width, height, at: "centre", cell }): a window at a time, the rows whose box meets the view and its margin, read from the index alone; past 5,000 rows in view their density from the index's counts, the footer saying how many it shades (`Density: 20,000 racks — zoom in to draw them`); its details by the runs the window's cells are of the index; a choice of the rail through an area index led by its fields, else the one led by none, the rest asked of the window's rows; its Backlog through the index's rows with no position. Refused as the kind is read, its read failing with Retry: area indexes all led by fields, or one boxing the rows otherwise than the kind's roles place them, the reason naming what to declare. A draft is kept by the row's key whatever the window, drawn where it places its row while the narrowing holds its stored row — the Backlog listing a row it unplaces, the Index a row it moves under the zone it now stands in — and Save writes it through the kind's patch door. A selected row stays selected off the window — Details reads it by its key, Del deletes it, `onSelect` hears no camera move — until a read shows it gone, a draft deletes or unplaces it, or a layer hides or locks it. The drawing's counts take the kind from its source, never its window
|
|
2758
2917
|
├─ Narrow (#1394) — below 480px of main: one finger pans, two pinch, a tap selects, and nothing drags — no Move or Connect among the View's tools, no library card drags, no drop cell, no link handles. Details takes the gestures' place: an item's Connect to… (a Chooser of the items a link may end at, the link kind beside it while several editable kinds start at the item) and a Backlog card's Place (at the view's centre), beside x and y, a link's ends, a net's sides and Delete, every route that connects held to `canConnect`; a resize keeps the selection and the drafts
|
|
2759
2918
|
└─ Factories:
|
|
2760
2919
|
├─ Schematic.items / zones / links / nets(record, config) — the kinds · Schematic.details(record, { status?, meter?, metric?, sublabel?, fields?, inspector?, entries? }) — a kind's details · Schematic.templates(rows, { name, group?, icon?, values, source }) — a kind's templates record, `values` every field but what a drop places · Schematic.layers(rows, config) — the layers
|
|
@@ -2766,17 +2925,17 @@ const slice = $.let(Slice.bindRecords("ops.floor", {
|
|
|
2766
2925
|
| Signature | Description | Example |
|
|
2767
2926
|
| --- | --- | --- |
|
|
2768
2927
|
| `items={{ racks: Schematic.items(Data.bind(racks), { name, icon, x, y, width?, height?, label, … }) }}` **❗** | An item kind over the app's record, read only and read whole; its card's second line and bar from accessors. A role naming a field the row lacks, or one of another type, is refused, naming the role, the field and its type. | `schematicFloor` |
|
|
2769
|
-
| `Schematic.items(Record.bind(r
|
|
2928
|
+
| `Schematic.items(Record.bind(r), { …, entries: Data.bindPaged(r) })` + a source of `slice` named after its slot **❗** | Editable, read complete through its source. `Data.bind` there, or a `Record.bind` without `entries`, is refused, naming the binding to use. | `schematicDataCentre` |
|
|
2770
2929
|
| `x` / `y` Options of Float | The rows with no position are the kind's backlog: drawn nowhere, and listed by `Schematic.library.backlog()`. | `schematicDataCentre` |
|
|
2771
2930
|
| `slice={{ slice: Slice.bindRecords(key, { racks: { source: Data.bindPaged(racks, { indexes }) }, … }) }}` · `narrowed="dim"` **❗** | One rail (#1392): each source narrows its kind everywhere it appears — the canvas, the Backlog, the Index, the templates it names, the inspector's Choosers and the drawing's counts — a link or a net only while its ends draw. Hidden by default, the kind reading only its narrowing; `dim` reads every row and draws what the narrowing leaves out as context, faded and greyed, never selected. `emphasis`, `frame` and `fit` take the matches whatever `show` is. | `schematicDataCentre` |
|
|
2772
|
-
| a kind's source listing `
|
|
2931
|
+
| a kind's source listing `index.area(…)`, beside area indexes led by the rail's fields **❗** | Read by area, a window at a time from the index alone; past 5,000 rows in view their density, the footer saying how many; drafts kept by key wherever the window goes, and Save read back through the index; a selected row kept off the window, read by its key; the drawing's counts its source's. Area indexes all led by fields, or one boxing the rows otherwise than the kind's roles, are refused, naming what to declare. | `schematicRecordIndexes` |
|
|
2773
2932
|
| below 480px of main | Nothing drags; Details' Connect to… and the Backlog card's Place take the gestures' place, beside an item's `x` and `y`, a link's ends and a net's sides, each held to `canConnect`. | `schematicRecordIndexesMobile`, `schematicDataCentre` |
|
|
2774
2933
|
| an editable kind, and no `library` or `inspector` | The frame is its toolbar — the tools, Move among them, and the history — its canvas and its footer; every gesture still one step Save writes. | `schematicHandover` |
|
|
2775
2934
|
| `footprint: (u) => some(Schematic.circle(u.radius))` + `width` / `height`; `Schematic.polygon` / `.circle` / `.polyline` **❗** | An accessor over the row's own fields, returning an `Option<Schematic.Types.Geometry>`: geometry about the item's centre, or a zone's `geometry` about its box's centre, each vertex an offset from it — the row's own `{ x, y }` points, or `{ x, y, bulge }`. A footprint without the box, and a shape role naming a field, are refused, naming the accessor to write. | `schematicShapes`, `schematicDataCentre` |
|
|
2776
2935
|
| `zones={{ areas: Schematic.zones(…, { x, y, width, height, label, pattern?, geometry? }) }}`, `pattern: (a) => a.kind.match({ … })`, `Schematic.outline()` / `.hatch()` | Zones in their boxes under the items, outlined or hatched by what the row says it is. | `schematicShapes`, `schematicDataCentre` |
|
|
2777
2936
|
| `links={{ fibre: Schematic.links(…, { from: { field, of }, to: { field, of }, via? }) }}`, `Schematic.solid()` / `.dashed()` | Links between items, routed orthogonally, two of one pair fanning into lanes, in the shared links look: each from its `from` item's edge to its `to` item's, its head there, in the links' muted ink or its style's tone; an end of a variant field takes its slot from its case. | `schematicDataCentre` |
|
|
2778
2937
|
| `nets={{ feeds: Schematic.nets(…, { sources: { field, of }, destinations: { field, of } }) }}` | A bus joining many items to many. | `schematicDataCentre` |
|
|
2779
|
-
| a kind bound with its patch door — `Record.bind(record
|
|
2938
|
+
| a kind bound with its patch door — `Record.bind(record)` **❗** | Every gesture one step of one history over a session per record (#1388): Move, Connect, Shift growing a net of the first editable net kind that takes its items, an end retargeted, a net extended, Del; Undo, Redo and Discard; Save sending each record's changes through its patch door, one refused leaving the others' commits standing. A gesture writes only the fields its roles map, a new row under a minted key. | `schematicDataCentre` |
|
|
2780
2939
|
| `details: Schematic.details(record, { status?, meter?, metric?, sublabel?, fields?, inspector?, entries? })` **❗** | A record keyed as its kind is, its roles drawn on the cards; a role given by the kind too is refused. Bound with its patch door — `entries` beside it where the kind is read through its source — the inspector edits it beside the kind's row. | `schematicDataCentre` |
|
|
2781
2940
|
| `templates: Schematic.templates(rows, { name, group?, values, source })` **❗** | A templates record, its `source` one of the slice's; `values` gives every field but what a drop places — an item's `x` and `y`, a link's ends, a net's sides. A list written in the code is refused. | `schematicDataCentre` |
|
|
2782
2941
|
| `library={[Schematic.library.templates(), .backlog(), .index(), .layers(), .tab(rows, {…})]}` **❗** | The library pane's tabs, each over every kind as the Calendar's are, each checked against the kinds: Templates and Backlog are refused when no kind has any, naming the change. The frame's start pane draws them, each with its count, each card tab following its source of the rail (#1389). | `schematicDataCentre` |
|
|
@@ -3087,16 +3246,16 @@ index, with `range: "start"`, and the kind gives `entries`, the record's own
|
|
|
3087
3246
|
entries, beside it:
|
|
3088
3247
|
|
|
3089
3248
|
```tsx
|
|
3090
|
-
// Package side:
|
|
3091
|
-
//
|
|
3249
|
+
// Package side, among each record's indexes: jobs_by_time: index.interval({ from: "start", to: "end" }),
|
|
3250
|
+
// jobs_by_status_time the same with `on: ["status"]`, machines_by_area: index.on(["area"]), job_templates_by_group: index.on(["group"]).
|
|
3092
3251
|
// Inside Reactive: jobs has optional start/end and a machine reference.
|
|
3093
3252
|
const machines = $.let(Data.bind(d.machines));
|
|
3094
3253
|
const templates = $.let(Data.bind(d.jobTemplates));
|
|
3095
|
-
const jobs = $.let(Record.bind(d.jobs
|
|
3096
|
-
const jobIndex = $.let(Data.bindPaged(d.jobs, { indexes: [
|
|
3254
|
+
const jobs = $.let(Record.bind(d.jobs));
|
|
3255
|
+
const jobIndex = $.let(Data.bindPaged(d.jobs, { indexes: ["jobs_by_time", "jobs_by_status_time"] }));
|
|
3097
3256
|
const jobEntries = $.let(Data.bindPaged(d.jobs));
|
|
3098
|
-
const machineIndex = $.let(Data.bindPaged(d.machines, { indexes: [
|
|
3099
|
-
const templateIndex = $.let(Data.bindPaged(d.jobTemplates, { indexes: [
|
|
3257
|
+
const machineIndex = $.let(Data.bindPaged(d.machines, { indexes: ["machines_by_area"] }));
|
|
3258
|
+
const templateIndex = $.let(Data.bindPaged(d.jobTemplates, { indexes: ["job_templates_by_group"] }));
|
|
3100
3259
|
// One rail, a source per record, each named after what it narrows.
|
|
3101
3260
|
const slice = $.let(Slice.bindRecords("ops.calendar", {
|
|
3102
3261
|
job: { source: jobIndex, fields: { status: { label: "Status" }, start: { label: "Dates" } }, range: "start" },
|
|
@@ -3138,15 +3297,15 @@ past 10,000 over the window is read or drawn. Its Backlog is paged through
|
|
|
3138
3297
|
each kind's source, a page at a time; nothing reads a record whole:
|
|
3139
3298
|
|
|
3140
3299
|
```tsx
|
|
3141
|
-
// Package side,
|
|
3142
|
-
//
|
|
3143
|
-
//
|
|
3300
|
+
// Package side, among the jobs record's indexes, beside its interval index (the e3 skill):
|
|
3301
|
+
// jobs_by_press_week: index.series({ key: "press", from: "start", to: "end", tile: "week" }),
|
|
3302
|
+
// jobs_by_press_month: index.summary({ key: "press", from: "start", to: "end", tile: "month" }),
|
|
3144
3303
|
// Inside Reactive: 2,000 presses a task generates, paged 200 to a window; the jobs a cell at a time.
|
|
3145
3304
|
const presses = $.let(Data.bindPaged(pressesTask));
|
|
3146
|
-
const jobs = $.let(Record.bind(jobsRecord
|
|
3305
|
+
const jobs = $.let(Record.bind(jobsRecord));
|
|
3147
3306
|
const entries = $.let(Data.bindPaged(jobsRecord));
|
|
3148
3307
|
const slice = $.let(Slice.bindRecords("ops.calendar", {
|
|
3149
|
-
job: { source: Data.bindPaged(jobsRecord, { indexes: [
|
|
3308
|
+
job: { source: Data.bindPaged(jobsRecord, { indexes: ["jobs_by_time", "jobs_by_press_week", "jobs_by_press_month"] }), range: "start" },
|
|
3150
3309
|
}));
|
|
3151
3310
|
return <Calendar slice={{ slice }} inspector library={[Calendar.library.backlog()]}
|
|
3152
3311
|
view={{ layout: "timeline", period: "week", date: new Date("2026-10-05T00:00:00Z") }}
|
|
@@ -3164,7 +3323,7 @@ return <Calendar slice={{ slice }} inspector library={[Calendar.library.backlog(
|
|
|
3164
3323
|
| `Calendar.events(record, config)` **❗** | `Schedule.events`: span fields, resource references, status cases, shared `field` editors, `ready`, custom `inspector`, overlap policy and record patch writes. Narrowed through its source of `slice`, named after its slot, it gives `entries: Data.bindPaged(record)` beside it for its original-key reads. A kind's own `slice` or `filter`, and `window` / `backlogWindow` beside a source, are refused, naming the change. Whole-row edits use one EditSession per kind and one shared EditHistory. | `calendarOperations` |
|
|
3165
3324
|
| `slice={{ slice: Slice.bindRecords(key, { [kind]: { source, fields, range: "start" }, [resources]: …, [templates]: … }), affordances?, persist? }}` **❗** | One rail, a source per record (#1343). An event kind's reads its events in the window through an interval index — the window's rows alone — and the Backlog tab pages its backlog through it, its start Unset, never with the window (#1424); a draft draws where it places its event, so a backlog event scheduled shows where it lands, and the draft of an event the kind's narrowing does not hold draws nowhere. A resource kind's narrows its columns, the timeline's rows and the resource Choosers, which keep a placement's own resource and the resources an event's fields name; a paged kind's pages the narrowing's resources in its index's order. A templates list's and an app tab's narrow their cards. One date window: the first event source with a range leads, its range the time navigator's Range panel, never a chip of the rail, and navigation writes the window into every event source. Overlaps, checks and commands read every event and every resource. `Slice.bind`, a single `Slice.bindRecord` and a source nothing names are refused. | `calendarOperations`, `calendarRecordIndexes` |
|
|
3166
3325
|
| `Calendar.resources(rows, config)` | `Schedule.resources` over a dictionary, usually a record bound with `Data.bind`, read whole. | `calendarOperations` |
|
|
3167
|
-
| `Calendar.resources(Data.bindPaged(record), { name, icon, label, meta? })` + a kind's source listing `
|
|
3326
|
+
| `Calendar.resources(Data.bindPaged(record), { name, icon, label, meta? })` + a kind's source listing `index.series` and `index.summary` beside its interval index **❗** | A resource kind paged a window at a time (#1424): 200 resources to a window, the timeline's rows and the Resources view's columns the pages around the view — never every resource read — and each Chooser seeking a resource by its key through the paged read (`Press 1460 · P-1460 · Hall 4`). Where a view lays its resources out, an event kind placed on it is read a cell at a time — the events of the blocks of fifty resources around those in view, over the tiles the window touches, a range a resource a level (#1433), two requests a change — and past 10,000 events in the cells wanted shades each resource from the summary, the share of each period booked; the time grids and the agenda count it over the window first. A source named after the paged kind's slot (`Data.bindPaged(record, { indexes })` over its own record) pages the narrowing's resources in its index's order, the last view standing, retained, until its windows land. A kind on it whose source lists its interval index alone, or a summary and no series, one read whole or through its day index, and one placed beside another resource kind, are refused, naming both declarations over the kind's own fields. | `calendarWindows`, `calendarWindowsByHall`, `calendarWindowsMobile` |
|
|
3168
3327
|
| every other event kind, past 10,000 events over the window | Counted before one of its events is read — through its source's interval index, through its day index (the days its events run on, an upper bound) or in memory — and none of them read or drawn but its drafts: a resource's row or column shades from its source's summary at the view's period, else is hatched, the banner says how many events there are and where they show, and every figure that would count them says they are not counted, never 0. | `calendarRecordIndexes`, `calendarWindowed` |
|
|
3169
3328
|
| `Calendar.templates(rows, { name, group?, at?, duration, values, source })` **❗** | A templates record, bound with `Data.bind`, its `source` the rail's source over it, whose shown keys filter the Templates tab; typed `values` supplies exactly the event fields other than its scheduling roles. A list written in the code, one naming no source of the slice, and templates on a Calendar with no slice are refused, naming the change. Template changes do not rewrite existing event rows. | `calendarOperations` |
|
|
3170
3329
|
| `Calendar.library.templates()` / `.backlog()` / `.tab(rows, { …, source? })` **❗** | Shared Library cards and search, each tab following its sources: it counts what it shows (`5 of 6`) and, when the rail leaves it none, says so with Clear filters for them. The Backlog counts `N of M`, what its kinds' narrowing shows beside the stored events their choices hide — `…` until its counts are read, never a bare `Backlog` — and its Clear filters keeps each window. A kind narrowed through its source has its backlog paged through it (#1424), a page a request, its count the index's: never every unscheduled event read, no step of the dates reads it again, and a new narrowing keeps its cards and count standing, retained, until its own land. That paged list has no search of its own, as the Schematic's Backlog has none: the rail's choices narrow it. An author's tab names the source it follows (`source`) and may apply `Calendar.patch(RowType, fields)` to a compatible selected event. Invalid or ambiguous patch row types, and a `source` the slice lacks, are refused. | `calendarOperations` |
|
|
@@ -3203,10 +3362,14 @@ times; teams:
|
|
|
3203
3362
|
```tsx
|
|
3204
3363
|
// Module level: the app's own duties; its workers (name, team), shift times (name, code, starts, hours, order) and teams likewise.
|
|
3205
3364
|
export const Duty = StructType({ person: StringType, start: DateTimeType, end: DateTimeType, shift: StringType, team: StringType });
|
|
3206
|
-
export const duties = e3.record("duties", DictType(StringType, Duty),
|
|
3207
|
-
|
|
3365
|
+
export const duties = e3.record("duties", DictType(StringType, Duty), {
|
|
3366
|
+
initial: new Map(),
|
|
3367
|
+
mutations: (mutation) => ({
|
|
3368
|
+
patch: mutation.patch(),
|
|
3369
|
+
}),
|
|
3370
|
+
});
|
|
3208
3371
|
// Inside Reactive:
|
|
3209
|
-
const rota = $.let(Record.bind(duties
|
|
3372
|
+
const rota = $.let(Record.bind(duties));
|
|
3210
3373
|
const staff = $.let(Data.bind(workers));
|
|
3211
3374
|
const times = $.let(Data.bind(shiftTimes));
|
|
3212
3375
|
const groups = $.let(Data.bind(teams));
|
|
@@ -3227,18 +3390,18 @@ its check, Publish, one rail and its fit (`rosterWarehouse`):
|
|
|
3227
3390
|
|
|
3228
3391
|
```tsx
|
|
3229
3392
|
// Module level: the warehouse's duty, WarehouseDuty — who (a staff key, or an agency's), start, end, shift, team, role, task,
|
|
3230
|
-
// unpaidBreak, agreed, costCentre and note — and its records: rosterDuties (
|
|
3393
|
+
// unpaidBreak, agreed, costCentre and note — and its records: rosterDuties (its interval index roster_duties_by_time), rosterWeeks, rosterCover, ….
|
|
3231
3394
|
// Inside Reactive: what the Roster writes, each through its patch door.
|
|
3232
|
-
const rota = $.let(Record.bind(rosterDuties
|
|
3395
|
+
const rota = $.let(Record.bind(rosterDuties));
|
|
3233
3396
|
const entries = $.let(Data.bindPaged(rosterDuties)); // one duty by its key
|
|
3234
|
-
const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: [
|
|
3235
|
-
const weeks = $.let(Record.bind(rosterWeeks
|
|
3236
|
-
const cover = $.let(Record.bind(rosterCover
|
|
3397
|
+
const byTime = $.let(Data.bindPaged(rosterDuties, { indexes: ["roster_duties_by_time"] })); // index.interval({ from: "start", to: "end" })
|
|
3398
|
+
const weeks = $.let(Record.bind(rosterWeeks));
|
|
3399
|
+
const cover = $.let(Record.bind(rosterCover));
|
|
3237
3400
|
// What it reads whole — every person and activity is the calculation scope — and their indexes for the rail.
|
|
3238
3401
|
const people = $.let(Data.bind(rosterStaff));
|
|
3239
3402
|
const acts = $.let(Data.bind(rosterActivities));
|
|
3240
|
-
const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: [
|
|
3241
|
-
const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: [
|
|
3403
|
+
const staffIndex = $.let(Data.bindPaged(rosterStaff, { indexes: ["roster_staff_by_team_name", "roster_staff_by_skill"] }));
|
|
3404
|
+
const actIndex = $.let(Data.bindPaged(rosterActivities, { indexes: ["roster_activities_by_group"] }));
|
|
3242
3405
|
const slice = $.let(Slice.bindRecords("ops.roster", {
|
|
3243
3406
|
// The week's duties: the Roster's navigation writes the range; no chip picks the week.
|
|
3244
3407
|
duty: { source: byTime, range: "start" },
|
|
@@ -3361,10 +3524,10 @@ export const rosterStaffFit = e3.function("roster_staff_fit", East.function([Ros
|
|
|
3361
3524
|
| --- | --- | --- |
|
|
3362
3525
|
| **Builder and records** | | |
|
|
3363
3526
|
| `<Roster events resources shifts groups periods? targets? positions? activities? proposals? patterns? pay? currency? rules? check? fit? optimise? slice? library? inspector? view? tileTimes? letters? week? density? chipSkills? requirements? readOnly? id? />` **❗** | Shifts by day or week, People by week or month (#1406). Refused at build, each naming the change: a removed prop (below); `events` holding other than one kind; `resources` other than `people` and `agencies`; a duty kind whose resource names no `people`; a resource kind read a page at a time; a `people` kind with no `group`; `positions` beside a duty kind with no `position` role; `pay` without `currency`; a `pay`, a `check` or a `fit` over another row type; a `view.period` its layout does not take (Shifts takes `"day"` or `"week"`, People `"week"` or `"month"`); `Roster.library.patterns()` without `patterns`; and a library tab listed twice. The renderer validates live configuration. | `rosterWarehouse`, `rosterMinimal`, `rosterPeopleMonth` |
|
|
3364
|
-
| `events={{ duty: Schedule.events(Record.bind(duties
|
|
3527
|
+
| `events={{ duty: Schedule.events(Record.bind(duties), { start, end, resource, shift, group, position?, activity?, hours?, fields?, inspector?, ready?, entries? }) }}` **❗** | Exactly one kind, the duties: a span kind of plain DateTime times, keyed by String. `resource` names a `people` key, or a variant whose cases name `people` and `agencies`: a person, or an agency asked to send someone. `hours` is what a duty counts for coverage, skill hours, contract hours, rest and pay; its length when omitted. The inspector shows a duty's times, position and activity, then the kind's form over the fields no role writes (`fields` hints) or the kind's own `inspector`; `ready` is the app's check on a draft. | `rosterWarehouse`, `rosterCustomInspector` |
|
|
3365
3528
|
| `resources={{ people: Schedule.resources(rows, { name, icon, label, group, position?, skills?, contract?, agency?, trainer?, trainee?, usual? }), agencies?: Schedule.resources(rows, { name, icon, label }) }}` | Every person and agency, read whole: the calculation scope. A person's key is what a duty's resource holds; `group` is their home group. | `rosterWarehouse` |
|
|
3366
|
-
| `periods={Schedule.periods(Record.bind(weeks
|
|
3367
|
-
| `targets={Roster.targets(Record.bind(cover
|
|
3529
|
+
| `periods={Schedule.periods(Record.bind(weeks), { every: "week", status, dismissed?, entries? })}` | The app's weeks, each keyed by its first day by the Roster's week start: the status chip, Publish and the picker's Published · Draft · Not started. `dismissed` keeps the proposals a week rejected; without it, a proposal offers Accept only. Without `periods`, no Publish. | `rosterWarehouse`, `rosterPublished` |
|
|
3530
|
+
| `targets={Roster.targets(Record.bind(cover), { day, group, shift, positions, hours, inspector?, entries? })}` **❗** | The app's cover targets, a row per slot, which its forecast fills: `day` a DateTime (a UTC midnight), `group` and `shift` Strings, `positions` an Integer, `hours` a `Dict<String, Float>` by activity. A slot with no row has no target; a target edit on one inserts a row under a minted key, its other fields their type's default. `inspector` is the app's own form for one target. | `rosterWarehouse`, `rosterCustomInspector` |
|
|
3368
3531
|
| `Roster.shifts(rows, { label, code, start, hours, order?, color? })` · `Roster.groups(rows, { label, order? })` · `Roster.positions(rows, { label, code, lead, order? })` **❗** | The lists the Roster reads over the app's rows, each a `Dict` keyed by String, in its `order`, then key order. A shift's `start` is a `Schedule.Types.Clock` and its `hours` what a new duty runs and a skill holder's capacity; its `color` (#1406) returns an `Option<ColorToken>` — east-ui's `ColorTokenType`, `Style.ColorToken("teal", "500")` — the People week's tiles' and the month's marks' fill, resolved through the theme, the same in both colour modes, the words on it in the ink the shade takes; `none`, neutral. A position that leads satisfies the lead rule, which needs the duty kind's `position` role. An accessor returning another type is refused, naming what it returns (`color`, naming `ColorTokenType`). | `rosterWarehouse`, `rosterMinimal`, `rosterPeople` |
|
|
3369
3532
|
| `Roster.activities(rows, { label, code?, group?, order? })` | Every activity from a keyed activities record (`Roster.Types.Activity` rows, or the app's own through the accessors): a group's skills are the activities of that group, and an activity of no group is a duty every group may assign. Coverage and checks read every one. | `rosterWarehouse` |
|
|
3370
3533
|
| `Roster.proposals(rows, { person, day, group, shift, reason })` | The app's suggestions, a person for a slot, each keyed by its row's key: Accept places the duty and keeps the key in the week's `dismissed`, one step; Reject keeps it there alone. A stale or already rostered proposal is hidden. | `rosterWarehouse` |
|
|
@@ -3475,10 +3638,14 @@ export const Bin = StructType({
|
|
|
3475
3638
|
bay: StringType, at: DateTimeType, load: OptionType(StringType),
|
|
3476
3639
|
m3: FloatType, kilns: DictType(StringType, FloatType), dryFor: TimeStepType,
|
|
3477
3640
|
});
|
|
3478
|
-
export const bins = e3.record("bins", DictType(StringType, Bin),
|
|
3479
|
-
|
|
3641
|
+
export const bins = e3.record("bins", DictType(StringType, Bin), {
|
|
3642
|
+
initial: new Map(),
|
|
3643
|
+
mutations: (mutation) => ({
|
|
3644
|
+
patch: mutation.patch(),
|
|
3645
|
+
}),
|
|
3646
|
+
});
|
|
3480
3647
|
// Inside Reactive:
|
|
3481
|
-
const rows = $.let(Record.bind(bins
|
|
3648
|
+
const rows = $.let(Record.bind(bins));
|
|
3482
3649
|
const loads = $.let(Data.bind(arrivals));
|
|
3483
3650
|
const yard = $.let(Data.bind(kilns));
|
|
3484
3651
|
const bays = $.let(Data.bind(yardBays));
|
|
@@ -3502,17 +3669,17 @@ own fields, one rail and its library (`allocationKilnYard`):
|
|
|
3502
3669
|
|
|
3503
3670
|
```tsx
|
|
3504
3671
|
// Inside Reactive: what the board writes, each through its patch door.
|
|
3505
|
-
const bins = $.let(Record.bind(kilnBins
|
|
3672
|
+
const bins = $.let(Record.bind(kilnBins));
|
|
3506
3673
|
const entries = $.let(Data.bindPaged(kilnBins)); // one bin by its key
|
|
3507
|
-
const byTime = $.let(Data.bindPaged(kilnBins, { indexes: [
|
|
3508
|
-
const days = $.let(Record.bind(yardDays
|
|
3674
|
+
const byTime = $.let(Data.bindPaged(kilnBins, { indexes: ["kiln_bins_by_time"] })); // index.interval({ from: "at", to: "at" })
|
|
3675
|
+
const days = $.let(Record.bind(yardDays));
|
|
3509
3676
|
// What it reads whole — every bay, kiln and load is the calculation scope — and their indexes for the rail.
|
|
3510
3677
|
const yard = $.let(Data.bind(kilns));
|
|
3511
3678
|
const loads = $.let(Data.bind(arrivals));
|
|
3512
3679
|
const bays = $.let(Data.bind(yardBays));
|
|
3513
|
-
const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: [
|
|
3514
|
-
const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: [
|
|
3515
|
-
const loadIndex = $.let(Data.bindPaged(arrivals, { indexes: [
|
|
3680
|
+
const bayIndex = $.let(Data.bindPaged(yardBays, { indexes: ["yard_bays_by_area"] }));
|
|
3681
|
+
const kilnIndex = $.let(Data.bindPaged(kilns, { indexes: ["kilns_by_group"] }));
|
|
3682
|
+
const loadIndex = $.let(Data.bindPaged(arrivals, { indexes: ["arrivals_by_spec"] }));
|
|
3516
3683
|
// One rail, a source named after each slot it narrows.
|
|
3517
3684
|
const slice = $.let(Slice.bindRecords("ops.allocation", {
|
|
3518
3685
|
bin: { source: byTime, range: "at" }, // the day's bins: the board's navigation writes the range
|
|
@@ -3587,10 +3754,10 @@ export const yardFit = e3.function("yard_fit", East.function([Allocation.Types.F
|
|
|
3587
3754
|
| --- | --- | --- |
|
|
3588
3755
|
| **Builder and records** | | |
|
|
3589
3756
|
| `<Allocation events resources arrivals fit optimise? periods? every? check? columns? slice? rules? library? inspector? view? horizon? title? readOnly? labels? table? id? />` **❗** | One toolbar row at every width — the day's navigator, its picker marking each day Published · Draft · Not started; Table · Timeline; the status chip; the focus; the rail and its count; the issues; with `optimise`, the Elara eye; the history and Publish — folding into one View in the narrowest frame, and below that, where the row is still too wide, the rail's icon dropping its caret and then the eye going into the View as its entry, the history and Save always in the row; then the Timeline or the Table, the panes the props ask for, and the footer's legend. Refused at build, each naming the change: a removed prop (below); `events` holding other than one kind; a bin kind with no lane or no parts; a resource kind that is neither the lanes' nor the resources'; resources read a page at a time or keyed by anything but String; a resources kind with no `min` or `max`; `arrivals` not from `Allocation.arrivals`, and its `values` of another struct than the bin kind's own fields; `periods` not from `Schedule.periods`; a `fit` bound over another row type, naming both; a `check` of another type; a library tab or a column given twice; an empty label; and Table bands that don't start at 0 and rise within the day. | `allocationMinimal`, `allocationKilnYard`, `allocationTable` |
|
|
3590
|
-
| `events={{ bin: Schedule.events(Record.bind(bins
|
|
3757
|
+
| `events={{ bin: Schedule.events(Record.bind(bins), { at, resource: { field, of }, arrival, quantity: { field, unit? }, parts: { field, of }, hold, kind?, fields?, inspector?, ready?, entries? }) }}` **❗** | Exactly one kind, the bins: an instant kind keyed by String. A bin's UTC day is its plan day, and a batch's bins follow one minute apart. `resource` names its lane, a String key of the lanes' kind; `arrival` the arrival it books, `none` a reservation; `quantity`'s unit shows everywhere a quantity does; `parts` holds its quantity on each resource of the slot `of` names, empty while it is not linked; `hold` and `kind` are kept from booking. The inspector shows the kind's form over the fields no role writes (`fields` hints), or the kind's own `inspector`. | `allocationKilnYard`, `allocationCustomBin` |
|
|
3591
3758
|
| `resources={{ <lanes>: Schedule.resources(rows, { name, icon, label, offset? }), <resources>: Schedule.resources(rows, { name, icon, label, min, max, group?, meta?, note?, hold?, jobs? }) }}` **❗** | The lanes' kind under the slot the bin kind's `resource.of` names, each lane taking a bin each `every` at its `offset` past it, in key order; the resources' kind under the slot its `parts.of` names, each with its range in the quantity's unit. `jobs` is a resource's other work: one covering the day's start makes it busy, the first after it is its next job; `hold` is how long a reservation on it is checked for. Both are read whole and keyed by String. | `allocationKilnYard` |
|
|
3592
3759
|
| `arrivals={Allocation.arrivals(rows: Dict<String, R>, { label, quantity, hold, kind?, meta?, due?, window?, details?, values? })}` **❗** | Every arrival the day may book, each accessor `(row, key) => …`. Booking one writes the bin's roles — `arrival` its key, `quantity`, `kind` and `hold` from these accessors, `parts` empty, `at` and the lane where it is booked — and `values`' struct into the row's other fields: the bin kind's fields no role maps, held to them at build. Without `values` they take their type's default, as a reservation's always do. `due` before the day makes it overdue. | `allocationKilnYard` |
|
|
3593
|
-
| `periods={Schedule.periods(Record.bind(days
|
|
3760
|
+
| `periods={Schedule.periods(Record.bind(days), { every: "day", status, entries? })}` | The app's days, each keyed by its UTC midnight: the status chip, Publish and the picker's Published · Draft · Not started. A published day is read only under its banner; navigation, the rail, the focus, links and the inspector still work. Without `periods`, no status chip and no Publish. | `allocationKilnYard`, `allocationPublished`, `allocationNoPublish` |
|
|
3594
3761
|
| `slice={{ slice: Slice.bindRecords(key, { <bin slot>?, <lanes>?, <resources>?, arrivals? }) }}` **❗** | One rail in both layouts. The bin kind's source reads the day's bins through an interval index from `at` to `at`, `range: "at"`, the range the board's own navigation writes — no chip picks the day — the kind given `entries`; without it the bins are read whole. The lanes', the resources' and `arrivals` each narrow every place their rows appear — the Timeline's lanes and rows, the Table's rows, the Choosers and the Library's tabs (`Resources 10 of 33`) — and the board reads each one's keys itself. The summary counts the day's bins too (`26 of 33 bin events`). The bare handle, `Slice.bind`, a single `Slice.bindRecord`, a source of another name and one keyed otherwise than by String are refused. | `allocationKilnYard`, `allocationTable`, `allocationWindowed` |
|
|
3595
3762
|
| `check={fn(Allocation.Types.CheckContext(R)) => Array<Allocation.Types.Issue>}` **❗** | The app's issues, after the built-in ones wherever issues show, over `{ day, plan, context, loads }`: each bin carries its own row (`bin.row`), so a check reads the app's own fields. An issue's `flag` marks its bin's card (`≠ spec`); a neutral or info issue is listed, not counted. | `allocationKilnYard` |
|
|
3596
3763
|
| `columns={[{ key, label, text: fn(R) => String, tone?: fn(R) => Option<StatusToken>, table? }]}` **❗** | The app's bin fields as Table columns after the quantity, and as the inspector's rows, each over the bin's own row; `table: false` shows one in the inspector only. | `allocationKilnYard` |
|