@umriss-ui/schedule 0.3.15 → 0.4.0

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/docs/llms-full.md CHANGED
@@ -1,8 +1,8 @@
1
- # @umriss-ui/schedule 0.3.15
1
+ # @umriss-ui/schedule 0.4.0
2
2
 
3
3
  > React Gantt-style schedule for data-dense applications - work on lanes over time, with dependencies, blocked time, findings and controlled editing.
4
4
 
5
- Install with `pnpm add @umriss-ui/schedule`. This text is generated from the package's demo (https://romanhaendler.github.io/umriss-ui/schedule/): every page with its import line, its examples - the source exactly as it runs, with the package name where the demo imports its own source - its props tables generated from the code, and what it deliberately does not do. The pages index stands in https://romanhaendler.github.io/umriss-ui/schedule/llms.txt.
5
+ Install with `npm install @umriss-ui/schedule @umriss-ui/core @umriss-ui/charts`. This text is generated from the package's demo (https://romanhaendler.github.io/umriss-ui/schedule/): every page with its import line, its examples - the source exactly as it runs, with the package name where the demo imports its own source - its props tables generated from the code, and what it deliberately does not do. The pages index stands in https://romanhaendler.github.io/umriss-ui/schedule/llms.txt.
6
6
 
7
7
  ## Scenarios
8
8
 
@@ -909,11 +909,15 @@ Install the schedule with its two peers and hand it plain data: lanes, tasks, su
909
909
  import { Schedule, Lane, Subtasks, Dependencies } from "@umriss-ui/schedule";
910
910
  ```
911
911
 
912
+ ```sh
913
+ npm install @umriss-ui/schedule @umriss-ui/core @umriss-ui/charts
914
+ ```
915
+
912
916
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/installation/
913
917
 
914
- Install it with its peers: `pnpm add @umriss-ui/schedule @umriss-ui/core @umriss-ui/charts`, with React 18 or 19. The stylesheets load themselves - each package's script imports its own, so there is nothing to import by hand; `@umriss-ui/schedule/styles.css` stays exported for setups that link stylesheets themselves.
918
+ The command brings its two peers along: core for the formats and the wording, charts for the time scale and the operating calendar ([ADR-0022](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0022-the-schedule-is-a-package-that-depends-on-core-and-charts.md)). React 18 or 19 is a peer as well and stays the application's own. The stylesheets load themselves - each package's script imports its own, so there is nothing to import by hand; `@umriss-ui/schedule/styles.css` stays exported for setups that link stylesheets themselves.
915
919
 
916
- The plan is plain data (ADR-0023). A lane is a person, a vehicle or a room, declared in JSX by its id. A task is an id, a colour and a name; a subtask is a main time on one lane, belonging to a task, with an optional lead-in and lead-out; a dependency joins two subtasks of one task with a lag. Times are epoch milliseconds.
920
+ The plan is plain data ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). A lane is a person, a vehicle or a room, declared in JSX by its id. A task is an id, a colour and a name; a subtask is a main time on one lane, belonging to a task, with an optional lead-in and lead-out; a dependency joins two subtasks of one task with a lag. Times are epoch milliseconds.
917
921
 
918
922
  The schedule reports, the application decides. It draws the data it is given and changes none of it: a drag ends in an intent reported to `onIntent`, and only the application's own state update moves a bar. Without `intents` the schedule is read-only - see [Move and lane](#/move-and-lane).
919
923
 
@@ -951,9 +955,74 @@ export default function SmallestSchedule() {
951
955
  }
952
956
  ```
953
957
 
954
- ## Plan
958
+ #### Keyboard
955
959
 
956
- What the schedule draws: work on lanes, and everything a bar says besides its colour.
960
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
961
+
962
+ #### Props on this page
963
+
964
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
965
+
966
+ | Prop | Type | Default | Description |
967
+ |---|---|---|---|
968
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
969
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
970
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
971
+
972
+ ###### Accessibility
973
+
974
+ | Prop | Type | Default | Description |
975
+ |---|---|---|---|
976
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
977
+
978
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
979
+
980
+ | Prop | Type | Default | Description |
981
+ |---|---|---|---|
982
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
983
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
984
+
985
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
986
+
987
+ | Prop | Type | Default | Description |
988
+ |---|---|---|---|
989
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
990
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
991
+
992
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
993
+
994
+ | Prop | Type | Default | Description |
995
+ |---|---|---|---|
996
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
997
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
998
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
999
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
1000
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
1001
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
1002
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
1003
+
1004
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
1005
+
1006
+ | Prop | Type | Default | Description |
1007
+ |---|---|---|---|
1008
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
1009
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
1010
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
1011
+
1012
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
1013
+
1014
+ | Prop | Type | Default | Description |
1015
+ |---|---|---|---|
1016
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
1017
+
1018
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
1019
+
1020
+ | Prop | Type | Default | Description |
1021
+ |---|---|---|---|
1022
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
1023
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
1024
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
1025
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
957
1026
 
958
1027
  ### First schedule
959
1028
 
@@ -969,7 +1038,7 @@ Lanes stand in the order they are declared; a subtask on a lane that is not decl
969
1038
 
970
1039
  What cannot work is drawn, never repaired: two subtasks on one lane at once, a dependency whose lag does not fit, work in blocked time. [Findings as data](#/findings) returns the same list for the application to act on.
971
1040
 
972
- The plot is one tab stop. The keys walk the subtasks as the pointer would hover them, the tooltip names the active one, and a live region reads it once the keys rest (ADR-0030, ADR-0033).
1041
+ The plot is one tab stop. The keys walk the subtasks as the pointer would hover them, the tooltip names the active one, and a live region reads it once the keys rest ([ADR-0030](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0030-a-chart-is-one-tab-stop-that-walks-its-hits.md), [ADR-0033](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0033-the-schedules-keys-walk-past-the-view.md)).
973
1042
 
974
1043
  #### Examples
975
1044
 
@@ -1181,48 +1250,271 @@ export default function MachinePlan() {
1181
1250
  | `Alt+← / Alt+→` | Proposes a move by one step of the raster, where the application handles `move`. |
1182
1251
  | `Alt+Shift+← / →` | Proposes a new end, where the application handles `stretch`. |
1183
1252
 
1253
+ #### Accessibility
1254
+
1255
+ The schedule is a figure named by `ariaLabel`, and its plot one tab stop with the role `application`, read as "schedule" (`aria-roledescription`), named alike and described by a summary: the lanes, the subtasks in view, the visible span, the findings by kind, then the keys. The canvas is hidden from a reader; the lane headers are text, and a group's fold button is named "Fold group" or "Unfold group" with the group's label. `ariaLabel` is required - name what the plan shows, "Plan of week 38".
1256
+
1257
+ When a key comes to rest, a polite live region beside the plot reads the active subtask: its lane, task and name, the day and the times, and every finding on it by name - an overlap, a dependency short by how much, blocked time. On a dependency it reads the task, the lag and where the line runs. A pointer announces nothing; [Findings as data](#/findings) gives the application the same findings to list. The words are core's wording, so under `@umriss-ui/core/wording/de` the plot is read as "Belegungsplan".
1258
+
1259
+ Under forced colours the plot paints itself in the system colours: the work in the text colour, told apart by lane and label; the active subtask in the selection colour; findings marked by a dash; grips and the ghost's label outlined. With reduced motion a group's chevron turns without its movement.
1260
+
1184
1261
  #### API
1185
1262
 
1186
1263
  ##### `ScheduleProps`
1187
1264
 
1188
1265
  | Prop | Type | Default | Description |
1189
1266
  |---|---|---|---|
1190
- | `ariaLabel` (required) | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
1191
- | `initialDomain` (required) | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
1192
- | `height` | `number` | — | Height in CSS pixels, both bands included. The width is the host's. |
1193
- | `laneHeight` | `number` | — | Height of one lane in pixels. |
1194
- | `headerWidth` | `number` | — | Width of the lane headers in pixels - at most 40 % of the schedule, so that on a phone the plot keeps the larger part. |
1195
- | `calendar` | `CalendarInput` | — | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Default: the wall clock. |
1196
- | `zoomLimits` | `ZoomLimits` | — | The narrowest and widest time span zoom may reach, in milliseconds. Default: one hour to 28 days. |
1197
- | `snap` | `"ticks" \| number \| SnapRaster \| false` | — | The raster a drag lands on: `"ticks"` - the fine band's current step -, a step in milliseconds from local midnight, a step with an offset (shifts at 06:00, 14:00 and 22:00 are eight hours offset by six), or `false` for none. It shapes the ghost and the intent, never the data. |
1198
- | `intents` | `readonly IntentKind[]` | — | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule (ADR-0023). |
1199
- | `canMoveTo` | `(subtask: Subtask, lane: string) => boolean` | — | Whether a subtask may go to a lane. Without it every lane is open; it narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`. |
1200
- | `onIntent` | `(intent: Intent) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
1201
- | `onInteraction` | `(interaction: ScheduleInteraction) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu. |
1202
- | `selectedTask` | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself. |
1203
- | `onSelectedTaskChange` | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked. |
1204
- | `collapsedGroups` | `readonly string[]` | — | The **Lane group**s that are folded, controlled. Leave it out and the schedule keeps them itself, starting from `defaultCollapsedGroups`. Folding changes the view and not the plan, which is why it is no intent: a caller that applies every intent it receives must never find a fold among them (ADR-0025). A folded outer group hides the inner ones without their entries leaving the list, so unfolding it gives back the view that was there. |
1205
- | `defaultCollapsedGroups` | `readonly string[]` | — | Which groups are folded when the schedule mounts, where the schedule keeps the state itself. Ignored while `collapsedGroups` is given. |
1206
- | `onCollapsedGroupsChange` | `(groups: readonly string[]) => void` | — | Called with the whole list when the planner folds or unfolds a group. |
1207
- | `onDomainChange` | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back. |
1208
- | `placing` | `PlacingItem \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted. |
1209
- | `route` | `DependencyRoute` | — | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself. |
1210
- | `attach` | `DependencyAttachment` | — | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone. |
1211
- | `ends` | `DependencyEnds` | — | Whether a dependency's two ends carry a dot (`"dot"`, the default) or the line stands alone (`"none"`). The dot says where the line is anchored - a help while a plan is being read, and noise in a plan full of short moves. A dependency may say otherwise for itself. |
1212
- | `label` | `(subtask: Subtask) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
1213
- | `tooltip` | `false \| ((target: ScheduleTooltipTarget) => ReactNode)` | — | The tooltip on a hovered subtask or dependency: its order, its times, its parts and its findings. `false` switches it off; a function receives what the pointer rests on and returns content of the application's own. |
1214
- | `now` | `boolean \| number` | — | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there. Off by default. |
1267
+ | `initialDomain` *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 74 more |
1268
+ | `height` | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 74 more |
1269
+ | `laneHeight` | `number` | `44` | Height of one lane in pixels.<br>Shown in: [Scroll more lanes than fit (Lanes)](#/lane/many-lanes) |
1270
+ | `headerWidth` | `number` | `160` | Width of the lane headers in pixels - at most 40 % of the schedule, so that on a phone the plot keeps the larger part.<br>Shown in: [What a lane header says (Lanes)](#/lane/lane-headers), [Scroll more lanes than fit (Lanes)](#/lane/many-lanes), [Group lanes under a head (Lane groups)](#/lane-groups/a-group) and 1 more |
1271
+ | `calendar` | [`CalendarInput`](#type-CalendarInput) | `[]` | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Without intervals every hour counts, as on the wall clock.<br>Shown in: [Cut the nights out of the axis (Time axis and calendar)](#/time-axis/working-calendar), [Plan in office hours only (Time axis and calendar)](#/time-axis/office-hours), [Plan the sprint around leave (Scenarios)](#/scenarios/plan-the-sprint-around-leave) |
1272
+ | `zoomLimits` | [`ZoomLimits`](#type-ZoomLimits) | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds.<br>Shown in: [Move through the plan (Pan and zoom)](#/pan-and-zoom/pan-and-zoom) |
1273
+ | `snap` | `"ticks" \| number \| `[`SnapRaster`](#type-SnapRaster)` \| false` | `"ticks"` | The raster a drag lands on: `"ticks"` - the fine band's current step -, a step in milliseconds from local midnight, a step with an offset (shifts at 06:00, 14:00 and 22:00 are eight hours offset by six), or `false` for none. It shapes the ghost and the intent, never the data.<br>Shown in: [Snap to a duration (Snapping)](#/snapping/snap-to-a-duration), [Snap to handover times (Snapping)](#/snapping/snap-to-handovers), [Place without snapping (Snapping)](#/snapping/no-snapping) |
1274
+ | `intents` | `readonly `[`IntentKind`](#type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)).<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Cut the nights out of the axis (Time axis and calendar)](#/time-axis/working-calendar), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around) and 18 more |
1275
+ | `canMoveTo` | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`, lane: string) => boolean` | every lane is open | Whether a subtask may go to a lane. It narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`.<br>Shown in: [Keep work on the lanes that fit (Where a subtask may go)](#/where-it-may-go/lanes-that-fit), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in), [Keep work out of someone's leave (Where a subtask may go)](#/where-it-may-go/around-leave) and 1 more |
1276
+ | `selectedTask` | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself.<br>Shown in: [Control the selection (Selection)](#/selection/control-the-selection) |
1277
+ | `onSelectedTaskChange` | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked.<br>Shown in: [Control the selection (Selection)](#/selection/control-the-selection) |
1278
+ | `defaultCollapsedGroups` | `readonly string[]` | — | Which groups are folded when the schedule mounts, where the schedule keeps the state itself. Ignored while `collapsedGroups` is given.<br>Shown in: [Start with groups folded (Lane groups)](#/lane-groups/start-with-groups-folded) |
1279
+ | `collapsedGroups` | `readonly string[]` | — | The **Lane group**s that are folded, controlled. Leave it out and the schedule keeps them itself, starting from `defaultCollapsedGroups`. Folding changes the view and not the plan, which is why it is no intent: a caller that applies every intent it receives must never find a fold among them ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)). A folded outer group hides the inner ones without their entries leaving the list, so unfolding it gives back the view that was there.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/controlled), [Read a folded group (Lane groups)](#/lane-groups/the-miniature) |
1280
+ | `onCollapsedGroupsChange` | `(groups: readonly string[]) => void` | — | Called with the whole list when the planner folds or unfolds a group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/controlled), [Read a folded group (Lane groups)](#/lane-groups/the-miniature) |
1281
+ | `placing` | [`PlacingItem`](#type-PlacingItem)` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
1282
+ | `route` | [`DependencyRoute`](#type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | `"curve"` | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself.<br>Shown in: [Straight (Routes)](#/routes/straight), [Orthogonal (Routes)](#/routes/orthogonal), [Per dependency (Routes)](#/routes/per-dependency) |
1283
+ | `attach` | [`DependencyAttachment`](#type-DependencyAttachment)<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone.<br>Shown in: [Choose where a line touches its bars (Dependencies)](#/dependencies/attach), [Mark the ends with a dot (Dependencies)](#/dependencies/ends), [Draw one handover its own way (Dependencies)](#/dependencies/one-handover-drawn-its-own-way) and 3 more |
1284
+ | `ends` | [`DependencyEnds`](#type-DependencyEnds)<br>`"dot" \| "none"` | `"dot"` | Whether a dependency's two ends carry a dot (`"dot"`) or the line stands alone (`"none"`). The dot says where the line is anchored - a help while a plan is being read, and noise in a plan full of short moves. A dependency may say otherwise for itself.<br>Shown in: [Mark the ends with a dot (Dependencies)](#/dependencies/ends), [Draw one handover its own way (Dependencies)](#/dependencies/one-handover-drawn-its-own-way) |
1285
+ | `label` | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge.<br>Shown in: [Plan a day in the machine shop](#/schedule/machine-plan), [Plan a sprint for a team (Subtasks)](#/subtasks/a-sprint), [Write into the bars (Bar labels)](#/bar-labels/bar-labels) and 7 more |
1286
+ | `tooltip` | `false \| ((target: `[`ScheduleTooltipTarget`](#/tooltip/type-ScheduleTooltipTarget)`) => ReactNode)` | — | The tooltip on a hovered subtask or dependency: its order, its times, its parts and its findings. `false` switches it off; a function receives what the pointer rests on and returns content of the application's own.<br>Shown in: [Write your own tooltip content (Tooltip)](#/tooltip/own-tooltip), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead), [Say what a bar covers and a line joins (Tooltip)](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
1287
+ | `now` | `boolean \| number` | `false` | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there.<br>Shown in: [Plan a day in the machine shop](#/schedule/machine-plan), [Mark the present (Now line)](#/now-line/now-line), [Follow the clock (Now line)](#/now-line/follow-the-clock) and 2 more |
1288
+ | `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 74 more |
1289
+
1290
+ ###### Events
1291
+
1292
+ | Prop | Type | Default | Description |
1293
+ |---|---|---|---|
1294
+ | `onIntent` | `(intent: `[`Intent`](#type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Cut the nights out of the axis (Time axis and calendar)](#/time-axis/working-calendar), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around) and 18 more |
1295
+ | `onInteraction` | `(interaction: `[`ScheduleInteraction`](#/interactions/type-ScheduleInteraction)`) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu.<br>Shown in: [Report what the pointer is on (Interactions)](#/interactions/report-the-pointer), [Open a context menu (Interactions)](#/interactions/open-a-context-menu), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead) and 2 more |
1296
+ | `onDomainChange` | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back.<br>Shown in: [Keep two schedules in step (Linked schedules)](#/linked-schedules/in-step), [Read a rota beside the incidents (Linked schedules)](#/linked-schedules/rota-beside-incidents), [Place a mark of your own (The handle)](#/handle/place-a-mark) |
1297
+
1298
+ ###### Accessibility
1299
+
1300
+ | Prop | Type | Default | Description |
1301
+ |---|---|---|---|
1302
+ | `ariaLabel` *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38".<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 74 more |
1303
+
1304
+ ###### Styling
1305
+
1306
+ | Prop | Type | Default | Description |
1307
+ |---|---|---|---|
1215
1308
  | `className` | `string` | — | Goes to the root element. |
1216
1309
  | `style` | `CSSProperties` | — | Goes to the root element. |
1217
- | `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
1310
+
1311
+ ##### Types on this page
1312
+
1313
+ ###### `CalendarInput`
1314
+
1315
+ From `@umriss-ui/charts`.
1316
+
1317
+ Every function of this module takes either: the raw list as it stands on the
1318
+ axis, or the calendar built from it.
1319
+
1320
+ ```ts
1321
+ type CalendarInput = WorkingCalendar | readonly WorkingInterval[];
1322
+ ```
1323
+
1324
+ ###### `ZoomLimits`
1325
+
1326
+ The narrowest and the widest span zoom may reach.
1327
+
1328
+ | Prop | Type | Default | Description |
1329
+ |---|---|---|---|
1330
+ | `min` *required* | `number` | — | <br>Shown in: [Move through the plan (Pan and zoom)](#/pan-and-zoom/pan-and-zoom) |
1331
+ | `max` *required* | `number` | — | <br>Shown in: [Move through the plan (Pan and zoom)](#/pan-and-zoom/pan-and-zoom) |
1332
+
1333
+ ###### `SnapRaster`
1334
+
1335
+ A raster: a step, and where it starts after local midnight.
1336
+
1337
+ | Prop | Type | Default | Description |
1338
+ |---|---|---|---|
1339
+ | `step` *required* | `number` | — | The distance between two lines of the raster, in milliseconds.<br>Shown in: [Snap to handover times (Snapping)](#/snapping/snap-to-handovers) |
1340
+ | `offset` *required* | `number` | — | Where the raster starts after local midnight, in milliseconds: six hours for handovers at 06:00, 14:00 and 22:00.<br>Shown in: [Snap to handover times (Snapping)](#/snapping/snap-to-handovers) |
1341
+
1342
+ ###### `IntentKind`
1343
+
1344
+ The names of the intents - the editing API of the schedule.
1345
+
1346
+ ```ts
1347
+ type IntentKind = Intent["kind"];
1348
+ ```
1349
+
1350
+ ###### `PlacingItem`
1351
+
1352
+ What the application says it is dragging in, while it drags it
1353
+ (schedule-refinement 07): the browser hands over the dragged data only on
1354
+ the drop, so the ghost before the drop can only come from the caller.
1355
+
1356
+ | Prop | Type | Default | Description |
1357
+ |---|---|---|---|
1358
+ | `item` *required* | `string` | — | The caller's key for the dragged item; it comes back in the intent.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
1359
+ | `task` *required* | `string` | — | The task the work belongs to.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
1360
+ | `duration` *required* | `number` | — | How long its main time is.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
1361
+ | `leadIn` | `number` | — | The lead-in it brings. |
1362
+ | `leadOut` | `number` | — | The lead-out it brings. |
1363
+
1364
+ ###### `DependencyRoute`
1365
+
1366
+ How a dependency is drawn between its two ends: a curve that leaves and
1367
+ arrives forwards, a straight line, or axis-parallel segments.
1368
+
1369
+ ```ts
1370
+ type DependencyRoute = "curve" | "straight" | "orthogonal";
1371
+ ```
1372
+
1373
+ ###### `DependencyAttachment`
1374
+
1375
+ Where on its bars a dependency's line attaches: the middle of both, or the
1376
+ edge that faces the other stop - which is the shortest line between them.
1377
+
1378
+ Deliberately not called an anchor: a dependency already HAS two anchors
1379
+ (`leaves`, `arrives`), and those decide what it connects and therefore
1380
+ whether it is violated. This decides where the line touches, and nothing else.
1381
+ The tree has an **Anchor** of its own as well (CONTEXT.md).
1382
+
1383
+ ```ts
1384
+ type DependencyAttachment = "centre" | "nearest";
1385
+ ```
1386
+
1387
+ ###### `DependencyEnds`
1388
+
1389
+ Whether a dependency's two ends carry a dot. The dot says where the line is
1390
+ anchored, which is worth saying while a plan is being read and is noise in a
1391
+ plan full of short moves - so it is the caller's choice.
1392
+
1393
+ ```ts
1394
+ type DependencyEnds = "dot" | "none";
1395
+ ```
1396
+
1397
+ ###### `Intent`
1398
+
1399
+ What the schedule reports when an interaction asks for a change. Every
1400
+ intent carries the values it asks for, not a difference: two intents of one
1401
+ drop - a move and a lane - give the same data in either order.
1402
+
1403
+ ```ts
1404
+ type Intent = MoveIntent | StretchIntent | LeadInIntent | LeadOutIntent | LaneIntent | PlaceIntent;
1405
+ ```
1406
+
1407
+ ###### `WorkingCalendar`
1408
+
1409
+ From `@umriss-ui/charts`.
1410
+
1411
+ A working calendar: sorted, merged intervals with the working time
1412
+ accumulated before each one begins. An empty calendar is no calendar: it maps
1413
+ unchanged.
1414
+
1415
+ Build once, then pass around: the mapping runs once per point, and were it to
1416
+ sort and accumulate every time, the calendar would be the most expensive
1417
+ thing about the frame.
1418
+
1419
+ | Prop | Type | Default | Description |
1420
+ |---|---|---|---|
1421
+ | `intervals` *required* | `readonly `[`WorkingInterval`](#type-WorkingInterval)`[]` | — | |
1422
+ | `offsets` *required* | `readonly number[]` | — | Working time accumulated before the respective interval begins. |
1423
+ | `total` *required* | `number` | — | The whole working time - the axis' extent is [0, total]. |
1424
+
1425
+ ###### `WorkingInterval`
1426
+
1427
+ From `@umriss-ui/charts`.
1428
+
1429
+ An interval in which time counts; thought of as half-open, but mappable from
1430
+ both sides at its edges: both `from` and `to` have a working time.
1431
+
1432
+ | Prop | Type | Default | Description |
1433
+ |---|---|---|---|
1434
+ | `from` *required* | `number` | — | |
1435
+ | `to` *required* | `number` | — | |
1436
+
1437
+ ###### `MoveIntent`
1438
+
1439
+ Move the subtask in time: the main time keeps its length.
1440
+
1441
+ | Prop | Type | Default | Description |
1442
+ |---|---|---|---|
1443
+ | `kind` *required* | `"move"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 8 more |
1444
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 5 more |
1445
+ | `from` *required* | `number` | — | The new start of the main time.<br>Shown in: [Push the later stops of a tour (Ripple)](#/ripple/push-the-later-stops), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
1446
+ | `to` *required* | `number` | — | The new end of the main time.<br>Shown in: [Push the later stops of a tour (Ripple)](#/ripple/push-the-later-stops), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
1447
+
1448
+ ###### `StretchIntent`
1449
+
1450
+ Stretch the main time at one or both of its edges.
1451
+
1452
+ | Prop | Type | Default | Description |
1453
+ |---|---|---|---|
1454
+ | `kind` *required* | `"stretch"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
1455
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
1456
+ | `from` *required* | `number` | — | The new start of the main time. |
1457
+ | `to` *required* | `number` | — | The new end of the main time. |
1458
+
1459
+ ###### `LeadInIntent`
1460
+
1461
+ Change the lead-in on its own.
1462
+
1463
+ | Prop | Type | Default | Description |
1464
+ |---|---|---|---|
1465
+ | `kind` *required* | `"leadIn"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
1466
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
1467
+ | `leadIn` *required* | `number` | — | The new duration of the lead-in.<br>Shown in: [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
1468
+
1469
+ ###### `LeadOutIntent`
1470
+
1471
+ Change the lead-out on its own.
1472
+
1473
+ | Prop | Type | Default | Description |
1474
+ |---|---|---|---|
1475
+ | `kind` *required* | `"leadOut"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
1476
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
1477
+ | `leadOut` *required* | `number` | — | The new duration of the lead-out. |
1478
+
1479
+ ###### `LaneIntent`
1480
+
1481
+ Put the subtask on another lane.
1482
+
1483
+ | Prop | Type | Default | Description |
1484
+ |---|---|---|---|
1485
+ | `kind` *required* | `"lane"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
1486
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
1487
+ | `lane` *required* | `string` | — | The id of the new lane. |
1488
+
1489
+ ###### `PlaceIntent`
1490
+
1491
+ Put work on the plan that was not on it: the answer to a drag from outside
1492
+ the schedule (schedule-refinement 07). It carries no subtask id - there is
1493
+ no subtask yet; the caller creates one, with its own identity, from
1494
+ `subtaskFromPlace`.
1495
+
1496
+ | Prop | Type | Default | Description |
1497
+ |---|---|---|---|
1498
+ | `kind` *required* | `"place"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
1499
+ | `item` *required* | `string` | — | The caller's key for the dragged item, as it declared it while dragging.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
1500
+ | `task` *required* | `string` | — | The task the work belongs to. |
1501
+ | `lane` *required* | `string` | — | The lane it was dropped on.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
1502
+ | `from` *required* | `number` | — | Where its main time would start. |
1503
+ | `to` *required* | `number` | — | Where its main time would end. |
1504
+ | `leadIn` | `number` | — | The lead-in it was declared with. |
1505
+ | `leadOut` | `number` | — | The lead-out it was declared with. |
1218
1506
 
1219
1507
  #### Known limits
1220
1508
 
1221
- - No dependency types, critical path or auto-scheduling: the application decides where work goes, the schedule shows the result and its findings (ADR-0032).
1509
+ - No dependency types, critical path or auto-scheduling: the application decides where work goes, the schedule shows the result and its findings ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
1222
1510
  - No milestones yet: a point in time on a lane has no mark of its own.
1223
1511
  - A dependency joins two subtasks of one task; dependencies across tasks are not yet possible.
1224
1512
 
1225
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
1513
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
1514
+
1515
+ ## Plan
1516
+
1517
+ What the schedule draws: work on lanes, and everything a bar says besides its colour.
1226
1518
 
1227
1519
  ### Lanes
1228
1520
 
@@ -1342,14 +1634,18 @@ export default function ManyLanes() {
1342
1634
 
1343
1635
  - Lanes that belong together, folded into one row → Lane groups
1344
1636
 
1637
+ #### Keyboard
1638
+
1639
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
1640
+
1345
1641
  #### API
1346
1642
 
1347
1643
  ##### `LaneProps`
1348
1644
 
1349
1645
  | Prop | Type | Default | Description |
1350
1646
  |---|---|---|---|
1351
- | `id` (required) | `string` | — | The identity subtasks name in their `lane`. |
1352
- | `label` | `ReactNode` | — | What the lane header says - the person, vehicle or room. Real text, read by a screen reader; without one the header shows the id. |
1647
+ | `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 74 more |
1648
+ | `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 74 more |
1353
1649
 
1354
1650
  ### Lane groups
1355
1651
 
@@ -1361,7 +1657,7 @@ import { LaneGroup, Lane } from "@umriss-ui/schedule";
1361
1657
 
1362
1658
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane-groups/
1363
1659
 
1364
- A group is structure, never a lane: nothing sits on it, no finding is reported for it and no intent names it (ADR-0025). The lanes keep the order they were declared in, whatever group they are in.
1660
+ A group is structure, never a lane: nothing sits on it, no finding is reported for it and no intent names it ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)). The lanes keep the order they were declared in, whatever group they are in.
1365
1661
 
1366
1662
  Folded, a group shows a miniature: every lane in it as a thin strip with its work in the tasks' colours. Dependencies still arrive at the right strip, findings are marked on the row, and a strip can be hovered and selected. Labels, appearances and grips need room and wait for the group to open.
1367
1663
 
@@ -1605,6 +1901,50 @@ export default function TheMiniature() {
1605
1901
  }
1606
1902
  ```
1607
1903
 
1904
+ ##### Start with groups folded
1905
+
1906
+ `defaultCollapsedGroups` folds groups when the schedule mounts and leaves the rest to the planner: Discovery starts folded, and its chevron opens it.
1907
+
1908
+ ```tsx
1909
+ import { Lane, LaneGroup, Schedule, Subtasks } from "@umriss-ui/schedule";
1910
+ import type { Subtask, Task } from "@umriss-ui/schedule";
1911
+
1912
+ /* Uncontrolled: the schedule keeps the fold from here on. To store it or
1913
+ steer it, pass `collapsedGroups` instead. */
1914
+
1915
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
1916
+
1917
+ const TASKS: Task[] = [{ id: "release", name: "Release 4.12", color: "light-dark(#0d9488, #3cc7b8)" }];
1918
+
1919
+ const WORK: Subtask[] = [
1920
+ { id: "s1", task: "release", lane: "priya", from: at(7), to: at(9) },
1921
+ { id: "s2", task: "release", lane: "jonas", from: at(9, 30), to: at(11) },
1922
+ { id: "s3", task: "release", lane: "tomasz", from: at(11, 30), to: at(13) },
1923
+ { id: "s4", task: "release", lane: "leila", from: at(13), to: at(14, 30) },
1924
+ ];
1925
+
1926
+ export default function StartWithGroupsFolded() {
1927
+ return (
1928
+ <Schedule
1929
+ ariaLabel="Two teams rolling out a release, Discovery folded at the start"
1930
+ initialDomain={[at(6), at(15)]}
1931
+ height={280}
1932
+ defaultCollapsedGroups={["discovery"]}
1933
+ >
1934
+ <LaneGroup id="payments" label="Payments">
1935
+ <Lane id="priya" label="Priya Raman" />
1936
+ <Lane id="jonas" label="Jonas Keller" />
1937
+ </LaneGroup>
1938
+ <LaneGroup id="discovery" label="Discovery">
1939
+ <Lane id="tomasz" label="Tomasz Nowak" />
1940
+ <Lane id="leila" label="Leila Haddad" />
1941
+ </LaneGroup>
1942
+ <Subtasks data={WORK} tasks={TASKS} />
1943
+ </Schedule>
1944
+ );
1945
+ }
1946
+ ```
1947
+
1608
1948
  #### Keyboard
1609
1949
 
1610
1950
  | Key | Action |
@@ -1612,21 +1952,23 @@ export default function TheMiniature() {
1612
1952
  | `Tab` | Reaches each group's fold button - the only buttons the schedule puts in the tab order. |
1613
1953
  | `Enter / Space` | Folds or unfolds the group. |
1614
1954
 
1955
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
1956
+
1615
1957
  #### API
1616
1958
 
1617
1959
  ##### `LaneGroupProps`
1618
1960
 
1619
1961
  | Prop | Type | Default | Description |
1620
1962
  |---|---|---|---|
1621
- | `id` (required) | `string` | — | The group's identity - what `collapsedGroups` names. It is never a lane: no subtask sits on it, no finding is reported for it, and no intent names it (ADR-0025). |
1622
- | `label` | `ReactNode` | — | What the group's header says - the team, the depot, the region. Real text, read by a screen reader; without one the header shows the id. |
1623
- | `children` | `ReactNode` | — | `Lane`s and further `LaneGroup`s, to any depth. |
1963
+ | `id` *required* | `string` | — | The group's identity - what `collapsedGroups` names. It is never a lane: no subtask sits on it, no finding is reported for it, and no intent names it ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)).<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/controlled) and 4 more |
1964
+ | `label` | `ReactNode` | `id` | What the group's header says - the team, the depot, the region. Real text, read by a screen reader.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/controlled) and 4 more |
1965
+ | `children` | `ReactNode` | — | `Lane`s and further `LaneGroup`s, to any depth.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/controlled) and 4 more |
1624
1966
 
1625
1967
  #### Known limits
1626
1968
 
1627
- - A folded group shows no summary - no utilisation band, no bar spanning its work: a computed claim would look like a drawn fact (ADR-0025).
1969
+ - A folded group shows no summary - no utilisation band, no bar spanning its work: a computed claim would look like a drawn fact ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)).
1628
1970
 
1629
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
1971
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
1630
1972
 
1631
1973
  ### Subtasks
1632
1974
 
@@ -1815,37 +2157,51 @@ export default function ASprint() {
1815
2157
  }
1816
2158
  ```
1817
2159
 
2160
+ #### Keyboard
2161
+
2162
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2163
+
1818
2164
  #### API
1819
2165
 
1820
2166
  ##### `SubtasksProps`
1821
2167
 
1822
2168
  | Prop | Type | Default | Description |
1823
2169
  |---|---|---|---|
1824
- | `data` (required) | `readonly Subtask[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
1825
- | `tasks` (required) | `readonly Task[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
2170
+ | `data` *required* | `readonly `[`Subtask`](#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 74 more |
2171
+ | `tasks` *required* | `readonly `[`Task`](#type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 74 more |
1826
2172
 
1827
2173
  ##### `Subtask`
1828
2174
 
1829
2175
  | Prop | Type | Default | Description |
1830
2176
  |---|---|---|---|
1831
- | `id` (required) | `string` | — | The caller's identity of the subtask. |
1832
- | `task` (required) | `string` | — | The id of the task it belongs to. |
1833
- | `lane` (required) | `string` | — | The id of the lane it sits on. |
1834
- | `from` (required) | `number` | — | Start of the main time. |
1835
- | `to` (required) | `number` | — | End of the main time. |
1836
- | `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero. |
1837
- | `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero. |
1838
- | `name` | `string` | — | What the subtask is called, where the schedule names it. |
1839
- | `appearance` | `readonly SubtaskAppearance[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins. |
1840
- | `progress` | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements. |
2177
+ | `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 71 more |
2178
+ | `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
2179
+ | `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
2180
+ | `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
2181
+ | `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
2182
+ | `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 24 more |
2183
+ | `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 21 more |
2184
+ | `name` | `string` | — | What the subtask is called, where the schedule names it.<br>Shown in: [Plan a sprint for a team](#/subtasks/a-sprint), [Write into the bars (Bar labels)](#/bar-labels/bar-labels), [Join the stops of a tour (Dependencies)](#/dependencies/stops-of-a-tour) and 25 more |
2185
+ | `appearance` | `readonly `[`SubtaskAppearance`](#type-SubtaskAppearance)`[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins.<br>Shown in: [Plan a sprint for a team](#/subtasks/a-sprint), [Provisional (Appearances)](#/appearances/provisional), [Fixed (Appearances)](#/appearances/fixed) and 3 more |
2186
+ | `progress` | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements.<br>Shown in: [Progress (Appearances)](#/appearances/progress), [Several at once (Appearances)](#/appearances/combinations) |
1841
2187
 
1842
2188
  ##### `Task`
1843
2189
 
1844
2190
  | Prop | Type | Default | Description |
1845
2191
  |---|---|---|---|
1846
- | `id` (required) | `string` | — | The caller's identity of the task. |
1847
- | `color` (required) | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
1848
- | `name` | `string` | — | What the task is called, where the schedule names it. |
2192
+ | `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 66 more |
2193
+ | `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 66 more |
2194
+ | `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 66 more |
2195
+
2196
+ ##### Types on this page
2197
+
2198
+ ###### `SubtaskAppearance`
2199
+
2200
+ The appearances a caller may give a subtask.
2201
+
2202
+ ```ts
2203
+ type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";
2204
+ ```
1849
2205
 
1850
2206
  ### Bar labels
1851
2207
 
@@ -1913,6 +2269,62 @@ export default function BarLabels() {
1913
2269
  }
1914
2270
  ```
1915
2271
 
2272
+ #### Keyboard
2273
+
2274
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2275
+
2276
+ #### Props on this page
2277
+
2278
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
2279
+
2280
+ | Prop | Type | Default | Description |
2281
+ |---|---|---|---|
2282
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
2283
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2284
+ | [`label`](#/schedule/ScheduleProps-label) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
2285
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
2286
+
2287
+ ###### Accessibility
2288
+
2289
+ | Prop | Type | Default | Description |
2290
+ |---|---|---|---|
2291
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
2292
+
2293
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
2294
+
2295
+ | Prop | Type | Default | Description |
2296
+ |---|---|---|---|
2297
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
2298
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
2299
+
2300
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
2301
+
2302
+ | Prop | Type | Default | Description |
2303
+ |---|---|---|---|
2304
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
2305
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
2306
+
2307
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
2308
+
2309
+ | Prop | Type | Default | Description |
2310
+ |---|---|---|---|
2311
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
2312
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
2313
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
2314
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
2315
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
2316
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
2317
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
2318
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
2319
+
2320
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2321
+
2322
+ | Prop | Type | Default | Description |
2323
+ |---|---|---|---|
2324
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
2325
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
2326
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2327
+
1916
2328
  ### Appearances
1917
2329
 
1918
2330
  What a bar says besides its colour: provisional, fixed, muted, running on past the view, and how far the work has got. Each takes one channel of the drawing, so several on one bar stay readable.
@@ -1925,7 +2337,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/appearances/
1925
2337
 
1926
2338
  `"provisional"` owns the fill (none at all), `"fixed"` the ends (a cap inside each), `"muted"` the saturation, `"open"` a fade at the edge of the view, and `progress` a rail inside the main time. The faint, outlined fill belongs to a lead-in and a lead-out and to nothing else.
1927
2339
 
1928
- `"provisional"` and `"fixed"` contradict, and the later one in the list wins. `resolveAppearance` is that rule, exported for the application to read as well.
2340
+ `"provisional"` and `"fixed"` contradict, and the later one in the list wins. [`resolveAppearance`](#/api/resolveAppearance) is that rule, exported for the application to read as well.
1929
2341
 
1930
2342
  #### Examples
1931
2343
 
@@ -2156,6 +2568,63 @@ export default function Combinations() {
2156
2568
  }
2157
2569
  ```
2158
2570
 
2571
+ #### Keyboard
2572
+
2573
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2574
+
2575
+ #### Props on this page
2576
+
2577
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
2578
+
2579
+ | Prop | Type | Default | Description |
2580
+ |---|---|---|---|
2581
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
2582
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2583
+ | [`label`](#/schedule/ScheduleProps-label) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
2584
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
2585
+
2586
+ ###### Accessibility
2587
+
2588
+ | Prop | Type | Default | Description |
2589
+ |---|---|---|---|
2590
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
2591
+
2592
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
2593
+
2594
+ | Prop | Type | Default | Description |
2595
+ |---|---|---|---|
2596
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
2597
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
2598
+
2599
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
2600
+
2601
+ | Prop | Type | Default | Description |
2602
+ |---|---|---|---|
2603
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
2604
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
2605
+
2606
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
2607
+
2608
+ | Prop | Type | Default | Description |
2609
+ |---|---|---|---|
2610
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
2611
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
2612
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
2613
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
2614
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
2615
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
2616
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
2617
+ | [`appearance`](#/subtasks/Subtask-appearance) | `readonly `[`SubtaskAppearance`](#/subtasks/type-SubtaskAppearance)`[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins. |
2618
+ | [`progress`](#/subtasks/Subtask-progress) | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements. |
2619
+
2620
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2621
+
2622
+ | Prop | Type | Default | Description |
2623
+ |---|---|---|---|
2624
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
2625
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
2626
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2627
+
2159
2628
  ### Overlap
2160
2629
 
2161
2630
  Two subtasks claiming one lane at the same time (also called a double booking or conflict): both drawn, offset and marked, never packed into sub-lanes.
@@ -2271,18 +2740,72 @@ export default function ThreeAtOnce() {
2271
2740
 
2272
2741
  - The overlaps as a list to count and act on → Findings as data
2273
2742
 
2274
- #### Known limits
2743
+ #### Keyboard
2275
2744
 
2276
- - No stacked layout on request yet: an overlap is always drawn offset, as the finding it is.
2745
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2277
2746
 
2278
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
2747
+ #### Props on this page
2279
2748
 
2280
- ### Dependencies
2749
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
2281
2750
 
2282
- Lines from one subtask's end to the next one's start within a task, with the lag that must pass between them (also called links or predecessors). A lag that does not fit is marked, not repaired.
2751
+ | Prop | Type | Default | Description |
2752
+ |---|---|---|---|
2753
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
2754
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2755
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
2283
2756
 
2284
- ```ts
2285
- import { Dependencies } from "@umriss-ui/schedule";
2757
+ ###### Accessibility
2758
+
2759
+ | Prop | Type | Default | Description |
2760
+ |---|---|---|---|
2761
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
2762
+
2763
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
2764
+
2765
+ | Prop | Type | Default | Description |
2766
+ |---|---|---|---|
2767
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
2768
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
2769
+
2770
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
2771
+
2772
+ | Prop | Type | Default | Description |
2773
+ |---|---|---|---|
2774
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
2775
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
2776
+
2777
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
2778
+
2779
+ | Prop | Type | Default | Description |
2780
+ |---|---|---|---|
2781
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
2782
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
2783
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
2784
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
2785
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
2786
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
2787
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
2788
+
2789
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2790
+
2791
+ | Prop | Type | Default | Description |
2792
+ |---|---|---|---|
2793
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
2794
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
2795
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2796
+
2797
+ #### Known limits
2798
+
2799
+ - No stacked layout on request yet: an overlap is always drawn offset, as the finding it is.
2800
+
2801
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
2802
+
2803
+ ### Dependencies
2804
+
2805
+ Lines from one subtask's end to the next one's start within a task, with the lag that must pass between them (also called links or predecessors). A lag that does not fit is marked, not repaired.
2806
+
2807
+ ```ts
2808
+ import { Dependencies } from "@umriss-ui/schedule";
2286
2809
  ```
2287
2810
 
2288
2811
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/dependencies/
@@ -2526,37 +3049,124 @@ export default function ViolatedDependency() {
2526
3049
  }
2527
3050
  ```
2528
3051
 
3052
+ ##### Draw one handover its own way
3053
+
3054
+ A dependency's own `attach` and `ends` override the schedule's for that one line: here the sign-off joins the middles and marks its ends, the rest stay quiet.
3055
+
3056
+ ```tsx
3057
+ import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
3058
+ import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
3059
+
3060
+ /* Picture only, like the schedule's own `attach` and `ends`: the finding
3061
+ follows from `leaves` and `arrives` alone. */
3062
+
3063
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
3064
+ const min = (n: number) => n * 60_000;
3065
+
3066
+ const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#2563eb, #6b9bff)" }];
3067
+
3068
+ const WORK: Subtask[] = [
3069
+ { id: "design", task: "portal", lane: "noah", from: at(7), to: at(8), name: "Design" },
3070
+ { id: "build", task: "portal", lane: "arjun", from: at(8, 30), to: at(9, 30), name: "Build" },
3071
+ { id: "test", task: "portal", lane: "eva", from: at(10), to: at(11), name: "Test" },
3072
+ { id: "sign-off", task: "portal", lane: "noah", from: at(11, 30), to: at(12), name: "Sign-off" },
3073
+ ];
3074
+
3075
+ const HANDOVERS: Dependency[] = [
3076
+ { id: "to-build", from: "design", to: "build", lag: min(15) },
3077
+ { id: "to-test", from: "build", to: "test", lag: min(15) },
3078
+ /* The one that matters: the designer signs off what was tested. */
3079
+ { id: "to-sign-off", from: "test", to: "sign-off", lag: min(15), attach: "centre", ends: "dot" },
3080
+ ];
3081
+
3082
+ export default function OneHandoverDrawnItsOwnWay() {
3083
+ return (
3084
+ <Schedule ariaLabel="Design, build, test and a sign-off drawn its own way" initialDomain={[at(6, 30), at(12, 30)]} height={188} attach="nearest" ends="none">
3085
+ <Lane id="noah" label="Noah Fischer" />
3086
+ <Lane id="arjun" label="Arjun Mehta" />
3087
+ <Lane id="eva" label="Eva Novak" />
3088
+ <Dependencies data={HANDOVERS} />
3089
+ <Subtasks data={WORK} tasks={PROJECTS} />
3090
+ </Schedule>
3091
+ );
3092
+ }
3093
+ ```
3094
+
2529
3095
  #### When to use something else
2530
3096
 
2531
3097
  - The shape of the line → Routes
2532
3098
 
3099
+ #### Keyboard
3100
+
3101
+ | Key | Action |
3102
+ |---|---|
3103
+ | `] or t` | Out along the active subtask's dependency; pressed again, on to the subtask it arrives at. |
3104
+ | `[ or Shift+T` | Back along the dependency to the subtask it leaves. |
3105
+
3106
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
3107
+
2533
3108
  #### API
2534
3109
 
2535
3110
  ##### `DependenciesProps`
2536
3111
 
2537
3112
  | Prop | Type | Default | Description |
2538
3113
  |---|---|---|---|
2539
- | `data` (required) | `readonly Dependency[]` | — | The dependencies, as the caller holds them. |
3114
+ | `data` *required* | `readonly `[`Dependency`](#type-Dependency)`[]` | — | The dependencies, as the caller holds them.<br>Shown in: [Join the stops of a tour](#/dependencies/stops-of-a-tour), [Choose what a dependency connects](#/dependencies/anchors), [Choose where a line touches its bars](#/dependencies/attach) and 27 more |
2540
3115
 
2541
3116
  ##### `Dependency`
2542
3117
 
2543
3118
  | Prop | Type | Default | Description |
2544
3119
  |---|---|---|---|
2545
- | `id` (required) | `string` | — | The caller's identity of the dependency. |
2546
- | `from` (required) | `string` | — | The id of the subtask it leaves. |
2547
- | `to` (required) | `string` | — | The id of the subtask it arrives at. |
2548
- | `lag` (required) | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
2549
- | `leaves` | `"main" \| "leadOut"` | — | Where it leaves: at the end of the main time, or after the lead-out. Default `"leadOut"` - the predecessor counts as ended once its lead-out is. |
2550
- | `arrives` | `"main" \| "leadIn"` | — | Where it arrives: at the start of the main time, or before the lead-in. Default `"leadIn"` - the lag must have run out before the successor's lead-in begins. |
2551
- | `route` | `DependencyRoute` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone. |
2552
- | `attach` | `DependencyAttachment` | — | Where on the bars this one's line attaches, where it does not attach like the rest. Also picture only, and not to be confused with the two anchors above: those say what the dependency connects, this says where the line touches. |
2553
- | `ends` | `DependencyEnds` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only. |
3120
+ | `id` *required* | `string` | — | The caller's identity of the dependency.<br>Shown in: [Join the stops of a tour](#/dependencies/stops-of-a-tour), [Choose what a dependency connects](#/dependencies/anchors), [Choose where a line touches its bars](#/dependencies/attach) and 26 more |
3121
+ | `from` *required* | `string` | — | The id of the subtask it leaves.<br>Shown in: [Join the stops of a tour](#/dependencies/stops-of-a-tour), [Choose what a dependency connects](#/dependencies/anchors), [Choose where a line touches its bars](#/dependencies/attach) and 26 more |
3122
+ | `to` *required* | `string` | — | The id of the subtask it arrives at.<br>Shown in: [Join the stops of a tour](#/dependencies/stops-of-a-tour), [Choose what a dependency connects](#/dependencies/anchors), [Choose where a line touches its bars](#/dependencies/attach) and 26 more |
3123
+ | `lag` *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends.<br>Shown in: [Join the stops of a tour](#/dependencies/stops-of-a-tour), [Choose what a dependency connects](#/dependencies/anchors), [Choose where a line touches its bars](#/dependencies/attach) and 26 more |
3124
+ | `leaves` | `"main" \| "leadOut"` | `"leadOut"` | Where it leaves: at the end of the main time, or after the lead-out. The predecessor counts as ended once its lead-out is.<br>Shown in: [Choose what a dependency connects](#/dependencies/anchors), [Plan a day in the machine shop (First schedule)](#/schedule/machine-plan), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
3125
+ | `arrives` | `"main" \| "leadIn"` | `"leadIn"` | Where it arrives: at the start of the main time, or before the lead-in. The lag must have run out before the successor's lead-in begins.<br>Shown in: [Choose what a dependency connects](#/dependencies/anchors), [A day of tours (First schedule)](#/schedule/first-schedule), [Plan a day in the machine shop (First schedule)](#/schedule/machine-plan) and 1 more |
3126
+ | `route` | [`DependencyRoute`](#type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone.<br>Shown in: [Per dependency (Routes)](#/routes/per-dependency) |
3127
+ | `attach` | [`DependencyAttachment`](#type-DependencyAttachment)<br>`"centre" \| "nearest"` | — | Where on the bars this one's line attaches, where it does not attach like the rest. Also picture only, and not to be confused with the two anchors above: those say what the dependency connects, this says where the line touches.<br>Shown in: [Draw one handover its own way](#/dependencies/one-handover-drawn-its-own-way) |
3128
+ | `ends` | [`DependencyEnds`](#type-DependencyEnds)<br>`"dot" \| "none"` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only.<br>Shown in: [Draw one handover its own way](#/dependencies/one-handover-drawn-its-own-way) |
3129
+
3130
+ ##### Types on this page
3131
+
3132
+ ###### `DependencyRoute`
3133
+
3134
+ How a dependency is drawn between its two ends: a curve that leaves and
3135
+ arrives forwards, a straight line, or axis-parallel segments.
3136
+
3137
+ ```ts
3138
+ type DependencyRoute = "curve" | "straight" | "orthogonal";
3139
+ ```
3140
+
3141
+ ###### `DependencyAttachment`
3142
+
3143
+ Where on its bars a dependency's line attaches: the middle of both, or the
3144
+ edge that faces the other stop - which is the shortest line between them.
3145
+
3146
+ Deliberately not called an anchor: a dependency already HAS two anchors
3147
+ (`leaves`, `arrives`), and those decide what it connects and therefore
3148
+ whether it is violated. This decides where the line touches, and nothing else.
3149
+ The tree has an **Anchor** of its own as well (CONTEXT.md).
3150
+
3151
+ ```ts
3152
+ type DependencyAttachment = "centre" | "nearest";
3153
+ ```
3154
+
3155
+ ###### `DependencyEnds`
3156
+
3157
+ Whether a dependency's two ends carry a dot. The dot says where the line is
3158
+ anchored, which is worth saying while a plan is being read and is noise in a
3159
+ plan full of short moves - so it is the caller's choice.
3160
+
3161
+ ```ts
3162
+ type DependencyEnds = "dot" | "none";
3163
+ ```
2554
3164
 
2555
3165
  #### Known limits
2556
3166
 
2557
- - Only finish-to-start within one task; no dependency types and no critical path (ADR-0032).
3167
+ - Only finish-to-start within one task; no dependency types and no critical path ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
2558
3168
 
2559
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
3169
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
2560
3170
 
2561
3171
  ### Routes
2562
3172
 
@@ -2727,6 +3337,77 @@ export default function PerDependency() {
2727
3337
  }
2728
3338
  ```
2729
3339
 
3340
+ #### Keyboard
3341
+
3342
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
3343
+
3344
+ #### Props on this page
3345
+
3346
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
3347
+
3348
+ | Prop | Type | Default | Description |
3349
+ |---|---|---|---|
3350
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
3351
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
3352
+ | [`route`](#/schedule/ScheduleProps-route) | [`DependencyRoute`](#/schedule/type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | `"curve"` | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself. |
3353
+ | [`attach`](#/schedule/ScheduleProps-attach) | [`DependencyAttachment`](#/schedule/type-DependencyAttachment)<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone. |
3354
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
3355
+
3356
+ ###### Accessibility
3357
+
3358
+ | Prop | Type | Default | Description |
3359
+ |---|---|---|---|
3360
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
3361
+
3362
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
3363
+
3364
+ | Prop | Type | Default | Description |
3365
+ |---|---|---|---|
3366
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
3367
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
3368
+
3369
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
3370
+
3371
+ | Prop | Type | Default | Description |
3372
+ |---|---|---|---|
3373
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
3374
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
3375
+
3376
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
3377
+
3378
+ | Prop | Type | Default | Description |
3379
+ |---|---|---|---|
3380
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
3381
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
3382
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
3383
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
3384
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
3385
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
3386
+
3387
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
3388
+
3389
+ | Prop | Type | Default | Description |
3390
+ |---|---|---|---|
3391
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
3392
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
3393
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
3394
+
3395
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
3396
+
3397
+ | Prop | Type | Default | Description |
3398
+ |---|---|---|---|
3399
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
3400
+
3401
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
3402
+
3403
+ | Prop | Type | Default | Description |
3404
+ |---|---|---|---|
3405
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
3406
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
3407
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
3408
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
3409
+ | [`route`](#/dependencies/Dependency-route) | [`DependencyRoute`](#/dependencies/type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone. |
3410
+
2730
3411
  ## Time
2731
3412
 
2732
3413
  The axis the work stands on: the calendar, blocked time, the now line, and moving through it.
@@ -2979,6 +3660,85 @@ export default function OfficeHours() {
2979
3660
 
2980
3661
  - Time a single lane is not available → Blocked time
2981
3662
 
3663
+ #### Keyboard
3664
+
3665
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
3666
+
3667
+ #### Props on this page
3668
+
3669
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
3670
+
3671
+ | Prop | Type | Default | Description |
3672
+ |---|---|---|---|
3673
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
3674
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
3675
+ | [`calendar`](#/schedule/ScheduleProps-calendar) | [`CalendarInput`](#/schedule/type-CalendarInput) | `[]` | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Without intervals every hour counts, as on the wall clock. |
3676
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
3677
+ | [`label`](#/schedule/ScheduleProps-label) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
3678
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
3679
+
3680
+ ###### Events
3681
+
3682
+ | Prop | Type | Default | Description |
3683
+ |---|---|---|---|
3684
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
3685
+
3686
+ ###### Accessibility
3687
+
3688
+ | Prop | Type | Default | Description |
3689
+ |---|---|---|---|
3690
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
3691
+
3692
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
3693
+
3694
+ | Prop | Type | Default | Description |
3695
+ |---|---|---|---|
3696
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
3697
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
3698
+
3699
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
3700
+
3701
+ | Prop | Type | Default | Description |
3702
+ |---|---|---|---|
3703
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
3704
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
3705
+
3706
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
3707
+
3708
+ | Prop | Type | Default | Description |
3709
+ |---|---|---|---|
3710
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
3711
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
3712
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
3713
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
3714
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
3715
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
3716
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
3717
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
3718
+
3719
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
3720
+
3721
+ | Prop | Type | Default | Description |
3722
+ |---|---|---|---|
3723
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
3724
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
3725
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
3726
+
3727
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
3728
+
3729
+ | Prop | Type | Default | Description |
3730
+ |---|---|---|---|
3731
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
3732
+
3733
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
3734
+
3735
+ | Prop | Type | Default | Description |
3736
+ |---|---|---|---|
3737
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
3738
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
3739
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
3740
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
3741
+
2982
3742
  ### Blocked time
2983
3743
 
2984
3744
  Leave, maintenance and other time a lane is not available: hatched behind the work, a finding where work covers it, and closed to a drag.
@@ -2989,7 +3749,7 @@ import { BlockedTimes, findings } from "@umriss-ui/schedule";
2989
3749
 
2990
3750
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/
2991
3751
 
2992
- Blocked time is data beside the work: one list, each interval naming its lane, as a leave table or a maintenance plan holds it. `findings` takes the same list as its third argument.
3752
+ Blocked time is data beside the work: one list, each interval naming its lane, as a leave table or a maintenance plan holds it. [`findings`](#/api/findings) takes the same list as its third argument.
2993
3753
 
2994
3754
  Work the data already put into blocked time is never locked there: it may be moved within it and out of it, and the finding stays until it is.
2995
3755
 
@@ -3135,7 +3895,7 @@ export default function DragAround() {
3135
3895
 
3136
3896
  ##### Check a sprint against the leave calendar
3137
3897
 
3138
- The team's holidays, sick days and training become `BlockedTimes`, and `findings` confirms that no work item runs into one.
3898
+ The team's holidays, sick days and training become `BlockedTimes`, and [`findings`](#/api/findings) confirms that no work item runs into one.
3139
3899
 
3140
3900
  ```tsx
3141
3901
  import { Stack, Text } from "@umriss-ui/core";
@@ -3270,23 +4030,27 @@ export default function TeamLeave() {
3270
4030
 
3271
4031
  - Hours nobody works, on every lane → Time axis and calendar
3272
4032
 
4033
+ #### Keyboard
4034
+
4035
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4036
+
3273
4037
  #### API
3274
4038
 
3275
4039
  ##### `BlockedTimesProps`
3276
4040
 
3277
4041
  | Prop | Type | Default | Description |
3278
4042
  |---|---|---|---|
3279
- | `data` (required) | `readonly BlockedTime[]` | — | The blocked time of the lanes - leave, maintenance, unavailability -, as the caller holds it: one list, each interval naming its lane. |
4043
+ | `data` *required* | `readonly `[`BlockedTime`](#type-BlockedTime)`[]` | — | The blocked time of the lanes - leave, maintenance, unavailability -, as the caller holds it: one list, each interval naming its lane.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
3280
4044
 
3281
4045
  ##### `BlockedTime`
3282
4046
 
3283
4047
  | Prop | Type | Default | Description |
3284
4048
  |---|---|---|---|
3285
- | `id` (required) | `string` | — | The caller's identity of the interval - what a finding names. |
3286
- | `lane` (required) | `string` | — | The id of the lane it blocks. |
3287
- | `from` (required) | `number` | — | Where it begins. |
3288
- | `to` (required) | `number` | — | Where it ends. |
3289
- | `label` | `string` | — | Why the lane is blocked - "Leave", "Maintenance" -, where the schedule names it. |
4049
+ | `id` *required* | `string` | — | The caller's identity of the interval - what a finding names.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
4050
+ | `lane` *required* | `string` | — | The id of the lane it blocks.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
4051
+ | `from` *required* | `number` | — | Where it begins.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
4052
+ | `to` *required* | `number` | — | Where it ends.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
4053
+ | `label` | `string` | — | Why the lane is blocked - "Leave", "Maintenance" -, where the schedule names it.<br>Shown in: [Block out leave and maintenance](#/blocked-time/leave), [Plan work around someone's leave](#/blocked-time/drag-around), [Check a sprint against the leave calendar](#/blocked-time/team-leave) and 5 more |
3290
4054
 
3291
4055
  ### Now line
3292
4056
 
@@ -3502,6 +4266,60 @@ export default function FollowTheClock() {
3502
4266
  }
3503
4267
  ```
3504
4268
 
4269
+ #### Keyboard
4270
+
4271
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4272
+
4273
+ #### Props on this page
4274
+
4275
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
4276
+
4277
+ | Prop | Type | Default | Description |
4278
+ |---|---|---|---|
4279
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
4280
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4281
+ | [`now`](#/schedule/ScheduleProps-now) | `boolean \| number` | `false` | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there. |
4282
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
4283
+
4284
+ ###### Accessibility
4285
+
4286
+ | Prop | Type | Default | Description |
4287
+ |---|---|---|---|
4288
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
4289
+
4290
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
4291
+
4292
+ | Prop | Type | Default | Description |
4293
+ |---|---|---|---|
4294
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
4295
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
4296
+
4297
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
4298
+
4299
+ | Prop | Type | Default | Description |
4300
+ |---|---|---|---|
4301
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
4302
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
4303
+
4304
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
4305
+
4306
+ | Prop | Type | Default | Description |
4307
+ |---|---|---|---|
4308
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
4309
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
4310
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
4311
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
4312
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
4313
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
4314
+
4315
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4316
+
4317
+ | Prop | Type | Default | Description |
4318
+ |---|---|---|---|
4319
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
4320
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
4321
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
4322
+
3505
4323
  ### Pan and zoom
3506
4324
 
3507
4325
  Moving through a plan with the gestures of any scrolling surface: drag, wheel, Ctrl with the wheel, and a pinch. Only the view moves; the plan stays as it is.
@@ -3514,7 +4332,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/pan-and-zoom/
3514
4332
 
3515
4333
  Dragging the background pans through time and through the lanes. The wheel scrolls the lanes and hands the scroll back to the page at their end; Shift with the wheel pans through time; Ctrl or ⌘ with the wheel, or a pinch, zooms around the pointer.
3516
4334
 
3517
- Nothing is reported and no intent is raised: pan and zoom change the visible span and nothing else (ADR-0001). `onDomainChange` reports the span for a second view to follow - see [Linked schedules](#/linked-schedules).
4335
+ Nothing is reported and no intent is raised: pan and zoom change the visible span and nothing else ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)). `onDomainChange` reports the span for a second view to follow - see [Linked schedules](#/linked-schedules).
3518
4336
 
3519
4337
  #### Examples
3520
4338
 
@@ -3567,6 +4385,66 @@ export default function PanAndZoom() {
3567
4385
  }
3568
4386
  ```
3569
4387
 
4388
+ #### Keyboard
4389
+
4390
+ | Key | Action |
4391
+ |---|---|
4392
+ | `Home / End` | The first or last subtask on the lane; the view pans to bring it in. |
4393
+ | `PageUp / PageDown` | Along the lane by about a tenth of the visible span, the view following. |
4394
+
4395
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4396
+
4397
+ #### Props on this page
4398
+
4399
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
4400
+
4401
+ | Prop | Type | Default | Description |
4402
+ |---|---|---|---|
4403
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
4404
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4405
+ | [`zoomLimits`](#/schedule/ScheduleProps-zoomLimits) | [`ZoomLimits`](#/schedule/type-ZoomLimits) | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds. |
4406
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
4407
+
4408
+ ###### Accessibility
4409
+
4410
+ | Prop | Type | Default | Description |
4411
+ |---|---|---|---|
4412
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
4413
+
4414
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
4415
+
4416
+ | Prop | Type | Default | Description |
4417
+ |---|---|---|---|
4418
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
4419
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
4420
+
4421
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
4422
+
4423
+ | Prop | Type | Default | Description |
4424
+ |---|---|---|---|
4425
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
4426
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
4427
+
4428
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
4429
+
4430
+ | Prop | Type | Default | Description |
4431
+ |---|---|---|---|
4432
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
4433
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
4434
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
4435
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
4436
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
4437
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
4438
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
4439
+
4440
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4441
+
4442
+ | Prop | Type | Default | Description |
4443
+ |---|---|---|---|
4444
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
4445
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
4446
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
4447
+
3570
4448
  ## Reading
3571
4449
 
3572
4450
  What a planner takes out of the picture: what is selected, what the pointer is on, and what a second view makes of it.
@@ -3721,37 +4599,106 @@ export default function ControlTheSelection() {
3721
4599
  | `Enter or Space` | Selects the active subtask's task, as a click does. |
3722
4600
  | `Escape` | Lets go of the active subtask. |
3723
4601
 
3724
- #### Known limits
4602
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
3725
4603
 
3726
- - One task at a time: there is no multiple selection.
4604
+ #### Props on this page
3727
4605
 
3728
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
4606
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
3729
4607
 
3730
- ### Interactions
4608
+ | Prop | Type | Default | Description |
4609
+ |---|---|---|---|
4610
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
4611
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4612
+ | [`selectedTask`](#/schedule/ScheduleProps-selectedTask) | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself. |
4613
+ | [`onSelectedTaskChange`](#/schedule/ScheduleProps-onSelectedTaskChange) | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked. |
4614
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
3731
4615
 
3732
- Reports what the pointer does on the plan - hover, click, right-click - with what it hit and the time and lane under it. Reach for it to open a menu, fill a detail panel or write a status line.
4616
+ ###### Accessibility
3733
4617
 
3734
- ```ts
3735
- import { Schedule } from "@umriss-ui/schedule";
3736
- ```
4618
+ | Prop | Type | Default | Description |
4619
+ |---|---|---|---|
4620
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
3737
4621
 
3738
- Demo page: https://romanhaendler.github.io/umriss-ui/schedule/interactions/
4622
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
3739
4623
 
3740
- A hit names a subtask together with the part of it (lead-in, main time, lead-out), a dependency, a lane, or nothing.
4624
+ | Prop | Type | Default | Description |
4625
+ |---|---|---|---|
4626
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
4627
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
3741
4628
 
3742
- While `onInteraction` is set, a right-click opens no browser menu: your application is expected to answer it.
4629
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
3743
4630
 
3744
- #### Examples
4631
+ | Prop | Type | Default | Description |
4632
+ |---|---|---|---|
4633
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
4634
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
3745
4635
 
3746
- ##### Report what the pointer is on
4636
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
3747
4637
 
3748
- `onInteraction` hears hover, click and right-click with the target, its part, the time and the lane - here written into a status line.
4638
+ | Prop | Type | Default | Description |
4639
+ |---|---|---|---|
4640
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
4641
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
4642
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
4643
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
4644
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
4645
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
4646
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
3749
4647
 
3750
- ```tsx
3751
- import { useState } from "react";
3752
- import { Stack, Text } from "@umriss-ui/core";
3753
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3754
- import type { Dependency, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
4648
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4649
+
4650
+ | Prop | Type | Default | Description |
4651
+ |---|---|---|---|
4652
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
4653
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
4654
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
4655
+
4656
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
4657
+
4658
+ | Prop | Type | Default | Description |
4659
+ |---|---|---|---|
4660
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
4661
+
4662
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
4663
+
4664
+ | Prop | Type | Default | Description |
4665
+ |---|---|---|---|
4666
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
4667
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
4668
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
4669
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
4670
+
4671
+ #### Known limits
4672
+
4673
+ - One task at a time: there is no multiple selection.
4674
+
4675
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
4676
+
4677
+ ### Interactions
4678
+
4679
+ Reports what the pointer does on the plan - hover, click, right-click - with what it hit and the time and lane under it. Reach for it to open a menu, fill a detail panel or write a status line.
4680
+
4681
+ ```ts
4682
+ import { Schedule } from "@umriss-ui/schedule";
4683
+ ```
4684
+
4685
+ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/interactions/
4686
+
4687
+ A hit names a subtask together with the part of it (lead-in, main time, lead-out), a dependency, a lane, or nothing.
4688
+
4689
+ While `onInteraction` is set, a right-click opens no browser menu: your application is expected to answer it.
4690
+
4691
+ #### Examples
4692
+
4693
+ ##### Report what the pointer is on
4694
+
4695
+ `onInteraction` hears hover, click and right-click with the target, its part, the time and the lane - here written into a status line.
4696
+
4697
+ ```tsx
4698
+ import { useState } from "react";
4699
+ import { Stack, Text } from "@umriss-ui/core";
4700
+ import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4701
+ import type { Dependency, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
3755
4702
 
3756
4703
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
3757
4704
  const min = (n: number) => n * 60_000;
@@ -3777,9 +4724,9 @@ const describe = (interaction: ScheduleInteraction): string => {
3777
4724
  : hit.kind === "dependency"
3778
4725
  ? `dependency ${hit.dependency.id}`
3779
4726
  : hit.kind === "lane"
3780
- ? `lane ${hit.lane}`
4727
+ ? "free time"
3781
4728
  : "nothing";
3782
- return `${interaction.type}: ${target} at ${time}`;
4729
+ return `${interaction.type}: ${target} at ${time}, lane ${interaction.lane ?? "none"}`;
3783
4730
  };
3784
4731
 
3785
4732
  export default function ReportThePointer() {
@@ -3929,18 +4876,36 @@ export default function OpenAContextMenu() {
3929
4876
  - You only need to know which task was picked → Selection
3930
4877
  - You need the time at a point outside an interaction → The handle
3931
4878
 
4879
+ #### Keyboard
4880
+
4881
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4882
+
3932
4883
  #### API
3933
4884
 
3934
4885
  ##### `ScheduleInteraction`
3935
4886
 
3936
4887
  | Prop | Type | Default | Description |
3937
4888
  |---|---|---|---|
3938
- | `type` (required) | `"click" \| "contextmenu" \| "hover"` | — | `"hover"` is reported when the target under the pointer changes, not on every movement. |
3939
- | `hit` (required) | `ScheduleHit` | — | What the pointer is on. |
3940
- | `clientX` (required) | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens. |
3941
- | `clientY` (required) | `number` | — | The pointer's vertical client coordinate. |
3942
- | `time` (required) | `number` | — | The wall-clock instant under the pointer. |
3943
- | `lane` (required) | `string \| null` | — | The lane under the pointer, or null below the last one. |
4889
+ | `type` *required* | `"click" \| "contextmenu" \| "hover"` | — | `"hover"` is reported when the target under the pointer changes, not on every movement.<br>Shown in: [Report what the pointer is on](#/interactions/report-the-pointer), [Open a context menu](#/interactions/open-a-context-menu), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead) and 1 more |
4890
+ | `hit` *required* | [`ScheduleHit`](#type-ScheduleHit) | — | What the pointer is on.<br>Shown in: [Report what the pointer is on](#/interactions/report-the-pointer), [Open a context menu](#/interactions/open-a-context-menu), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead) and 1 more |
4891
+ | `clientX` *required* | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens.<br>Shown in: [Open a context menu](#/interactions/open-a-context-menu), [Place a mark of your own (The handle)](#/handle/place-a-mark), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
4892
+ | `clientY` *required* | `number` | — | The pointer's vertical client coordinate.<br>Shown in: [Open a context menu](#/interactions/open-a-context-menu), [Place a mark of your own (The handle)](#/handle/place-a-mark), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
4893
+ | `time` *required* | `number` | — | The wall-clock instant under the pointer.<br>Shown in: [Report what the pointer is on](#/interactions/report-the-pointer) |
4894
+ | `lane` *required* | `string \| null` | — | The lane under the pointer, or null below the last one.<br>Shown in: [Report what the pointer is on](#/interactions/report-the-pointer) |
4895
+
4896
+ ##### Types on this page
4897
+
4898
+ ###### `ScheduleHit`
4899
+
4900
+ What a pointer is on.
4901
+
4902
+ ```ts
4903
+ type ScheduleHit =
4904
+ | { readonly kind: "subtask"; readonly subtask: Subtask; readonly part: "leadIn" | "main" | "leadOut" }
4905
+ | { readonly kind: "dependency"; readonly dependency: Dependency }
4906
+ | { readonly kind: "lane"; readonly lane: string }
4907
+ | { readonly kind: "nothing" };
4908
+ ```
3944
4909
 
3945
4910
  ### Tooltip
3946
4911
 
@@ -4225,22 +5190,99 @@ export default function DetailPanelInstead() {
4225
5190
  }
4226
5191
  ```
4227
5192
 
5193
+ ##### Say what a bar covers and a line joins
5194
+
5195
+ The target says what is hovered: a bar brings its `subtask` and the `blocked` time it covers, a line its `dependency` with the subtasks it joins, `from` and `to`.
5196
+
5197
+ ```tsx
5198
+ import { Stack, Text } from "@umriss-ui/core";
5199
+ import { BlockedTimes, Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5200
+ import type { BlockedTime, Dependency, ScheduleTooltipTarget, Subtask, Task } from "@umriss-ui/schedule";
5201
+
5202
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
5203
+ const min = (n: number) => n * 60_000;
5204
+
5205
+ const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#2563eb, #6b9bff)" }];
5206
+
5207
+ const WORK: Subtask[] = [
5208
+ { id: "design", task: "portal", lane: "noah", from: at(7), to: at(9), name: "Design" },
5209
+ { id: "build", task: "portal", lane: "arjun", from: at(9, 30), to: at(13), name: "Build" },
5210
+ { id: "test", task: "portal", lane: "eva", from: at(13, 30), to: at(15), name: "Test" },
5211
+ ];
5212
+
5213
+ const HANDOVERS: Dependency[] = [
5214
+ { id: "to-build", from: "design", to: "build", lag: min(15) },
5215
+ { id: "to-test", from: "build", to: "test", lag: min(15) },
5216
+ ];
5217
+
5218
+ const LEAVE: BlockedTime[] = [{ id: "arjun-dentist", lane: "arjun", from: at(11), to: at(12), label: "Dentist" }];
5219
+
5220
+ const time = (instant: number) => new Date(instant).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
5221
+
5222
+ function Covers({ target }: { target: ScheduleTooltipTarget }) {
5223
+ if (target.kind === "dependency") {
5224
+ return (
5225
+ <Stack gap={1}>
5226
+ <Text size="sm" weight="semibold">
5227
+ Handover {target.dependency.id}
5228
+ </Text>
5229
+ <Text size="xs" tone="secondary">
5230
+ {target.from?.name ?? "?"} → {target.to?.name ?? "?"}
5231
+ </Text>
5232
+ </Stack>
5233
+ );
5234
+ }
5235
+ return (
5236
+ <Stack gap={1}>
5237
+ <Text size="sm" weight="semibold">
5238
+ {target.subtask.name}
5239
+ </Text>
5240
+ <Text size="xs" tone="secondary">
5241
+ {time(target.subtask.from)} – {time(target.subtask.to)}
5242
+ </Text>
5243
+ {target.blocked.map((blocked) => (
5244
+ <Text size="xs" tone="secondary" key={blocked.id}>
5245
+ Covers {blocked.label}, {time(blocked.from)} – {time(blocked.to)}
5246
+ </Text>
5247
+ ))}
5248
+ </Stack>
5249
+ );
5250
+ }
5251
+
5252
+ export default function SayWhatABarCoversAndALineJoins() {
5253
+ return (
5254
+ <Schedule ariaLabel="Design, build and test, with leave under the build" initialDomain={[at(6, 30), at(15, 30)]} height={188} tooltip={(target) => <Covers target={target} />}>
5255
+ <Lane id="noah" label="Noah Fischer" />
5256
+ <Lane id="arjun" label="Arjun Mehta" />
5257
+ <Lane id="eva" label="Eva Novak" />
5258
+ <BlockedTimes data={LEAVE} />
5259
+ <Dependencies data={HANDOVERS} />
5260
+ <Subtasks data={WORK} tasks={PROJECTS} />
5261
+ </Schedule>
5262
+ );
5263
+ }
5264
+ ```
5265
+
5266
+ #### Keyboard
5267
+
5268
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
5269
+
4228
5270
  #### API
4229
5271
 
4230
5272
  ##### `ScheduleTooltipTarget`
4231
5273
 
4232
5274
  | Prop | Type | Default | Description |
4233
5275
  |---|---|---|---|
4234
- | `kind` (required) | `"subtask" \| "dependency"` | — | A subtask is hovered. A dependency is hovered. |
4235
- | `subtask` | `Subtask` | — | The hovered subtask. |
4236
- | `task` (required) | `Task \| undefined` | — | Its task, where the tasks name it. |
4237
- | `overlapping` | `readonly Subtask[]` | — | The subtasks it overlaps with on its lane. |
4238
- | `violatedDependencies` | `readonly ViolatedDependency[]` | — | The violated dependencies leaving or reaching it. |
4239
- | `blocked` | `readonly BlockedTime[]` | — | The blocked time it covers on its lane. |
4240
- | `dependency` | `Dependency` | — | The hovered dependency. |
4241
- | `from` | `Subtask \| undefined` | — | The subtask it leaves. |
4242
- | `to` | `Subtask \| undefined` | — | The subtask it reaches. |
4243
- | `violated` | `ViolatedDependency \| undefined` | — | Its finding, where it is violated. |
5276
+ | `kind` *required* | `"subtask" \| "dependency"` | — | A subtask is hovered. A dependency is hovered.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip), [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5277
+ | `subtask` | [`Subtask`](#/subtasks/type-Subtask) | — | The hovered subtask.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5278
+ | `task` *required* | [`Task`](#/subtasks/type-Task)` \| undefined` | — | Its task, where the tasks name it.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
5279
+ | `overlapping` | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks it overlaps with on its lane.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
5280
+ | `violatedDependencies` | `readonly `[`ViolatedDependency`](#/findings/type-ViolatedDependency)`[]` | — | The violated dependencies leaving or reaching it.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
5281
+ | `blocked` | `readonly `[`BlockedTime`](#/blocked-time/type-BlockedTime)`[]` | — | The blocked time it covers on its lane.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5282
+ | `dependency` | [`Dependency`](#/dependencies/type-Dependency) | — | The hovered dependency.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5283
+ | `from` | [`Subtask`](#/subtasks/type-Subtask)` \| undefined` | — | The subtask it leaves.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5284
+ | `to` | [`Subtask`](#/subtasks/type-Subtask)` \| undefined` | — | The subtask it reaches.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
5285
+ | `violated` | [`ViolatedDependency`](#/findings/type-ViolatedDependency)` \| undefined` | — | Its finding, where it is violated.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
4244
5286
 
4245
5287
  ### Linked schedules
4246
5288
 
@@ -4486,11 +5528,72 @@ export default function RotaBesideIncidents() {
4486
5528
 
4487
5529
  - The lanes belong together in one plan → Lane groups
4488
5530
 
5531
+ #### Keyboard
5532
+
5533
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
5534
+
5535
+ #### Props on this page
5536
+
5537
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
5538
+
5539
+ | Prop | Type | Default | Description |
5540
+ |---|---|---|---|
5541
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
5542
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
5543
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
5544
+
5545
+ ###### Events
5546
+
5547
+ | Prop | Type | Default | Description |
5548
+ |---|---|---|---|
5549
+ | [`onDomainChange`](#/schedule/ScheduleProps-onDomainChange) | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back. |
5550
+
5551
+ ###### Accessibility
5552
+
5553
+ | Prop | Type | Default | Description |
5554
+ |---|---|---|---|
5555
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
5556
+
5557
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
5558
+
5559
+ | Prop | Type | Default | Description |
5560
+ |---|---|---|---|
5561
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
5562
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
5563
+
5564
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
5565
+
5566
+ | Prop | Type | Default | Description |
5567
+ |---|---|---|---|
5568
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
5569
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
5570
+
5571
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
5572
+
5573
+ | Prop | Type | Default | Description |
5574
+ |---|---|---|---|
5575
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
5576
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
5577
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
5578
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
5579
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
5580
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
5581
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
5582
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
5583
+
5584
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
5585
+
5586
+ | Prop | Type | Default | Description |
5587
+ |---|---|---|---|
5588
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
5589
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
5590
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
5591
+
4489
5592
  #### Known limits
4490
5593
 
4491
5594
  - Only the span is shared - not the selection, the hover or the vertical scroll.
4492
5595
 
4493
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
5596
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
4494
5597
 
4495
5598
  ### The handle
4496
5599
 
@@ -4597,109 +5700,392 @@ export default function PlaceAMark() {
4597
5700
 
4598
5701
  - You need the time under the pointer during a hover or a click → Interactions
4599
5702
 
4600
- ## Editing
5703
+ #### Keyboard
4601
5704
 
4602
- Direct manipulation that changes nothing by itself: the schedule reports, the application decides.
5705
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4603
5706
 
4604
- ### Move and lane
5707
+ #### Props on this page
4605
5708
 
4606
- Drag a bar to a new time or onto another lane (also called drag-and-drop rescheduling). A ghost shows where it would land and what that would cause; the drop reports the change for your application to apply.
5709
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
4607
5710
 
4608
- ```ts
4609
- import { Schedule, applyIntent } from "@umriss-ui/schedule";
4610
- ```
5711
+ | Prop | Type | Default | Description |
5712
+ |---|---|---|---|
5713
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
5714
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
5715
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
4611
5716
 
4612
- Demo page: https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/
5717
+ ###### Events
4613
5718
 
4614
- Each name in `intents` enables one gesture. Without `intents` the schedule is read-only, and pressing a bar pans the plot.
5719
+ | Prop | Type | Default | Description |
5720
+ |---|---|---|---|
5721
+ | [`onInteraction`](#/schedule/ScheduleProps-onInteraction) | `(interaction: `[`ScheduleInteraction`](#/interactions/type-ScheduleInteraction)`) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu. |
5722
+ | [`onDomainChange`](#/schedule/ScheduleProps-onDomainChange) | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back. |
4615
5723
 
4616
- The schedule never changes your data (ADR-0023): `onIntent` reports a move and a lane change as values, and `applyIntent` is the arithmetic to apply them. A drop across lanes and time reports both, and your handler may apply them, refuse them, or apply one.
5724
+ ###### Accessibility
4617
5725
 
4618
- Held at the edge of the plot, a drag pans it along; Escape cancels the drag.
5726
+ | Prop | Type | Default | Description |
5727
+ |---|---|---|---|
5728
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
4619
5729
 
4620
- #### Examples
5730
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
4621
5731
 
4622
- ##### Move work in time
5732
+ | Prop | Type | Default | Description |
5733
+ |---|---|---|---|
5734
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
5735
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
4623
5736
 
4624
- List `move` in `intents` and apply what `onIntent` reports with `applyIntent`; the plan changes only in your state.
5737
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
4625
5738
 
4626
- ```tsx
4627
- import { useState } from "react";
4628
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
4629
- import type { Subtask, Task } from "@umriss-ui/schedule";
5739
+ | Prop | Type | Default | Description |
5740
+ |---|---|---|---|
5741
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
5742
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
4630
5743
 
4631
- const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
5744
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
4632
5745
 
4633
- const PROJECTS: readonly Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" }];
5746
+ | Prop | Type | Default | Description |
5747
+ |---|---|---|---|
5748
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
5749
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
5750
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
5751
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
5752
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
5753
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
5754
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
4634
5755
 
4635
- const START: readonly Subtask[] = [
4636
- { id: "w-102", task: "portal", lane: "arjun", from: day(17), to: day(18, 17), name: "Profile page" },
4637
- { id: "w-105", task: "portal", lane: "chloe", from: day(18), to: day(19, 17), name: "Change of address form" },
4638
- ];
5756
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4639
5757
 
4640
- export default function MoveInTime() {
4641
- const [work, setWork] = useState(START);
4642
- return (
4643
- <Schedule
4644
- ariaLabel="Two people's work this week"
4645
- initialDomain={[day(16, 0), day(21, 0)]}
4646
- height={140}
4647
- intents={["move"]}
4648
- onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
4649
- >
4650
- <Lane id="arjun" label="Arjun Mehta" />
4651
- <Lane id="chloe" label="Chloe Durand" />
4652
- <Subtasks data={work} tasks={PROJECTS} />
4653
- </Schedule>
4654
- );
4655
- }
5758
+ | Prop | Type | Default | Description |
5759
+ |---|---|---|---|
5760
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
5761
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
5762
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
5763
+
5764
+ From `ScheduleInteraction` — the full table stands on [Interactions](#/interactions/type-ScheduleInteraction).
5765
+
5766
+ | Prop | Type | Default | Description |
5767
+ |---|---|---|---|
5768
+ | [`clientX`](#/interactions/ScheduleInteraction-clientX) *required* | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens. |
5769
+ | [`clientY`](#/interactions/ScheduleInteraction-clientY) *required* | `number` | — | The pointer's vertical client coordinate. |
5770
+
5771
+ ### Findings as data
5772
+
5773
+ Lists what is wrong with a plan - double bookings on a lane, dependencies that do not fit, work in blocked time - from the same data the schedule draws. Reach for it to list, count or filter the problems beside the picture.
5774
+
5775
+ ```ts
5776
+ import { findings, overlaps, violatedDependencies, inBlockedTime } from "@umriss-ui/schedule";
4656
5777
  ```
4657
5778
 
4658
- ##### Move work onto another lane
5779
+ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/findings/
4659
5780
 
4660
- Add `lane` to `intents`: a drop reports a move and a lane change separately, and the ghost shows the findings first.
5781
+ `findings(subtasks, dependencies, blocked)` returns all three kinds; [`overlaps`](#/api/overlaps), [`violatedDependencies`](#/api/violatedDependencies) and [`inBlockedTime`](#/api/inBlockedTime) return one each. They are pure functions: run them wherever the data changes, with or without a schedule on screen.
5782
+
5783
+ Lead-in and lead-out count as occupied time; touching at a shared edge is no overlap.
5784
+
5785
+ #### Examples
5786
+
5787
+ ##### List the findings
5788
+
5789
+ [`findings`](#/api/findings) returns what the schedule marks - overlaps, violated dependencies, work in blocked time - as data for a list beside it.
4661
5790
 
4662
5791
  ```tsx
4663
- import { useState } from "react";
4664
5792
  import { Stack, Text } from "@umriss-ui/core";
4665
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
4666
- import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
4667
-
4668
- const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
4669
- const min = (n: number) => n * 60_000;
5793
+ import { BlockedTimes, Dependencies, Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
5794
+ import type { BlockedTime, Dependency, Subtask, Task } from "@umriss-ui/schedule";
4670
5795
 
4671
- const VEHICLES = [
4672
- { id: "truck-118", label: "Truck FP 118 R" },
4673
- { id: "van-402", label: "Van FP 402 R" },
4674
- { id: "truck-520", label: "Truck FP 520 E" },
4675
- { id: "van-455", label: "E-van FP 455 R" },
4676
- { id: "van-290", label: "Van FP 290 E" },
4677
- ];
5796
+ const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
4678
5797
 
4679
- const CONSIGNMENTS: readonly Task[] = [
4680
- { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
4681
- { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
4682
- { id: "c-2045", name: "C-2045 Tamsin's Bakery", color: "light-dark(#be185d, #f06aa6)" },
4683
- { id: "c-2046", name: "C-2046 Pellham Hardware", color: "light-dark(#4d7c0f, #8fc43e)" },
5798
+ const PROJECTS: readonly Task[] = [
5799
+ { id: "shop", name: "Online shop relaunch", color: "light-dark(#2563eb, #6b9bff)" },
5800
+ { id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
4684
5801
  ];
4685
5802
 
4686
- const LEGS: readonly Subtask[] = [
4687
- { id: "c-2041-1", task: "c-2041", lane: "truck-118", from: at(6), to: at(7), leadIn: min(15), leadOut: min(10) },
4688
- { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
4689
- { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(10), to: at(11, 30), leadIn: min(15) },
4690
- { id: "c-2046-2", task: "c-2046", lane: "van-402", from: at(13), to: at(14, 30), leadIn: min(20), leadOut: min(10) },
4691
- { id: "c-2043-3", task: "c-2043", lane: "van-455", from: at(12), to: at(14), leadIn: min(20), leadOut: min(20) },
4692
- { id: "c-2045-3", task: "c-2045", lane: "van-290", from: at(15, 45), to: at(16, 30) },
5803
+ const WORK: readonly Subtask[] = [
5804
+ { id: "design", task: "shop", lane: "noah", from: day(16), to: day(17, 17), name: "Search results layout" },
5805
+ { id: "build", task: "shop", lane: "chloe", from: day(17), to: day(19, 17), name: "Search results page" },
5806
+ { id: "address", task: "portal", lane: "chloe", from: day(18), to: day(19, 17), name: "Change of address form" },
5807
+ { id: "profile", task: "portal", lane: "arjun", from: day(17), to: day(19, 17), name: "Profile page" },
4693
5808
  ];
4694
5809
 
4695
- const TRANSFERS: readonly Dependency[] = [
4696
- { id: "t-2043-1", from: "c-2043-1", to: "c-2043-2", lag: min(45) },
4697
- { id: "t-2043-2", from: "c-2043-2", to: "c-2043-3", lag: min(25) },
4698
- ];
5810
+ /* The build may start only a morning after the design is done. */
5811
+ const HANDOVERS: readonly Dependency[] = [{ id: "design-to-build", from: "design", to: "build", lag: 4 * 60 * 60_000 }];
4699
5812
 
4700
- export default function MoveToAnotherLane() {
4701
- const [legs, setLegs] = useState(LEGS);
4702
- const [last, setLast] = useState<Intent | null>(null);
5813
+ const LEAVE: readonly BlockedTime[] = [{ id: "arjun-holiday", lane: "arjun", from: day(18, 0), to: day(21, 0), label: "Holiday" }];
5814
+
5815
+ const when = (instant: number) => new Date(instant).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit" });
5816
+ const hours = (ms: number) => Math.round(ms / 3_600_000);
5817
+
5818
+ export default function ListTheFindings() {
5819
+ const found = findings(WORK, HANDOVERS, LEAVE);
5820
+
5821
+ return (
5822
+ <Stack gap={3}>
5823
+ <Schedule ariaLabel="Three people's week with its findings" initialDomain={[day(16, 0), day(21, 0)]} height={184}>
5824
+ <Lane id="noah" label="Noah Fischer" />
5825
+ <Lane id="chloe" label="Chloe Durand" />
5826
+ <Lane id="arjun" label="Arjun Mehta" />
5827
+ <BlockedTimes data={LEAVE} />
5828
+ <Dependencies data={HANDOVERS} />
5829
+ <Subtasks data={WORK} tasks={PROJECTS} />
5830
+ </Schedule>
5831
+ <ul style={{ margin: 0, paddingLeft: 18 }}>
5832
+ {found.overlaps.map((o) => (
5833
+ <Text as="li" size="sm" key={`${o.first}/${o.second}`}>
5834
+ Double-booked: {o.lane} has {o.first} and {o.second} from {when(o.from)} to {when(o.to)}
5835
+ </Text>
5836
+ ))}
5837
+ {found.violatedDependencies.map((v) => (
5838
+ <Text as="li" size="sm" key={v.dependency}>
5839
+ Too early: {v.dependency} leaves {when(v.departure)} for a start at {when(v.arrival)}, {hours(v.shortBy)} hours short
5840
+ </Text>
5841
+ ))}
5842
+ {found.inBlockedTime.map((b) => (
5843
+ <Text as="li" size="sm" key={`${b.subtask}/${b.blocked}`}>
5844
+ In leave: {b.subtask} covers {b.blocked} from {when(b.from)} to {when(b.to)}
5845
+ </Text>
5846
+ ))}
5847
+ </ul>
5848
+ </Stack>
5849
+ );
5850
+ }
5851
+ ```
5852
+
5853
+ ##### Say when there is nothing to fix
5854
+
5855
+ When all three lists [`findings`](#/api/findings) returns are empty, say so plainly instead of showing an empty list.
5856
+
5857
+ ```tsx
5858
+ import { EmptyState, Stack } from "@umriss-ui/core";
5859
+ import { Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
5860
+ import type { Subtask, Task } from "@umriss-ui/schedule";
5861
+
5862
+ const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
5863
+
5864
+ const PROJECTS: readonly Task[] = [{ id: "booking", name: "Booking app", color: "light-dark(#0d9488, #3cc7b8)" }];
5865
+
5866
+ const WORK: readonly Subtask[] = [
5867
+ { id: "w-111", task: "booking", lane: "hana", from: day(16), to: day(18, 17), name: "Push notifications" },
5868
+ { id: "w-113", task: "booking", lane: "kofi", from: day(16), to: day(18, 17), name: "Calendar sync" },
5869
+ ];
5870
+
5871
+ export default function NothingToFix() {
5872
+ const found = findings(WORK, []);
5873
+ const count = found.overlaps.length + found.violatedDependencies.length + found.inBlockedTime.length;
5874
+
5875
+ return (
5876
+ <Stack gap={3}>
5877
+ <Schedule ariaLabel="Two developers' week, without findings" initialDomain={[day(16, 0), day(21, 0)]} height={140}>
5878
+ <Lane id="hana" label="Hana Sato" />
5879
+ <Lane id="kofi" label="Kofi Mensah" />
5880
+ <Subtasks data={WORK} tasks={PROJECTS} />
5881
+ </Schedule>
5882
+ {count === 0 && <EmptyState title="Nothing to fix" description="Nobody is double-booked, every hand-over fits, and no work falls into leave." />}
5883
+ </Stack>
5884
+ );
5885
+ }
5886
+ ```
5887
+
5888
+ ##### Count the findings per kind
5889
+
5890
+ [`overlaps`](#/api/overlaps) and [`violatedDependencies`](#/api/violatedDependencies) are the halves of [`findings`](#/api/findings) on their own - here as counts above the day's legs.
5891
+
5892
+ ```tsx
5893
+ import { Badge, Stack } from "@umriss-ui/core";
5894
+ import { Dependencies, Lane, Schedule, Subtasks, overlaps, violatedDependencies } from "@umriss-ui/schedule";
5895
+ import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
5896
+
5897
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
5898
+ const min = (n: number) => n * 60_000;
5899
+
5900
+ const VEHICLES = [
5901
+ { id: "van-402", label: "Van FP 402 R" },
5902
+ { id: "truck-520", label: "Truck FP 520 E" },
5903
+ { id: "van-455", label: "E-van FP 455 R" },
5904
+ ];
5905
+
5906
+ const CONSIGNMENTS: readonly Task[] = [
5907
+ { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
5908
+ { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
5909
+ ];
5910
+
5911
+ const LEGS: readonly Subtask[] = [
5912
+ { id: "c-2041-2", task: "c-2041", lane: "van-402", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
5913
+ { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
5914
+ { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(10), to: at(11, 30), leadIn: min(15) },
5915
+ { id: "c-2043-3", task: "c-2043", lane: "van-455", from: at(12), to: at(14), leadIn: min(20), leadOut: min(20) },
5916
+ ];
5917
+
5918
+ const TRANSFERS: readonly Dependency[] = [
5919
+ { id: "t-2043-1", from: "c-2043-1", to: "c-2043-2", lag: min(45) },
5920
+ { id: "t-2043-2", from: "c-2043-2", to: "c-2043-3", lag: min(25) },
5921
+ ];
5922
+
5923
+ export default function CountPerKind() {
5924
+ const doubleBooked = overlaps(LEGS).length;
5925
+ const late = violatedDependencies(LEGS, TRANSFERS).length;
5926
+
5927
+ return (
5928
+ <Stack gap={3}>
5929
+ <Stack direction="row" gap={2}>
5930
+ <Badge tone={doubleBooked > 0 ? "warning" : "success"}>{doubleBooked} double-booked</Badge>
5931
+ <Badge tone={late > 0 ? "warning" : "success"}>{late} transfers too short</Badge>
5932
+ </Stack>
5933
+ <Schedule ariaLabel="The day's legs with their findings counted" initialDomain={[at(5, 30), at(18)]} height={196}>
5934
+ {VEHICLES.map((vehicle) => (
5935
+ <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5936
+ ))}
5937
+ <Dependencies data={TRANSFERS} />
5938
+ <Subtasks data={LEGS} tasks={CONSIGNMENTS} />
5939
+ </Schedule>
5940
+ </Stack>
5941
+ );
5942
+ }
5943
+ ```
5944
+
5945
+ #### Keyboard
5946
+
5947
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
5948
+
5949
+ #### API
5950
+
5951
+ ##### `Overlap`
5952
+
5953
+ | Prop | Type | Default | Description |
5954
+ |---|---|---|---|
5955
+ | `lane` *required* | `string` | — | The lane both sit on.<br>Shown in: [List the findings](#/findings/list-the-findings), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) |
5956
+ | `first` *required* | `string` | — | The subtask that starts earlier (lead-in included); on a tie, the one that stands first in the data.<br>Shown in: [List the findings](#/findings/list-the-findings), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) |
5957
+ | `second` *required* | `string` | — | The other one.<br>Shown in: [List the findings](#/findings/list-the-findings), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) |
5958
+ | `from` *required* | `number` | — | Where the covering begins.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5959
+ | `to` *required* | `number` | — | Where it ends.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5960
+
5961
+ ##### `ViolatedDependency`
5962
+
5963
+ | Prop | Type | Default | Description |
5964
+ |---|---|---|---|
5965
+ | `dependency` *required* | `string` | — | The id of the dependency.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5966
+ | `departure` *required* | `number` | — | When it leaves, by its anchor.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5967
+ | `arrival` *required* | `number` | — | When it has to have arrived, by its anchor.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5968
+ | `shortBy` *required* | `number` | — | How much time is missing: departure plus lag, minus arrival.<br>Shown in: [List the findings](#/findings/list-the-findings) |
5969
+
5970
+ ##### `InBlockedTime`
5971
+
5972
+ | Prop | Type | Default | Description |
5973
+ |---|---|---|---|
5974
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [List the findings](#/findings/list-the-findings), [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Check a sprint against the leave calendar (Blocked time)](#/blocked-time/team-leave) and 2 more |
5975
+ | `blocked` *required* | `string` | — | The id of the blocked time it covers.<br>Shown in: [List the findings](#/findings/list-the-findings), [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Plan the sprint around leave (Scenarios)](#/scenarios/plan-the-sprint-around-leave) |
5976
+ | `lane` *required* | `string` | — | The lane both are on.<br>Shown in: [Hand over the on-call week (Scenarios)](#/scenarios/hand-over-the-on-call-week), [Plan the sprint around leave (Scenarios)](#/scenarios/plan-the-sprint-around-leave) |
5977
+ | `from` *required* | `number` | — | Where the covering begins.<br>Shown in: [List the findings](#/findings/list-the-findings), [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Hand over the on-call week (Scenarios)](#/scenarios/hand-over-the-on-call-week) |
5978
+ | `to` *required* | `number` | — | Where it ends.<br>Shown in: [List the findings](#/findings/list-the-findings), [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Hand over the on-call week (Scenarios)](#/scenarios/hand-over-the-on-call-week) |
5979
+
5980
+ #### Known limits
5981
+
5982
+ - Overlapping work is marked, not packed into sub-lanes; stacked overlap is not built yet.
5983
+
5984
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
5985
+
5986
+ ## Editing
5987
+
5988
+ Direct manipulation that changes nothing by itself: the schedule reports, the application decides.
5989
+
5990
+ ### Move and lane
5991
+
5992
+ Drag a bar to a new time or onto another lane (also called drag-and-drop rescheduling). A ghost shows where it would land and what that would cause; the drop reports the change for your application to apply.
5993
+
5994
+ ```ts
5995
+ import { Schedule, applyIntent } from "@umriss-ui/schedule";
5996
+ ```
5997
+
5998
+ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/
5999
+
6000
+ Each name in `intents` enables one gesture. Without `intents` the schedule is read-only, and pressing a bar pans the plot.
6001
+
6002
+ The schedule never changes your data ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)): `onIntent` reports a move and a lane change as values, and [`applyIntent`](#/api/applyIntent) is the arithmetic to apply them. A drop across lanes and time reports both, and your handler may apply them, refuse them, or apply one.
6003
+
6004
+ Held at the edge of the plot, a drag pans it along; Escape cancels the drag.
6005
+
6006
+ #### Examples
6007
+
6008
+ ##### Move work in time
6009
+
6010
+ List `move` in `intents` and apply what `onIntent` reports with [`applyIntent`](#/api/applyIntent); the plan changes only in your state.
6011
+
6012
+ ```tsx
6013
+ import { useState } from "react";
6014
+ import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6015
+ import type { Subtask, Task } from "@umriss-ui/schedule";
6016
+
6017
+ const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
6018
+
6019
+ const PROJECTS: readonly Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" }];
6020
+
6021
+ const START: readonly Subtask[] = [
6022
+ { id: "w-102", task: "portal", lane: "arjun", from: day(17), to: day(18, 17), name: "Profile page" },
6023
+ { id: "w-105", task: "portal", lane: "chloe", from: day(18), to: day(19, 17), name: "Change of address form" },
6024
+ ];
6025
+
6026
+ export default function MoveInTime() {
6027
+ const [work, setWork] = useState(START);
6028
+ return (
6029
+ <Schedule
6030
+ ariaLabel="Two people's work this week"
6031
+ initialDomain={[day(16, 0), day(21, 0)]}
6032
+ height={140}
6033
+ intents={["move"]}
6034
+ onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
6035
+ >
6036
+ <Lane id="arjun" label="Arjun Mehta" />
6037
+ <Lane id="chloe" label="Chloe Durand" />
6038
+ <Subtasks data={work} tasks={PROJECTS} />
6039
+ </Schedule>
6040
+ );
6041
+ }
6042
+ ```
6043
+
6044
+ ##### Move work onto another lane
6045
+
6046
+ Add `lane` to `intents`: a drop reports a move and a lane change separately, and the ghost shows the findings first.
6047
+
6048
+ ```tsx
6049
+ import { useState } from "react";
6050
+ import { Stack, Text } from "@umriss-ui/core";
6051
+ import { Dependencies, Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6052
+ import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
6053
+
6054
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
6055
+ const min = (n: number) => n * 60_000;
6056
+
6057
+ const VEHICLES = [
6058
+ { id: "truck-118", label: "Truck FP 118 R" },
6059
+ { id: "van-402", label: "Van FP 402 R" },
6060
+ { id: "truck-520", label: "Truck FP 520 E" },
6061
+ { id: "van-455", label: "E-van FP 455 R" },
6062
+ { id: "van-290", label: "Van FP 290 E" },
6063
+ ];
6064
+
6065
+ const CONSIGNMENTS: readonly Task[] = [
6066
+ { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
6067
+ { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
6068
+ { id: "c-2045", name: "C-2045 Tamsin's Bakery", color: "light-dark(#be185d, #f06aa6)" },
6069
+ { id: "c-2046", name: "C-2046 Pellham Hardware", color: "light-dark(#4d7c0f, #8fc43e)" },
6070
+ ];
6071
+
6072
+ const LEGS: readonly Subtask[] = [
6073
+ { id: "c-2041-1", task: "c-2041", lane: "truck-118", from: at(6), to: at(7), leadIn: min(15), leadOut: min(10) },
6074
+ { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
6075
+ { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(10), to: at(11, 30), leadIn: min(15) },
6076
+ { id: "c-2046-2", task: "c-2046", lane: "van-402", from: at(13), to: at(14, 30), leadIn: min(20), leadOut: min(10) },
6077
+ { id: "c-2043-3", task: "c-2043", lane: "van-455", from: at(12), to: at(14), leadIn: min(20), leadOut: min(20) },
6078
+ { id: "c-2045-3", task: "c-2045", lane: "van-290", from: at(15, 45), to: at(16, 30) },
6079
+ ];
6080
+
6081
+ const TRANSFERS: readonly Dependency[] = [
6082
+ { id: "t-2043-1", from: "c-2043-1", to: "c-2043-2", lag: min(45) },
6083
+ { id: "t-2043-2", from: "c-2043-2", to: "c-2043-3", lag: min(25) },
6084
+ ];
6085
+
6086
+ export default function MoveToAnotherLane() {
6087
+ const [legs, setLegs] = useState(LEGS);
6088
+ const [last, setLast] = useState<Intent | null>(null);
4703
6089
 
4704
6090
  return (
4705
6091
  <Stack gap={3}>
@@ -4729,7 +6115,7 @@ export default function MoveToAnotherLane() {
4729
6115
 
4730
6116
  ##### Refuse a move that double-books someone
4731
6117
 
4732
- Your `onIntent` decides: here it applies a drop only when `overlaps` finds no new double booking, and says why otherwise.
6118
+ Your `onIntent` decides: here it applies a drop only when [`overlaps`](#/api/overlaps) finds no new double booking, and says why otherwise.
4733
6119
 
4734
6120
  ```tsx
4735
6121
  import { useRef, useState } from "react";
@@ -4876,12 +6262,95 @@ export default function RefuseADoubleBooking() {
4876
6262
  | `Alt+← Alt+→` | Proposes moving the active subtask one raster step earlier or later, stepping over blocked time. |
4877
6263
  | `Escape` | Cancels a drag in flight: the ghost goes and nothing is reported. |
4878
6264
 
4879
- #### Known limits
6265
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4880
6266
 
4881
- - No undo: your application owns the data, and with it the history (ADR-0032).
4882
- - The keys move work in time only; a lane change needs the pointer.
6267
+ #### Props on this page
4883
6268
 
4884
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
6269
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
6270
+
6271
+ | Prop | Type | Default | Description |
6272
+ |---|---|---|---|
6273
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
6274
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6275
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
6276
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
6277
+
6278
+ ###### Events
6279
+
6280
+ | Prop | Type | Default | Description |
6281
+ |---|---|---|---|
6282
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
6283
+
6284
+ ###### Accessibility
6285
+
6286
+ | Prop | Type | Default | Description |
6287
+ |---|---|---|---|
6288
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
6289
+
6290
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
6291
+
6292
+ | Prop | Type | Default | Description |
6293
+ |---|---|---|---|
6294
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
6295
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
6296
+
6297
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
6298
+
6299
+ | Prop | Type | Default | Description |
6300
+ |---|---|---|---|
6301
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
6302
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
6303
+
6304
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
6305
+
6306
+ | Prop | Type | Default | Description |
6307
+ |---|---|---|---|
6308
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
6309
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
6310
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
6311
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
6312
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
6313
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
6314
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
6315
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
6316
+
6317
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6318
+
6319
+ | Prop | Type | Default | Description |
6320
+ |---|---|---|---|
6321
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
6322
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
6323
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6324
+
6325
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
6326
+
6327
+ | Prop | Type | Default | Description |
6328
+ |---|---|---|---|
6329
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
6330
+
6331
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
6332
+
6333
+ | Prop | Type | Default | Description |
6334
+ |---|---|---|---|
6335
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
6336
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
6337
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
6338
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
6339
+
6340
+ From `Overlap` — the full table stands on [Findings as data](#/findings/type-Overlap).
6341
+
6342
+ | Prop | Type | Default | Description |
6343
+ |---|---|---|---|
6344
+ | [`lane`](#/findings/Overlap-lane) *required* | `string` | — | The lane both sit on. |
6345
+ | [`first`](#/findings/Overlap-first) *required* | `string` | — | The subtask that starts earlier (lead-in included); on a tie, the one that stands first in the data. |
6346
+ | [`second`](#/findings/Overlap-second) *required* | `string` | — | The other one. |
6347
+
6348
+ #### Known limits
6349
+
6350
+ - No undo: your application owns the data, and with it the history ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
6351
+ - The keys move work in time only; a lane change needs the pointer.
6352
+
6353
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
4885
6354
 
4886
6355
  ### Stretch, lead-in, lead-out
4887
6356
 
@@ -4980,11 +6449,71 @@ export default function ChangeLeadInAndLeadOut() {
4980
6449
  |---|---|
4981
6450
  | `Alt+Shift+← Alt+Shift+→` | Proposes ending the active subtask one raster step earlier or later. |
4982
6451
 
6452
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
6453
+
6454
+ #### Props on this page
6455
+
6456
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
6457
+
6458
+ | Prop | Type | Default | Description |
6459
+ |---|---|---|---|
6460
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
6461
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6462
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
6463
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
6464
+
6465
+ ###### Events
6466
+
6467
+ | Prop | Type | Default | Description |
6468
+ |---|---|---|---|
6469
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
6470
+
6471
+ ###### Accessibility
6472
+
6473
+ | Prop | Type | Default | Description |
6474
+ |---|---|---|---|
6475
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
6476
+
6477
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
6478
+
6479
+ | Prop | Type | Default | Description |
6480
+ |---|---|---|---|
6481
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
6482
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
6483
+
6484
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
6485
+
6486
+ | Prop | Type | Default | Description |
6487
+ |---|---|---|---|
6488
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
6489
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
6490
+
6491
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
6492
+
6493
+ | Prop | Type | Default | Description |
6494
+ |---|---|---|---|
6495
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
6496
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
6497
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
6498
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
6499
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
6500
+ | [`leadIn`](#/subtasks/Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
6501
+ | [`leadOut`](#/subtasks/Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |
6502
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
6503
+
6504
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6505
+
6506
+ | Prop | Type | Default | Description |
6507
+ |---|---|---|---|
6508
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
6509
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
6510
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6511
+
4983
6512
  #### Known limits
4984
6513
 
4985
6514
  - A main time never becomes shorter than one raster step, or a minute without a raster.
4986
6515
 
4987
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
6516
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
4988
6517
 
4989
6518
  ### Snapping
4990
6519
 
@@ -4998,7 +6527,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/snapping/
4998
6527
 
4999
6528
  Without `snap` a drag lands on the fine band's step, which follows the zoom. A number is a step in milliseconds; `{ step, offset }` counts from local midnight; `false` places to the minute.
5000
6529
 
5001
- Snapping shapes the ghost and so the intent; it never moves data that is not being dragged. `snapTime` is the same arithmetic for your own use.
6530
+ Snapping shapes the ghost and so the intent; it never moves data that is not being dragged. [`snapTime`](#/api/snapTime) is the same arithmetic for your own use.
5002
6531
 
5003
6532
  #### Examples
5004
6533
 
@@ -5142,6 +6671,67 @@ export default function NoSnapping() {
5142
6671
  }
5143
6672
  ```
5144
6673
 
6674
+ #### Keyboard
6675
+
6676
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
6677
+
6678
+ #### Props on this page
6679
+
6680
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
6681
+
6682
+ | Prop | Type | Default | Description |
6683
+ |---|---|---|---|
6684
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
6685
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6686
+ | [`snap`](#/schedule/ScheduleProps-snap) | `"ticks" \| number \| `[`SnapRaster`](#/schedule/type-SnapRaster)` \| false` | `"ticks"` | The raster a drag lands on: `"ticks"` - the fine band's current step -, a step in milliseconds from local midnight, a step with an offset (shifts at 06:00, 14:00 and 22:00 are eight hours offset by six), or `false` for none. It shapes the ghost and the intent, never the data. |
6687
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
6688
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
6689
+
6690
+ ###### Events
6691
+
6692
+ | Prop | Type | Default | Description |
6693
+ |---|---|---|---|
6694
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
6695
+
6696
+ ###### Accessibility
6697
+
6698
+ | Prop | Type | Default | Description |
6699
+ |---|---|---|---|
6700
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
6701
+
6702
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
6703
+
6704
+ | Prop | Type | Default | Description |
6705
+ |---|---|---|---|
6706
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
6707
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
6708
+
6709
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
6710
+
6711
+ | Prop | Type | Default | Description |
6712
+ |---|---|---|---|
6713
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
6714
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
6715
+
6716
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
6717
+
6718
+ | Prop | Type | Default | Description |
6719
+ |---|---|---|---|
6720
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
6721
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
6722
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
6723
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
6724
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
6725
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
6726
+
6727
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6728
+
6729
+ | Prop | Type | Default | Description |
6730
+ |---|---|---|---|
6731
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
6732
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
6733
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6734
+
5145
6735
  ### Placing from outside
5146
6736
 
5147
6737
  Drag work that is not on the plan yet - from a backlog or a list of unassigned jobs - onto a lane, with the browser's own drag and drop. The drop reports where it should go; your application creates it.
@@ -5154,13 +6744,13 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/placing/
5154
6744
 
5155
6745
  The browser hands over dragged data only on the drop, so declare what is being dragged in `placing` on your `dragstart` and clear it on `dragend`. The schedule then shows the ghost at the snapped time, with the findings the drop would create.
5156
6746
 
5157
- A `place` intent carries your item's key, the task, the lane and the times. The id of the new subtask is yours; `subtaskFromPlace` builds it from the intent.
6747
+ A `place` intent carries your item's key, the task, the lane and the times. The id of the new subtask is yours; [`subtaskFromPlace`](#/api/subtaskFromPlace) builds it from the intent.
5158
6748
 
5159
6749
  #### Examples
5160
6750
 
5161
6751
  ##### Drag work in from the backlog
5162
6752
 
5163
- Set `placing` on your list's `dragstart` and clear it on `dragend`; the drop reports a `place` intent that `subtaskFromPlace` turns into work.
6753
+ Set `placing` on your list's `dragstart` and clear it on `dragend`; the drop reports a `place` intent that [`subtaskFromPlace`](#/api/subtaskFromPlace) turns into work.
5164
6754
 
5165
6755
  ```tsx
5166
6756
  import { useState } from "react";
@@ -5265,6 +6855,82 @@ export default function DragFromTheBacklog() {
5265
6855
 
5266
6856
  - The work is already on the plan → Move and lane
5267
6857
 
6858
+ #### Keyboard
6859
+
6860
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
6861
+
6862
+ #### Props on this page
6863
+
6864
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
6865
+
6866
+ | Prop | Type | Default | Description |
6867
+ |---|---|---|---|
6868
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
6869
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6870
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
6871
+ | [`placing`](#/schedule/ScheduleProps-placing) | [`PlacingItem`](#/schedule/type-PlacingItem)` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted. |
6872
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
6873
+
6874
+ ###### Events
6875
+
6876
+ | Prop | Type | Default | Description |
6877
+ |---|---|---|---|
6878
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
6879
+
6880
+ ###### Accessibility
6881
+
6882
+ | Prop | Type | Default | Description |
6883
+ |---|---|---|---|
6884
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
6885
+
6886
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
6887
+
6888
+ | Prop | Type | Default | Description |
6889
+ |---|---|---|---|
6890
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
6891
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
6892
+
6893
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
6894
+
6895
+ | Prop | Type | Default | Description |
6896
+ |---|---|---|---|
6897
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
6898
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
6899
+
6900
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
6901
+
6902
+ | Prop | Type | Default | Description |
6903
+ |---|---|---|---|
6904
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
6905
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
6906
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
6907
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
6908
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
6909
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
6910
+
6911
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6912
+
6913
+ | Prop | Type | Default | Description |
6914
+ |---|---|---|---|
6915
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
6916
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
6917
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6918
+
6919
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
6920
+
6921
+ | Prop | Type | Default | Description |
6922
+ |---|---|---|---|
6923
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
6924
+
6925
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
6926
+
6927
+ | Prop | Type | Default | Description |
6928
+ |---|---|---|---|
6929
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
6930
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
6931
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
6932
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
6933
+
5268
6934
  ### Where a subtask may go
5269
6935
 
5270
6936
  Marks the lanes and times a piece of work may not go to as soon as a drag begins, and holds the ghost where it is allowed. Reach for it when rules of your domain - equipment, skills, leave - limit where work can stand.
@@ -5598,11 +7264,88 @@ export default function AroundLeave() {
5598
7264
 
5599
7265
  - The rule depends on the result, such as no double booking → Move and lane
5600
7266
 
7267
+ #### Keyboard
7268
+
7269
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
7270
+
7271
+ #### Props on this page
7272
+
7273
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
7274
+
7275
+ | Prop | Type | Default | Description |
7276
+ |---|---|---|---|
7277
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
7278
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
7279
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
7280
+ | [`canMoveTo`](#/schedule/ScheduleProps-canMoveTo) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`, lane: string) => boolean` | every lane is open | Whether a subtask may go to a lane. It narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`. |
7281
+ | [`placing`](#/schedule/ScheduleProps-placing) | [`PlacingItem`](#/schedule/type-PlacingItem)` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted. |
7282
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
7283
+
7284
+ ###### Events
7285
+
7286
+ | Prop | Type | Default | Description |
7287
+ |---|---|---|---|
7288
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
7289
+
7290
+ ###### Accessibility
7291
+
7292
+ | Prop | Type | Default | Description |
7293
+ |---|---|---|---|
7294
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
7295
+
7296
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
7297
+
7298
+ | Prop | Type | Default | Description |
7299
+ |---|---|---|---|
7300
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
7301
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
7302
+
7303
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
7304
+
7305
+ | Prop | Type | Default | Description |
7306
+ |---|---|---|---|
7307
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
7308
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
7309
+
7310
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
7311
+
7312
+ | Prop | Type | Default | Description |
7313
+ |---|---|---|---|
7314
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
7315
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
7316
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
7317
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
7318
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
7319
+
7320
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
7321
+
7322
+ | Prop | Type | Default | Description |
7323
+ |---|---|---|---|
7324
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
7325
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
7326
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
7327
+
7328
+ From `BlockedTimesProps` — the full table stands on [Blocked time](#/blocked-time/type-BlockedTimesProps).
7329
+
7330
+ | Prop | Type | Default | Description |
7331
+ |---|---|---|---|
7332
+ | [`data`](#/blocked-time/BlockedTimesProps-data) *required* | `readonly `[`BlockedTime`](#/blocked-time/type-BlockedTime)`[]` | — | The blocked time of the lanes - leave, maintenance, unavailability -, as the caller holds it: one list, each interval naming its lane. |
7333
+
7334
+ From `BlockedTime` — the full table stands on [Blocked time](#/blocked-time/type-BlockedTime).
7335
+
7336
+ | Prop | Type | Default | Description |
7337
+ |---|---|---|---|
7338
+ | [`id`](#/blocked-time/BlockedTime-id) *required* | `string` | — | The caller's identity of the interval - what a finding names. |
7339
+ | [`lane`](#/blocked-time/BlockedTime-lane) *required* | `string` | — | The id of the lane it blocks. |
7340
+ | [`from`](#/blocked-time/BlockedTime-from) *required* | `number` | — | Where it begins. |
7341
+ | [`to`](#/blocked-time/BlockedTime-to) *required* | `number` | — | Where it ends. |
7342
+ | [`label`](#/blocked-time/BlockedTime-label) | `string` | — | Why the lane is blocked - "Leave", "Maintenance" -, where the schedule names it. |
7343
+
5601
7344
  #### Known limits
5602
7345
 
5603
7346
  - A rule that changes while a drag runs is noticed at the drop, not before.
5604
7347
 
5605
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
7348
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
5606
7349
 
5607
7350
  ### Ripple
5608
7351
 
@@ -5616,13 +7359,13 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/ripple/
5616
7359
 
5617
7360
  `ripple(subtasks, dependencies, intent)` returns move intents down the whole chain, in the order they were pushed. A successor keeps its length and is never pulled earlier: room that opens up is the planner's.
5618
7361
 
5619
- `shiftTask` moves every subtask of one task by the same amount, so all its dependencies keep fitting.
7362
+ [`shiftTask`](#/api/shiftTask) moves every subtask of one task by the same amount, so all its dependencies keep fitting.
5620
7363
 
5621
7364
  #### Examples
5622
7365
 
5623
7366
  ##### Push the later stops of a tour
5624
7367
 
5625
- `ripple` returns the moves that push every stop whose drive no longer fits behind a late one; you decide whether to apply them.
7368
+ [`ripple`](#/api/ripple) returns the moves that push every stop whose drive no longer fits behind a late one; you decide whether to apply them.
5626
7369
 
5627
7370
  ```tsx
5628
7371
  import { useState } from "react";
@@ -5690,7 +7433,7 @@ export default function PushTheLaterStops() {
5690
7433
 
5691
7434
  ##### Move a whole tour
5692
7435
 
5693
- `shiftTask` returns one move per stop, all by the same amount, so every drive that fitted still fits.
7436
+ [`shiftTask`](#/api/shiftTask) returns one move per stop, all by the same amount, so every drive that fitted still fits.
5694
7437
 
5695
7438
  ```tsx
5696
7439
  import { useState } from "react";
@@ -5749,7 +7492,7 @@ export default function MoveAWholeTour() {
5749
7492
 
5750
7493
  ##### Ripple on every drag
5751
7494
 
5752
- Run `ripple` inside `onIntent` and apply its moves with the drop: drag the design later and build and test follow.
7495
+ Run [`ripple`](#/api/ripple) inside `onIntent` and apply its moves with the drop: drag the design later and build and test follow.
5753
7496
 
5754
7497
  ```tsx
5755
7498
  import { useState } from "react";
@@ -5799,356 +7542,603 @@ export default function RippleOnEveryDrag() {
5799
7542
  }
5800
7543
  ```
5801
7544
 
7545
+ #### Keyboard
7546
+
7547
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
7548
+
7549
+ #### Props on this page
7550
+
7551
+ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/type-ScheduleProps).
7552
+
7553
+ | Prop | Type | Default | Description |
7554
+ |---|---|---|---|
7555
+ | [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
7556
+ | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
7557
+ | [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
7558
+ | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
7559
+
7560
+ ###### Events
7561
+
7562
+ | Prop | Type | Default | Description |
7563
+ |---|---|---|---|
7564
+ | [`onIntent`](#/schedule/ScheduleProps-onIntent) | `(intent: `[`Intent`](#/schedule/type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
7565
+
7566
+ ###### Accessibility
7567
+
7568
+ | Prop | Type | Default | Description |
7569
+ |---|---|---|---|
7570
+ | [`ariaLabel`](#/schedule/ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
7571
+
7572
+ From `LaneProps` — the full table stands on [Lanes](#/lane/type-LaneProps).
7573
+
7574
+ | Prop | Type | Default | Description |
7575
+ |---|---|---|---|
7576
+ | [`id`](#/lane/LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
7577
+ | [`label`](#/lane/LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
7578
+
7579
+ From `SubtasksProps` — the full table stands on [Subtasks](#/subtasks/type-SubtasksProps).
7580
+
7581
+ | Prop | Type | Default | Description |
7582
+ |---|---|---|---|
7583
+ | [`data`](#/subtasks/SubtasksProps-data) *required* | `readonly `[`Subtask`](#/subtasks/type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
7584
+ | [`tasks`](#/subtasks/SubtasksProps-tasks) *required* | `readonly `[`Task`](#/subtasks/type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
7585
+
7586
+ From `Subtask` — the full table stands on [Subtasks](#/subtasks/type-Subtask).
7587
+
7588
+ | Prop | Type | Default | Description |
7589
+ |---|---|---|---|
7590
+ | [`id`](#/subtasks/Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
7591
+ | [`task`](#/subtasks/Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
7592
+ | [`lane`](#/subtasks/Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
7593
+ | [`from`](#/subtasks/Subtask-from) *required* | `number` | — | Start of the main time. |
7594
+ | [`to`](#/subtasks/Subtask-to) *required* | `number` | — | End of the main time. |
7595
+ | [`name`](#/subtasks/Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |
7596
+
7597
+ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
7598
+
7599
+ | Prop | Type | Default | Description |
7600
+ |---|---|---|---|
7601
+ | [`id`](#/subtasks/Task-id) *required* | `string` | — | The caller's identity of the task. |
7602
+ | [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
7603
+ | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
7604
+
7605
+ From `DependenciesProps` — the full table stands on [Dependencies](#/dependencies/type-DependenciesProps).
7606
+
7607
+ | Prop | Type | Default | Description |
7608
+ |---|---|---|---|
7609
+ | [`data`](#/dependencies/DependenciesProps-data) *required* | `readonly `[`Dependency`](#/dependencies/type-Dependency)`[]` | — | The dependencies, as the caller holds them. |
7610
+
7611
+ From `Dependency` — the full table stands on [Dependencies](#/dependencies/type-Dependency).
7612
+
7613
+ | Prop | Type | Default | Description |
7614
+ |---|---|---|---|
7615
+ | [`id`](#/dependencies/Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
7616
+ | [`from`](#/dependencies/Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
7617
+ | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
7618
+ | [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
7619
+
5802
7620
  #### Known limits
5803
7621
 
5804
- - No dependency types, slack or critical path: the schedule is no project-planning engine (ADR-0032).
7622
+ - No dependency types, slack or critical path: the schedule is no project-planning engine ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
5805
7623
  - A dependency joins subtasks of one task, so work of other tasks is never pushed.
5806
7624
 
5807
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
7625
+ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
5808
7626
 
5809
- ## Findings
7627
+ ## API index
5810
7628
 
5811
- What a planner opens a schedule for, as data beside the picture.
7629
+ Every name the package exports, in one place.
5812
7630
 
5813
- ### Findings as data
7631
+ ### API index
5814
7632
 
5815
- Lists what is wrong with a plan - double bookings on a lane, dependencies that do not fit, work in blocked time - from the same data the schedule draws. Reach for it to list, count or filter the problems beside the picture.
7633
+ Everything `@umriss-ui/schedule` exports, with its signature. A component's props stand on its own page.
5816
7634
 
5817
- ```ts
5818
- import { findings, overlaps, violatedDependencies, inBlockedTime } from "@umriss-ui/schedule";
5819
- ```
7635
+ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/api/
5820
7636
 
5821
- Demo page: https://romanhaendler.github.io/umriss-ui/schedule/findings/
7637
+ #### Components
5822
7638
 
5823
- `findings(subtasks, dependencies, blocked)` returns all three kinds; `overlaps`, `violatedDependencies` and `inBlockedTime` return one each. They are pure functions: run them wherever the data changes, with or without a schedule on screen.
7639
+ ##### `BlockedTimes`
5824
7640
 
5825
- Lead-in and lead-out count as occupied time; touching at a shared edge is no overlap.
7641
+ [Blocked time](#/blocked-time) – A layer of blocked time. Wherever it is declared, it is drawn behind every
7642
+ subtask and dependency: it is a property of the lane, not something on it.
5826
7643
 
5827
- #### Examples
7644
+ Used on [Scenarios](#/), [Blocked time](#/blocked-time), [Tooltip](#/tooltip), [Findings as data](#/findings) and [Where a subtask may go](#/where-it-may-go).
5828
7645
 
5829
- ##### List the findings
7646
+ ##### `Dependencies`
5830
7647
 
5831
- `findings` returns what the schedule marks - overlaps, violated dependencies, work in blocked time - as data for a list beside it.
7648
+ [Dependencies](#/dependencies) – Lines from one subtask's end to the next one's start within a task, with the lag that must pass between them (also called links or predecessors). A lag that does not fit is marked, not repaired.
5832
7649
 
5833
- ```tsx
5834
- import { Stack, Text } from "@umriss-ui/core";
5835
- import { BlockedTimes, Dependencies, Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
5836
- import type { BlockedTime, Dependency, Subtask, Task } from "@umriss-ui/schedule";
7650
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lane groups](#/lane-groups), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Placing from outside](#/placing) and [Ripple](#/ripple).
5837
7651
 
5838
- const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
7652
+ ##### `Lane`
5839
7653
 
5840
- const PROJECTS: readonly Task[] = [
5841
- { id: "shop", name: "Online shop relaunch", color: "light-dark(#2563eb, #6b9bff)" },
5842
- { id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
5843
- ];
7654
+ [Lanes](#/lane) – One lane: a person, a vehicle, a room. The lanes run top to bottom in the order
7655
+ they are declared - whichever group they are declared in.
5844
7656
 
5845
- const WORK: readonly Subtask[] = [
5846
- { id: "design", task: "shop", lane: "noah", from: day(16), to: day(17, 17), name: "Search results layout" },
5847
- { id: "build", task: "shop", lane: "chloe", from: day(17), to: day(19, 17), name: "Search results page" },
5848
- { id: "address", task: "portal", lane: "chloe", from: day(18), to: day(19, 17), name: "Change of address form" },
5849
- { id: "profile", task: "portal", lane: "arjun", from: day(17), to: day(19, 17), name: "Profile page" },
5850
- ];
7657
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
5851
7658
 
5852
- /* The build may start only a morning after the design is done. */
5853
- const HANDOVERS: readonly Dependency[] = [{ id: "design-to-build", from: "design", to: "build", lag: 4 * 60 * 60_000 }];
7659
+ ##### `LaneGroup`
5854
7660
 
5855
- const LEAVE: readonly BlockedTime[] = [{ id: "arjun-holiday", lane: "arjun", from: day(18, 0), to: day(21, 0), label: "Holiday" }];
7661
+ [Lane groups](#/lane-groups) – A group of lanes: structure over them, and never a lane itself.
5856
7662
 
5857
- const when = (instant: number) => new Date(instant).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit" });
5858
- const hours = (ms: number) => Math.round(ms / 3_600_000);
7663
+ It gives its id to its children through context, so a lane never names its
7664
+ group and a group reads in JSX as it reads in the organisation. Folded, it becomes
7665
+ one row showing a **Miniature** of everything in it; open, it shows a slim
7666
+ head above its lanes. Which groups are folded is `collapsedGroups` on
7667
+ `Schedule` - a view state, never an **Intent**.
5859
7668
 
5860
- export default function ListTheFindings() {
5861
- const found = findings(WORK, HANDOVERS, LEAVE);
7669
+ Used on [Scenarios](#/) and [Lane groups](#/lane-groups).
5862
7670
 
5863
- return (
5864
- <Stack gap={3}>
5865
- <Schedule ariaLabel="Three people's week with its findings" initialDomain={[day(16, 0), day(21, 0)]} height={184}>
5866
- <Lane id="noah" label="Noah Fischer" />
5867
- <Lane id="chloe" label="Chloe Durand" />
5868
- <Lane id="arjun" label="Arjun Mehta" />
5869
- <BlockedTimes data={LEAVE} />
5870
- <Dependencies data={HANDOVERS} />
5871
- <Subtasks data={WORK} tasks={PROJECTS} />
5872
- </Schedule>
5873
- <ul style={{ margin: 0, paddingLeft: 18 }}>
5874
- {found.overlaps.map((o) => (
5875
- <Text as="li" size="sm" key={`${o.first}/${o.second}`}>
5876
- Double-booked: {o.lane} has {o.first} and {o.second} from {when(o.from)} to {when(o.to)}
5877
- </Text>
5878
- ))}
5879
- {found.violatedDependencies.map((v) => (
5880
- <Text as="li" size="sm" key={v.dependency}>
5881
- Too early: {v.dependency} is {hours(v.shortBy)} hours short
5882
- </Text>
5883
- ))}
5884
- {found.inBlockedTime.map((b) => (
5885
- <Text as="li" size="sm" key={`${b.subtask}/${b.blocked}`}>
5886
- In leave: {b.subtask} covers {b.blocked} from {when(b.from)} to {when(b.to)}
5887
- </Text>
5888
- ))}
5889
- </ul>
5890
- </Stack>
5891
- );
5892
- }
7671
+ ##### `Schedule`
7672
+
7673
+ [First schedule](#/schedule) – A plan of subtasks on lanes over time - people, vehicles, rooms - with
7674
+ dependencies between them. The contents are declared as children (`Lane`,
7675
+ `LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes`); the planner pans,
7676
+ zooms and drags, and every drag ends as an **Intent** through `onIntent`.
7677
+ The data changes only where the caller applies it; without `intents` the
7678
+ schedule is read-only. A ref gives the `ScheduleHandle`.
7679
+
7680
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
7681
+
7682
+ ##### `Subtasks`
7683
+
7684
+ [Subtasks](#/subtasks) – The bars: a main time with an optional lead-in before it and lead-out after it, in the colour of the task they belong to (also called bookings or assignments).
7685
+
7686
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
7687
+
7688
+ #### Functions
7689
+
7690
+ ##### `applyIntent`
7691
+
7692
+ The subtask as the intent asks for it. An intent for another subtask leaves
7693
+ it as it is.
7694
+
7695
+ ```ts
7696
+ function applyIntent<S extends Subtask>(subtask: S, intent: Intent): S;
5893
7697
  ```
5894
7698
 
5895
- ##### Say when there is nothing to fix
7699
+ Used on [Scenarios](#/), [Lane groups](#/lane-groups), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Interactions](#/interactions), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
5896
7700
 
5897
- When all three lists `findings` returns are empty, say so plainly instead of showing an empty list.
7701
+ ##### `arrival`
5898
7702
 
5899
- ```tsx
5900
- import { EmptyState, Stack } from "@umriss-ui/core";
5901
- import { Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
5902
- import type { Subtask, Task } from "@umriss-ui/schedule";
7703
+ When a dependency has to have arrived at its subtask, by its anchor.
5903
7704
 
5904
- const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
7705
+ ```ts
7706
+ function arrival(dependency: Dependency, to: Subtask): number;
7707
+ ```
5905
7708
 
5906
- const PROJECTS: readonly Task[] = [{ id: "booking", name: "Booking app", color: "light-dark(#0d9488, #3cc7b8)" }];
7709
+ Used on [Scenarios](#/), [Now line](#/now-line) and [Findings as data](#/findings).
5907
7710
 
5908
- const WORK: readonly Subtask[] = [
5909
- { id: "w-111", task: "booking", lane: "hana", from: day(16), to: day(18, 17), name: "Push notifications" },
5910
- { id: "w-113", task: "booking", lane: "kofi", from: day(16), to: day(18, 17), name: "Calendar sync" },
5911
- ];
7711
+ ##### `departure`
5912
7712
 
5913
- export default function NothingToFix() {
5914
- const found = findings(WORK, []);
5915
- const count = found.overlaps.length + found.violatedDependencies.length + found.inBlockedTime.length;
7713
+ When a dependency leaves its subtask, by its anchor.
5916
7714
 
5917
- return (
5918
- <Stack gap={3}>
5919
- <Schedule ariaLabel="Two developers' week, without findings" initialDomain={[day(16, 0), day(21, 0)]} height={140}>
5920
- <Lane id="hana" label="Hana Sato" />
5921
- <Lane id="kofi" label="Kofi Mensah" />
5922
- <Subtasks data={WORK} tasks={PROJECTS} />
5923
- </Schedule>
5924
- {count === 0 && <EmptyState title="Nothing to fix" description="Nobody is double-booked, every hand-over fits, and no work falls into leave." />}
5925
- </Stack>
5926
- );
5927
- }
7715
+ ```ts
7716
+ function departure(dependency: Dependency, from: Subtask): number;
5928
7717
  ```
5929
7718
 
5930
- ##### Count the findings per kind
7719
+ Used on [Findings as data](#/findings).
5931
7720
 
5932
- `overlaps` and `violatedDependencies` are the halves of `findings` on their own - here as counts above the day's legs.
7721
+ ##### `findings`
5933
7722
 
5934
- ```tsx
5935
- import { Badge, Stack } from "@umriss-ui/core";
5936
- import { Dependencies, Lane, Schedule, Subtasks, overlaps, violatedDependencies } from "@umriss-ui/schedule";
5937
- import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
7723
+ Every finding at once. Without blocked time there is none in it.
5938
7724
 
5939
- const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
5940
- const min = (n: number) => n * 60_000;
7725
+ ```ts
7726
+ function findings(subtasks: readonly Subtask[], dependencies: readonly Dependency[], blocked?: readonly BlockedTime[]): Findings;
7727
+ ```
5941
7728
 
5942
- const VEHICLES = [
5943
- { id: "van-402", label: "Van FP 402 R" },
5944
- { id: "truck-520", label: "Truck FP 520 E" },
5945
- { id: "van-455", label: "E-van FP 455 R" },
5946
- ];
7729
+ Used on [Scenarios](#/), [First schedule](#/schedule), [Lane groups](#/lane-groups), [Blocked time](#/blocked-time), [Tooltip](#/tooltip) and [Findings as data](#/findings).
5947
7730
 
5948
- const CONSIGNMENTS: readonly Task[] = [
5949
- { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
5950
- { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
5951
- ];
7731
+ ##### `inBlockedTime`
5952
7732
 
5953
- const LEGS: readonly Subtask[] = [
5954
- { id: "c-2041-2", task: "c-2041", lane: "van-402", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
5955
- { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
5956
- { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(10), to: at(11, 30), leadIn: min(15) },
5957
- { id: "c-2043-3", task: "c-2043", lane: "van-455", from: at(12), to: at(14), leadIn: min(20), leadOut: min(20) },
5958
- ];
7733
+ Every subtask that covers blocked time on its lane, lead-in and lead-out
7734
+ included - once per blocked interval it covers, in the order of the
7735
+ subtasks. Touching at a shared edge is not covering.
5959
7736
 
5960
- const TRANSFERS: readonly Dependency[] = [
5961
- { id: "t-2043-1", from: "c-2043-1", to: "c-2043-2", lag: min(45) },
5962
- { id: "t-2043-2", from: "c-2043-2", to: "c-2043-3", lag: min(25) },
5963
- ];
7737
+ ```ts
7738
+ function inBlockedTime(subtasks: readonly Subtask[], blocked: readonly BlockedTime[]): InBlockedTime[];
7739
+ ```
5964
7740
 
5965
- export default function CountPerKind() {
5966
- const doubleBooked = overlaps(LEGS).length;
5967
- const late = violatedDependencies(LEGS, TRANSFERS).length;
7741
+ Used on [Scenarios](#/), [Blocked time](#/blocked-time) and [Findings as data](#/findings).
5968
7742
 
5969
- return (
5970
- <Stack gap={3}>
5971
- <Stack direction="row" gap={2}>
5972
- <Badge tone={doubleBooked > 0 ? "warning" : "success"}>{doubleBooked} double-booked</Badge>
5973
- <Badge tone={late > 0 ? "warning" : "success"}>{late} transfers too short</Badge>
5974
- </Stack>
5975
- <Schedule ariaLabel="The day's legs with their findings counted" initialDomain={[at(5, 30), at(18)]} height={196}>
5976
- {VEHICLES.map((vehicle) => (
5977
- <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5978
- ))}
5979
- <Dependencies data={TRANSFERS} />
5980
- <Subtasks data={LEGS} tasks={CONSIGNMENTS} />
5981
- </Schedule>
5982
- </Stack>
5983
- );
5984
- }
7743
+ ##### `occupied`
7744
+
7745
+ The interval a subtask occupies on its lane: lead-in and lead-out included.
7746
+
7747
+ ```ts
7748
+ function occupied(subtask: Subtask): {
7749
+ from: number;
7750
+ to: number;
7751
+ };
5985
7752
  ```
5986
7753
 
5987
- #### API
7754
+ ##### `overlaps`
5988
7755
 
5989
- ##### `Overlap`
7756
+ Every pair of subtasks that cover each other on a lane. Touching at a
7757
+ shared edge is not covering, and a subtask that covers no time covers
7758
+ nothing.
5990
7759
 
5991
- | Prop | Type | Default | Description |
5992
- |---|---|---|---|
5993
- | `lane` (required) | `string` | — | The lane both sit on. |
5994
- | `first` (required) | `string` | — | The subtask that starts earlier (lead-in included); on a tie, the one that stands first in the data. |
5995
- | `second` (required) | `string` | — | The other one. |
5996
- | `from` (required) | `number` | — | Where the covering begins. |
5997
- | `to` (required) | `number` | — | Where it ends. |
7760
+ ```ts
7761
+ function overlaps(subtasks: readonly Subtask[]): Overlap[];
7762
+ ```
5998
7763
 
5999
- ##### `ViolatedDependency`
7764
+ Used on [Scenarios](#/), [Lane groups](#/lane-groups), [Findings as data](#/findings) and [Move and lane](#/move-and-lane).
6000
7765
 
6001
- | Prop | Type | Default | Description |
6002
- |---|---|---|---|
6003
- | `dependency` (required) | `string` | — | The id of the dependency. |
6004
- | `departure` (required) | `number` | — | When it leaves, by its anchor. |
6005
- | `arrival` (required) | `number` | — | When it has to have arrived, by its anchor. |
6006
- | `shortBy` (required) | `number` | — | How much time is missing: departure plus lag, minus arrival. |
7766
+ ##### `resolveAppearance`
6007
7767
 
6008
- ##### `InBlockedTime`
7768
+ Settles a subtask's appearances into what is drawn. `provisional` and
7769
+ `fixed` contradict each other; the later one in the list wins.
6009
7770
 
6010
- | Prop | Type | Default | Description |
6011
- |---|---|---|---|
6012
- | `subtask` (required) | `string` | — | The id of the subtask. |
6013
- | `blocked` (required) | `string` | — | The id of the blocked time it covers. |
6014
- | `lane` (required) | `string` | — | The lane both are on. |
6015
- | `from` (required) | `number` | — | Where the covering begins. |
6016
- | `to` (required) | `number` | — | Where it ends. |
7771
+ - `appearance` – The subtask's `appearance`, in the order it was given.
7772
+ - Returns Which of the four channels are on.
6017
7773
 
6018
- #### Known limits
7774
+ ```ts
7775
+ function resolveAppearance(appearance: readonly SubtaskAppearance[] | undefined): ResolvedAppearance;
7776
+ ```
6019
7777
 
6020
- - Overlapping work is marked, not packed into sub-lanes; stacked overlap is not built yet.
7778
+ Used on [Appearances](#/appearances).
7779
+
7780
+ ##### `ripple`
6021
7781
 
6022
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0032-what-umriss-is-not.md).
7782
+ The moves that push every successor whose dependency no longer fits after
7783
+ `intent` - transitively, in the order they were first pushed. The subtask the
7784
+ intent is about is never moved, and a cycle among the successors ends after a
7785
+ bounded number of pushes instead of running on.
6023
7786
 
6024
- ## The rest of the API
7787
+ ```ts
7788
+ function ripple(subtasks: readonly Subtask[], dependencies: readonly Dependency[], intent: Intent): MoveIntent[];
7789
+ ```
6025
7790
 
6026
- Exported as well, and named on no page above: the pure modules behind the components, and the types they are made of. Each with its declaration as the package's `.d.ts` carries it.
7791
+ Used on [Scenarios](#/), [Dependencies](#/dependencies) and [Ripple](#/ripple).
6027
7792
 
6028
- ### `occupied`
7793
+ ##### `shiftTask`
7794
+
7795
+ The moves that carry a whole task in time - every subtask of it by the same
7796
+ amount, so that its lengths and the distances between its stops stay as
7797
+ they were. The schedule never runs it: apply the moves like any other
7798
+ intent, or run [`ripple`](#/api/ripple) over them first.
7799
+
7800
+ - `subtasks` – All subtasks; only those of `task` are moved.
7801
+ - `task` – The id of the task, as its subtasks name it in `task`.
7802
+ - `by` – How far, in milliseconds; negative is earlier.
7803
+ - Returns One move intent per subtask of the task - none where `by` is zero
7804
+ or not finite.
6029
7805
 
6030
7806
  ```ts
6031
- /** The interval a subtask occupies on its lane: lead-in and lead-out included. */
6032
- function occupied(subtask: Subtask): {
6033
- from: number;
6034
- to: number;
6035
- };
7807
+ function shiftTask(subtasks: readonly Subtask[], task: string, by: number): MoveIntent[];
6036
7808
  ```
6037
7809
 
6038
- ### `LaneIntent`
7810
+ Used on [Scenarios](#/) and [Ripple](#/ripple).
7811
+
7812
+ ##### `snapTime`
7813
+
7814
+ `time` onto the nearest line of the raster in local time - a step alone
7815
+ starts at local midnight. A step of zero or less is no raster: the time
7816
+ stays as it is.
6039
7817
 
6040
7818
  ```ts
6041
- /** Put the subtask on another lane. */
6042
- interface LaneIntent {
6043
- readonly kind: "lane";
6044
- /** The id of the subtask. */
6045
- readonly subtask: string;
6046
- /** The id of the new lane. */
6047
- readonly lane: string;
6048
- }
7819
+ function snapTime(time: number, raster: number | SnapRaster): number;
6049
7820
  ```
6050
7821
 
6051
- ### `LeadInIntent`
7822
+ Used on [Snapping](#/snapping).
7823
+
7824
+ ##### `subtaskFromPlace`
7825
+
7826
+ The subtask a place intent asks for, under an id the caller chooses.
6052
7827
 
6053
7828
  ```ts
6054
- /** Change the lead-in on its own. */
6055
- interface LeadInIntent {
6056
- readonly kind: "leadIn";
6057
- /** The id of the subtask. */
6058
- readonly subtask: string;
6059
- /** The new duration of the lead-in. */
6060
- readonly leadIn: number;
6061
- }
7829
+ function subtaskFromPlace(intent: PlaceIntent, id: string): Subtask;
6062
7830
  ```
6063
7831
 
6064
- ### `StretchIntent`
7832
+ Used on [Placing from outside](#/placing) and [Where a subtask may go](#/where-it-may-go).
7833
+
7834
+ ##### `violatedDependencies`
7835
+
7836
+ Every dependency whose successor is to be reached before the predecessor's
7837
+ departure plus the dependency's lag. A dependency naming a subtask that
7838
+ is not in the data is passed over: there is nothing to judge.
6065
7839
 
6066
7840
  ```ts
6067
- /** Stretch the main time at one or both of its edges. */
6068
- interface StretchIntent {
6069
- readonly kind: "stretch";
6070
- /** The id of the subtask. */
6071
- readonly subtask: string;
6072
- /** The new start of the main time. */
6073
- readonly from: number;
6074
- /** The new end of the main time. */
6075
- readonly to: number;
6076
- }
7841
+ function violatedDependencies(subtasks: readonly Subtask[], dependencies: readonly Dependency[]): ViolatedDependency[];
6077
7842
  ```
6078
7843
 
6079
- ### `LeadOutIntent`
7844
+ Used on [Scenarios](#/), [Lane groups](#/lane-groups), [Tooltip](#/tooltip) and [Findings as data](#/findings).
7845
+
7846
+ #### Types
7847
+
7848
+ ##### `BlockedTime`
7849
+
7850
+ Its table stands on [Blocked time](#/blocked-time/type-BlockedTime).
7851
+
7852
+ Used on [Scenarios](#/), [Blocked time](#/blocked-time), [Tooltip](#/tooltip), [Findings as data](#/findings) and [Where a subtask may go](#/where-it-may-go).
7853
+
7854
+ ##### `BlockedTimesProps`
7855
+
7856
+ Its table stands on [Blocked time](#/blocked-time/type-BlockedTimesProps).
7857
+
7858
+ ##### `DependenciesProps`
7859
+
7860
+ Its table stands on [Dependencies](#/dependencies/type-DependenciesProps).
7861
+
7862
+ ##### `Dependency`
7863
+
7864
+ Its table stands on [Dependencies](#/dependencies/type-Dependency).
7865
+
7866
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lane groups](#/lane-groups), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Placing from outside](#/placing) and [Ripple](#/ripple).
7867
+
7868
+ ##### `DependencyAttachment`
7869
+
7870
+ Where on its bars a dependency's line attaches: the middle of both, or the
7871
+ edge that faces the other stop - which is the shortest line between them.
7872
+
7873
+ Deliberately not called an anchor: a dependency already HAS two anchors
7874
+ (`leaves`, `arrives`), and those decide what it connects and therefore
7875
+ whether it is violated. This decides where the line touches, and nothing else.
7876
+ The tree has an **Anchor** of its own as well (CONTEXT.md).
6080
7877
 
6081
7878
  ```ts
6082
- /** Change the lead-out on its own. */
6083
- interface LeadOutIntent {
6084
- readonly kind: "leadOut";
6085
- /** The id of the subtask. */
6086
- readonly subtask: string;
6087
- /** The new duration of the lead-out. */
6088
- readonly leadOut: number;
6089
- }
7879
+ type DependencyAttachment = "centre" | "nearest";
6090
7880
  ```
6091
7881
 
6092
- ### `Findings`
7882
+ Used on [Dependencies](#/dependencies).
7883
+
7884
+ ##### `DependencyEnds`
7885
+
7886
+ Whether a dependency's two ends carry a dot. The dot says where the line is
7887
+ anchored, which is worth saying while a plan is being read and is noise in a
7888
+ plan full of short moves - so it is the caller's choice.
6093
7889
 
6094
7890
  ```ts
6095
- /** Every kind of finding over the same data. */
6096
- interface Findings {
6097
- readonly overlaps: readonly Overlap[];
6098
- readonly violatedDependencies: readonly ViolatedDependency[];
6099
- readonly inBlockedTime: readonly InBlockedTime[];
6100
- }
7891
+ type DependencyEnds = "dot" | "none";
6101
7892
  ```
6102
7893
 
6103
- ### `PlaceIntent`
7894
+ Used on [Dependencies](#/dependencies).
7895
+
7896
+ ##### `DependencyRoute`
7897
+
7898
+ How a dependency is drawn between its two ends: a curve that leaves and
7899
+ arrives forwards, a straight line, or axis-parallel segments.
6104
7900
 
6105
7901
  ```ts
6106
- /** Put work on the plan that was not on it: the answer to a drag from outside
6107
- the schedule (schedule-refinement 07). It carries no subtask id - there is
6108
- no subtask yet; the caller creates one, with its own identity, from
6109
- `subtaskFromPlace`. */
6110
- interface PlaceIntent {
6111
- readonly kind: "place";
6112
- /** The caller's key for the dragged item, as it declared it while dragging. */
6113
- readonly item: string;
6114
- /** The task the work belongs to. */
6115
- readonly task: string;
6116
- /** The lane it was dropped on. */
6117
- readonly lane: string;
6118
- /** Where its main time would start. */
6119
- readonly from: number;
6120
- /** Where its main time would end. */
6121
- readonly to: number;
6122
- /** The lead-in it was declared with. */
6123
- readonly leadIn?: number;
6124
- /** The lead-out it was declared with. */
6125
- readonly leadOut?: number;
6126
- }
7902
+ type DependencyRoute = "curve" | "straight" | "orthogonal";
7903
+ ```
7904
+
7905
+ ##### `Findings`
7906
+
7907
+ Every kind of finding over the same data.
7908
+
7909
+ | Prop | Type | Default | Description |
7910
+ |---|---|---|---|
7911
+ | `overlaps` *required* | `readonly `[`Overlap`](#/findings/type-Overlap)`[]` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [List the findings (Findings as data)](#/findings/list-the-findings), [Say when there is nothing to fix (Findings as data)](#/findings/nothing-to-fix) and 1 more |
7912
+ | `violatedDependencies` *required* | `readonly `[`ViolatedDependency`](#/findings/type-ViolatedDependency)`[]` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [List the findings (Findings as data)](#/findings/list-the-findings), [Say when there is nothing to fix (Findings as data)](#/findings/nothing-to-fix) and 1 more |
7913
+ | `inBlockedTime` *required* | `readonly `[`InBlockedTime`](#/findings/type-InBlockedTime)`[]` | — | <br>Shown in: [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Check a sprint against the leave calendar (Blocked time)](#/blocked-time/team-leave), [List the findings (Findings as data)](#/findings/list-the-findings) and 3 more |
7914
+
7915
+ ##### `InBlockedTime`
7916
+
7917
+ Its table stands on [Findings as data](#/findings/type-InBlockedTime).
7918
+
7919
+ ##### `Intent`
7920
+
7921
+ What the schedule reports when an interaction asks for a change. Every
7922
+ intent carries the values it asks for, not a difference: two intents of one
7923
+ drop - a move and a lane - give the same data in either order.
7924
+
7925
+ ```ts
7926
+ type Intent = MoveIntent | StretchIntent | LeadInIntent | LeadOutIntent | LaneIntent | PlaceIntent;
6127
7927
  ```
6128
7928
 
6129
- ### `ResolvedAppearance`
7929
+ Used on [Scenarios](#/), [Blocked time](#/blocked-time), [Move and lane](#/move-and-lane), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
7930
+
7931
+ ##### `IntentKind`
7932
+
7933
+ The names of the intents - the editing API of the schedule.
6130
7934
 
6131
7935
  ```ts
6132
- /** What the drawing needs to know, with the contradiction already settled.
6133
-
6134
- The four names are the ones this module was born with, and they are kept so
6135
- that a caller's code does not change because a picture did. Two of them are
6136
- now named after a drawing they no longer use, and the channel each one
6137
- really owns stands beside it (`sceneDraw.ts`, "ONE CHANNEL PER
6138
- STATEMENT"). */
6139
- interface ResolvedAppearance {
6140
- /** `provisional`: the FILL - none at all, so the surface shows through, with
6141
- a dashed outline in the task colour. Planned, not released. */
6142
- readonly dashed: boolean;
6143
- /** `fixed`: the ENDS - a cap at each end of the main time. Not a hatch any
6144
- more; a hatch now says "not available" of a refused lane, and the name is
6145
- kept only so that this type did not change with the picture. */
6146
- readonly hatched: boolean;
6147
- /** `muted`: the SATURATION - the task colour mixed half into the surface,
6148
- opaque and at full height. Another team's, another crew's. */
6149
- readonly muted: boolean;
6150
- /** `open`: the FADE, where the bar passes the edge of the view. It goes on
6151
- past what is drawn. */
6152
- readonly open: boolean;
6153
- }
7936
+ type IntentKind = Intent["kind"];
7937
+ ```
7938
+
7939
+ ##### `LaneGroupProps`
7940
+
7941
+ Its table stands on [Lane groups](#/lane-groups/type-LaneGroupProps).
7942
+
7943
+ ##### `LaneIntent`
7944
+
7945
+ Put the subtask on another lane.
7946
+
7947
+ | Prop | Type | Default | Description |
7948
+ |---|---|---|---|
7949
+ | `kind` *required* | `"lane"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
7950
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
7951
+ | `lane` *required* | `string` | — | The id of the new lane. |
7952
+
7953
+ ##### `LaneProps`
7954
+
7955
+ Its table stands on [Lanes](#/lane/type-LaneProps).
7956
+
7957
+ ##### `LeadInIntent`
7958
+
7959
+ Change the lead-in on its own.
7960
+
7961
+ | Prop | Type | Default | Description |
7962
+ |---|---|---|---|
7963
+ | `kind` *required* | `"leadIn"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
7964
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
7965
+ | `leadIn` *required* | `number` | — | The new duration of the lead-in.<br>Shown in: [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
7966
+
7967
+ ##### `LeadOutIntent`
7968
+
7969
+ Change the lead-out on its own.
7970
+
7971
+ | Prop | Type | Default | Description |
7972
+ |---|---|---|---|
7973
+ | `kind` *required* | `"leadOut"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
7974
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
7975
+ | `leadOut` *required* | `number` | — | The new duration of the lead-out. |
7976
+
7977
+ ##### `MoveIntent`
7978
+
7979
+ Move the subtask in time: the main time keeps its length.
7980
+
7981
+ | Prop | Type | Default | Description |
7982
+ |---|---|---|---|
7983
+ | `kind` *required* | `"move"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 8 more |
7984
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 5 more |
7985
+ | `from` *required* | `number` | — | The new start of the main time.<br>Shown in: [Push the later stops of a tour (Ripple)](#/ripple/push-the-later-stops), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
7986
+ | `to` *required* | `number` | — | The new end of the main time.<br>Shown in: [Push the later stops of a tour (Ripple)](#/ripple/push-the-later-stops), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
7987
+
7988
+ Used on [Ripple](#/ripple).
7989
+
7990
+ ##### `Overlap`
7991
+
7992
+ Its table stands on [Findings as data](#/findings/type-Overlap).
7993
+
7994
+ ##### `PlaceIntent`
7995
+
7996
+ Put work on the plan that was not on it: the answer to a drag from outside
7997
+ the schedule (schedule-refinement 07). It carries no subtask id - there is
7998
+ no subtask yet; the caller creates one, with its own identity, from
7999
+ `subtaskFromPlace`.
8000
+
8001
+ | Prop | Type | Default | Description |
8002
+ |---|---|---|---|
8003
+ | `kind` *required* | `"place"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
8004
+ | `item` *required* | `string` | — | The caller's key for the dragged item, as it declared it while dragging.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in), [Replan the day on the line (Scenarios)](#/scenarios/replan-the-day-on-the-line) |
8005
+ | `task` *required* | `string` | — | The task the work belongs to. |
8006
+ | `lane` *required* | `string` | — | The lane it was dropped on.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
8007
+ | `from` *required* | `number` | — | Where its main time would start. |
8008
+ | `to` *required* | `number` | — | Where its main time would end. |
8009
+ | `leadIn` | `number` | — | The lead-in it was declared with. |
8010
+ | `leadOut` | `number` | — | The lead-out it was declared with. |
8011
+
8012
+ ##### `PlacingItem`
8013
+
8014
+ What the application says it is dragging in, while it drags it
8015
+ (schedule-refinement 07): the browser hands over the dragged data only on
8016
+ the drop, so the ghost before the drop can only come from the caller.
8017
+
8018
+ | Prop | Type | Default | Description |
8019
+ |---|---|---|---|
8020
+ | `item` *required* | `string` | — | The caller's key for the dragged item; it comes back in the intent.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
8021
+ | `task` *required* | `string` | — | The task the work belongs to.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
8022
+ | `duration` *required* | `number` | — | How long its main time is.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
8023
+ | `leadIn` | `number` | — | The lead-in it brings. |
8024
+ | `leadOut` | `number` | — | The lead-out it brings. |
8025
+
8026
+ Used on [Placing from outside](#/placing) and [Where a subtask may go](#/where-it-may-go).
8027
+
8028
+ ##### `ResolvedAppearance`
8029
+
8030
+ What the drawing needs to know, with the contradiction already settled.
8031
+
8032
+ The four names are the ones this module was born with, and they are kept so
8033
+ that a caller's code does not change because a picture did. Two of them are
8034
+ now named after a drawing they no longer use, and the channel each one
8035
+ really owns stands beside it.
8036
+
8037
+ | Prop | Type | Default | Description |
8038
+ |---|---|---|---|
8039
+ | `dashed` *required* | `boolean` | — | `provisional`: the FILL - none at all, so the surface shows through, with a dashed outline in the task colour. Planned, not released. |
8040
+ | `hatched` *required* | `boolean` | — | `fixed`: the ENDS - a cap at each end of the main time. Not a hatch any more; a hatch now says "not available" of a refused lane, and the name is kept only so that this type did not change with the picture. |
8041
+ | `muted` *required* | `boolean` | — | `muted`: the SATURATION - the task colour mixed half into the surface, opaque and at full height. Another team's, another crew's. |
8042
+ | `open` *required* | `boolean` | — | `open`: the FADE, where the bar passes the edge of the view. It goes on past what is drawn. |
8043
+
8044
+ ##### `ScheduleHandle`
8045
+
8046
+ What a schedule offers a caller imperatively: the arithmetic between a
8047
+ point on the screen and a time on a lane. Everything else is props.
8048
+
8049
+ | Prop | Type | Default | Description |
8050
+ |---|---|---|---|
8051
+ | `positionAt` *required* | `(clientX: number, clientY: number) => { time: number; lane: string \| null } \| null` | — | The time and the lane at a client point, or null outside the plot.<br>Shown in: [Place a mark of your own (The handle)](#/handle/place-a-mark) |
8052
+ | `clientPointOf` *required* | `(time: number, lane?: string) => { x: number; y: number } \| null` | — | The client point of a time - on the middle of a lane where one is named - or null while the schedule is not on screen.<br>Shown in: [Place a mark of your own (The handle)](#/handle/place-a-mark) |
8053
+ | `visibleDomain` *required* | `() => readonly [number, number]` | — | The visible span, as two wall-clock instants. |
8054
+
8055
+ Used on [The handle](#/handle).
8056
+
8057
+ ##### `ScheduleHit`
8058
+
8059
+ What a pointer is on.
8060
+
8061
+ ```ts
8062
+ type ScheduleHit =
8063
+ | { readonly kind: "subtask"; readonly subtask: Subtask; readonly part: "leadIn" | "main" | "leadOut" }
8064
+ | { readonly kind: "dependency"; readonly dependency: Dependency }
8065
+ | { readonly kind: "lane"; readonly lane: string }
8066
+ | { readonly kind: "nothing" };
8067
+ ```
8068
+
8069
+ ##### `ScheduleInteraction`
8070
+
8071
+ Its table stands on [Interactions](#/interactions/type-ScheduleInteraction).
8072
+
8073
+ Used on [Scenarios](#/), [Interactions](#/interactions) and [Tooltip](#/tooltip).
8074
+
8075
+ ##### `ScheduleProps`
8076
+
8077
+ Its table stands on [First schedule](#/schedule/type-ScheduleProps).
8078
+
8079
+ ##### `ScheduleTooltipTarget`
8080
+
8081
+ Its table stands on [Tooltip](#/tooltip/type-ScheduleTooltipTarget).
8082
+
8083
+ Used on [Tooltip](#/tooltip).
8084
+
8085
+ ##### `SnapRaster`
8086
+
8087
+ A raster: a step, and where it starts after local midnight.
8088
+
8089
+ | Prop | Type | Default | Description |
8090
+ |---|---|---|---|
8091
+ | `step` *required* | `number` | — | The distance between two lines of the raster, in milliseconds.<br>Shown in: [Snap to handover times (Snapping)](#/snapping/snap-to-handovers) |
8092
+ | `offset` *required* | `number` | — | Where the raster starts after local midnight, in milliseconds: six hours for handovers at 06:00, 14:00 and 22:00.<br>Shown in: [Snap to handover times (Snapping)](#/snapping/snap-to-handovers) |
8093
+
8094
+ Used on [Snapping](#/snapping).
8095
+
8096
+ ##### `StretchIntent`
8097
+
8098
+ Stretch the main time at one or both of its edges.
8099
+
8100
+ | Prop | Type | Default | Description |
8101
+ |---|---|---|---|
8102
+ | `kind` *required* | `"stretch"` | — | <br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 7 more |
8103
+ | `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Refuse a move that double-books someone (Move and lane)](#/move-and-lane/refuse-a-double-booking) and 4 more |
8104
+ | `from` *required* | `number` | — | The new start of the main time. |
8105
+ | `to` *required* | `number` | — | The new end of the main time. |
8106
+
8107
+ ##### `Subtask`
8108
+
8109
+ Its table stands on [Subtasks](#/subtasks/type-Subtask).
8110
+
8111
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
8112
+
8113
+ ##### `SubtaskAppearance`
8114
+
8115
+ The appearances a caller may give a subtask.
8116
+
8117
+ ```ts
8118
+ type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";
6154
8119
  ```
8120
+
8121
+ ##### `SubtasksProps`
8122
+
8123
+ Its table stands on [Subtasks](#/subtasks/type-SubtasksProps).
8124
+
8125
+ ##### `Task`
8126
+
8127
+ Its table stands on [Subtasks](#/subtasks/type-Task).
8128
+
8129
+ Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
8130
+
8131
+ ##### `ViolatedDependency`
8132
+
8133
+ Its table stands on [Findings as data](#/findings/type-ViolatedDependency).
8134
+
8135
+ Used on [Dependencies](#/dependencies).
8136
+
8137
+ ##### `ZoomLimits`
8138
+
8139
+ The narrowest and the widest span zoom may reach.
8140
+
8141
+ | Prop | Type | Default | Description |
8142
+ |---|---|---|---|
8143
+ | `min` *required* | `number` | — | <br>Shown in: [Move through the plan (Pan and zoom)](#/pan-and-zoom/pan-and-zoom) |
8144
+ | `max` *required* | `number` | — | <br>Shown in: [Move through the plan (Pan and zoom)](#/pan-and-zoom/pan-and-zoom) |