@umriss-ui/schedule 0.4.0 → 0.5.1

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,4 +1,4 @@
1
- # @umriss-ui/schedule 0.4.0
1
+ # @umriss-ui/schedule 0.5.1
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
 
@@ -22,7 +22,7 @@ Built from: First schedule, Blocked time, Now line, Move and lane, Findings as d
22
22
  ```tsx
23
23
  import { useState } from "react";
24
24
  import { Alert, Badge, Stack, Text } from "@umriss-ui/core";
25
- import { BlockedTimes, Lane, Schedule, Subtasks, applyIntent, findings } from "@umriss-ui/schedule";
25
+ import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
26
26
  import type { BlockedTime, Intent, Subtask, Task } from "@umriss-ui/schedule";
27
27
 
28
28
  /* Data from the operations world, written out here so the example runs on its own. */
@@ -108,6 +108,7 @@ const when = (instant: number) =>
108
108
  });
109
109
 
110
110
  export default function HandOver() {
111
+ const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(16, 6), day(23, 12)] } });
111
112
  const [rota, setRota] = useState<readonly Subtask[]>(START);
112
113
 
113
114
  const onIntent = (intent: Intent) => {
@@ -133,7 +134,6 @@ export default function HandOver() {
133
134
  <div data-callout="2">
134
135
  <Schedule
135
136
  ariaLabel="On-call rota, 16 to 23 March"
136
- initialDomain={[day(16, 6), day(23, 12)]}
137
137
  height={420}
138
138
  now={NOW}
139
139
  intents={["lane"]}
@@ -195,7 +195,7 @@ Built from: First schedule, Lane groups, Dependencies, Ripple, Badge, Button.
195
195
  ```tsx
196
196
  import { useState } from "react";
197
197
  import { Badge, Button, Stack, Text } from "@umriss-ui/core";
198
- import { Dependencies, Lane, LaneGroup, Schedule, Subtasks, applyIntent, ripple, shiftTask } from "@umriss-ui/schedule";
198
+ import { applyIntent, ripple, shiftTask, useSchedule } from "@umriss-ui/schedule";
199
199
  import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
200
200
 
201
201
  /* Data from the logistics world, written out here so the example runs on its own. */
@@ -391,6 +391,7 @@ const plateOf = (vehicle: string) => VEHICLES.find((one) => one.id === vehicle)?
391
391
  const driverOf = (driver: string) => DRIVERS.find((one) => one.id === driver)?.name ?? driver;
392
392
 
393
393
  export default function TourPlan() {
394
+ const { Schedule, Lane, LaneGroup, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [tomorrow(5, 30), tomorrow(15)] } });
394
395
  const [plan, setPlan] = useState<readonly Subtask[]>(START);
395
396
 
396
397
  const apply = (intent: Intent) =>
@@ -418,7 +419,6 @@ export default function TourPlan() {
418
419
  <div data-callout="1">
419
420
  <Schedule
420
421
  ariaLabel="Tours of Wednesday, 18 March"
421
- initialDomain={[tomorrow(5, 30), tomorrow(15)]}
422
422
  height={470}
423
423
  intents={["move"]}
424
424
  onIntent={apply}
@@ -479,7 +479,7 @@ Built from: First schedule, Lane groups, Blocked time, Time axis and calendar, W
479
479
  ```tsx
480
480
  import { useState } from "react";
481
481
  import { Alert, Badge, Stack, Text } from "@umriss-ui/core";
482
- import { BlockedTimes, Lane, LaneGroup, Schedule, Subtasks, applyIntent, findings } from "@umriss-ui/schedule";
482
+ import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
483
483
  import type { BlockedTime, Intent, Subtask } from "@umriss-ui/schedule";
484
484
 
485
485
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -631,6 +631,7 @@ const teamOf = (person: string) => PEOPLE.find((one) => one.id === person)?.team
631
631
  const nameOf = (person: string) => PEOPLE.find((one) => one.id === person)?.name ?? person;
632
632
 
633
633
  export default function SprintAroundLeave() {
634
+ const { Schedule, Lane, LaneGroup, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [SPRINT.from, SPRINT.to] } });
634
635
  const [work, setWork] = useState<readonly Subtask[]>(START);
635
636
 
636
637
  const onIntent = (intent: Intent) => {
@@ -652,7 +653,6 @@ export default function SprintAroundLeave() {
652
653
  <div data-callout="2">
653
654
  <Schedule
654
655
  ariaLabel="Sprint 14, work per person"
655
- initialDomain={[SPRINT.from, SPRINT.to]}
656
656
  calendar={WORKING_DAYS}
657
657
  height={570}
658
658
  now={NOW}
@@ -716,7 +716,7 @@ Built from: First schedule, Dependencies, Move and lane, Stretch, lead-in, lead-
716
716
  ```tsx
717
717
  import { useState } from "react";
718
718
  import { Checkbox, ContextMenu, MenuItem, MenuSeparator, Stack, Text } from "@umriss-ui/core";
719
- import { Lane, Schedule, Subtasks, Dependencies, applyIntent, findings, ripple, shiftTask } from "@umriss-ui/schedule";
719
+ import { applyIntent, findings, ripple, shiftTask, useSchedule } from "@umriss-ui/schedule";
720
720
  import type { Dependency, Intent, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
721
721
 
722
722
  /* Data from the plant world, written out here so the example runs on its own. */
@@ -821,6 +821,7 @@ function applied(plan: Plan, intent: Intent, cascade: boolean): Plan {
821
821
  }
822
822
 
823
823
  export default function ReplanTheDay() {
824
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY_OF_PLAN } });
824
825
  const [plan, setPlan] = useState<Plan>(START);
825
826
  const [cascade, setCascade] = useState(true);
826
827
  const [menu, setMenu] = useState<Menu | null>(null);
@@ -844,7 +845,6 @@ export default function ReplanTheDay() {
844
845
  <div data-callout="2">
845
846
  <Schedule
846
847
  ariaLabel="Plan of Tuesday, 17 March, in the planner's hands"
847
- initialDomain={DAY_OF_PLAN}
848
848
  height={380}
849
849
  intents={["move", "lane", "stretch", "leadIn", "leadOut"]}
850
850
  onIntent={apply}
@@ -906,7 +906,7 @@ What an application installs and hands over before the first plan.
906
906
  Install the schedule with its two peers and hand it plain data: lanes, tasks, subtasks and dependencies. It draws them and reports what a planner wants to change.
907
907
 
908
908
  ```ts
909
- import { Schedule, Lane, Subtasks, Dependencies } from "@umriss-ui/schedule";
909
+ import { useSchedule } from "@umriss-ui/schedule";
910
910
  ```
911
911
 
912
912
  ```sh
@@ -928,7 +928,7 @@ The schedule reports, the application decides. It draws the data it is given and
928
928
  Two people, one project, one dependency: lanes in JSX, the rest as plain arrays handed to `Subtasks` and `Dependencies`.
929
929
 
930
930
  ```tsx
931
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
931
+ import { useSchedule } from "@umriss-ui/schedule";
932
932
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
933
933
 
934
934
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -944,8 +944,9 @@ const subtasks: Subtask[] = [
944
944
  const dependencies: Dependency[] = [{ id: "handover", from: "design", to: "build", lag: min(15) }];
945
945
 
946
946
  export default function SmallestSchedule() {
947
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(8), at(16)] } });
947
948
  return (
948
- <Schedule ariaLabel="Tuesday, design and build" initialDomain={[at(8), at(16)]} height={150}>
949
+ <Schedule ariaLabel="Tuesday, design and build" height={150}>
949
950
  <Lane id="noah" label="Noah Fischer" />
950
951
  <Lane id="arjun" label="Arjun Mehta" />
951
952
  <Dependencies data={dependencies} />
@@ -965,7 +966,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
965
966
 
966
967
  | Prop | Type | Default | Description |
967
968
  |---|---|---|---|
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
969
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
970
970
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
971
971
 
@@ -1024,12 +1024,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
1024
1024
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
1025
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. |
1026
1026
 
1027
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
1028
+
1029
+ | Prop | Type | Default | Description |
1030
+ |---|---|---|---|
1031
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
1032
+
1033
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
1034
+
1035
+ | Prop | Type | Default | Description |
1036
+ |---|---|---|---|
1037
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
1038
+
1027
1039
  ### First schedule
1028
1040
 
1029
1041
  Work on lanes over time, with a day band above and time ticks that follow the zoom (also called a Gantt chart, resource plan or timeline). Reach for it when a planner needs to see who or what is busy when, and what does not fit.
1030
1042
 
1031
1043
  ```ts
1032
- import { Schedule, Lane, Subtasks, Dependencies } from "@umriss-ui/schedule";
1044
+ import { useSchedule } from "@umriss-ui/schedule";
1033
1045
  ```
1034
1046
 
1035
1047
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/schedule/
@@ -1047,7 +1059,7 @@ The plot is one tab stop. The keys walk the subtasks as the pointer would hover
1047
1059
  Vehicles as lanes, tours as tasks: each leg is a subtask with loading as its lead-in, each handover a dependency with a transfer time.
1048
1060
 
1049
1061
  ```tsx
1050
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
1062
+ import { useSchedule } from "@umriss-ui/schedule";
1051
1063
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
1052
1064
 
1053
1065
  /* Two findings stand in this plan on purpose: two tours claim van FP 214 K at
@@ -1091,8 +1103,9 @@ const HANDOVERS: readonly Dependency[] = [
1091
1103
  ];
1092
1104
 
1093
1105
  export default function FirstSchedule() {
1106
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY } });
1094
1107
  return (
1095
- <Schedule ariaLabel="Tours of Tuesday, 17 March" initialDomain={DAY} height={240}>
1108
+ <Schedule ariaLabel="Tours of Tuesday, 17 March" height={240}>
1096
1109
  {VEHICLES.map((vehicle) => (
1097
1110
  <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
1098
1111
  ))}
@@ -1108,7 +1121,7 @@ export default function FirstSchedule() {
1108
1121
  With nothing planned yet, `Subtasks` gets an empty array: the lanes and the time axis still stand, ready for the first drop.
1109
1122
 
1110
1123
  ```tsx
1111
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
1124
+ import { useSchedule } from "@umriss-ui/schedule";
1112
1125
  import type { Subtask, Task } from "@umriss-ui/schedule";
1113
1126
 
1114
1127
  const at = (day: number, hours: number) => new Date(2026, 2, day, hours).getTime();
@@ -1123,8 +1136,9 @@ const PEOPLE = [
1123
1136
  ];
1124
1137
 
1125
1138
  export default function NoWorkYet() {
1139
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(23, 8), at(27, 18)] } });
1126
1140
  return (
1127
- <Schedule ariaLabel="Sprint 15, nothing planned yet" initialDomain={[at(23, 8), at(27, 18)]} height={170}>
1141
+ <Schedule ariaLabel="Sprint 15, nothing planned yet" height={170}>
1128
1142
  {PEOPLE.map((person) => (
1129
1143
  <Lane key={person.id} id={person.id} label={person.label} />
1130
1144
  ))}
@@ -1139,7 +1153,7 @@ export default function NoWorkYet() {
1139
1153
  The full case: seven stations, six orders and their transports, the present marked and every order named in its bars.
1140
1154
 
1141
1155
  ```tsx
1142
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
1156
+ import { useSchedule } from "@umriss-ui/schedule";
1143
1157
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
1144
1158
 
1145
1159
  /* Data from the plant world, written out here so the example runs on its own. */
@@ -1216,10 +1230,10 @@ const NAMES = new Map(ORDERS.map((order) => [order.id, order.name ?? order.id]))
1216
1230
  const HALF_PAST_TEN = new Date(2026, 2, 17, 10, 30).getTime();
1217
1231
 
1218
1232
  export default function MachinePlan() {
1233
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY_OF_PLAN } });
1219
1234
  return (
1220
1235
  <Schedule
1221
1236
  ariaLabel="Machine shop, Tuesday 17 March"
1222
- initialDomain={DAY_OF_PLAN}
1223
1237
  height={380}
1224
1238
  now={HALF_PAST_TEN}
1225
1239
  label={(step) => NAMES.get(step.task) ?? step.task}
@@ -1264,8 +1278,7 @@ Under forced colours the plot paints itself in the system colours: the work in t
1264
1278
 
1265
1279
  | Prop | Type | Default | Description |
1266
1280
  |---|---|---|---|
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 |
1281
+ | `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 75 more |
1269
1282
  | `laneHeight` | `number` | `44` | Height of one lane in pixels.<br>Shown in: [Scroll more lanes than fit (Lanes)](#/lane/many-lanes) |
1270
1283
  | `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
1284
  | `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) |
@@ -1275,9 +1288,6 @@ Under forced colours the plot paints itself in the system colours: the work in t
1275
1288
  | `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
1289
  | `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
1290
  | `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
1291
  | `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
1292
  | `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
1293
  | `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 |
@@ -1285,7 +1295,7 @@ Under forced colours the plot paints itself in the system colours: the work in t
1285
1295
  | `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
1296
  | `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
1297
  | `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 |
1298
+ | `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 75 more |
1289
1299
 
1290
1300
  ###### Events
1291
1301
 
@@ -1293,13 +1303,12 @@ Under forced colours the plot paints itself in the system colours: the work in t
1293
1303
  |---|---|---|---|
1294
1304
  | `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
1305
  | `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
1306
 
1298
1307
  ###### Accessibility
1299
1308
 
1300
1309
  | Prop | Type | Default | Description |
1301
1310
  |---|---|---|---|
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 |
1311
+ | `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 75 more |
1303
1312
 
1304
1313
  ###### Styling
1305
1314
 
@@ -1521,7 +1530,7 @@ What the schedule draws: work on lanes, and everything a bar says besides its co
1521
1530
  One row per person, vehicle or room, named by its header and kept in the order it is declared, however many there are (also called resources).
1522
1531
 
1523
1532
  ```ts
1524
- import { Lane } from "@umriss-ui/schedule";
1533
+ import { useSchedule } from "@umriss-ui/schedule";
1525
1534
  ```
1526
1535
 
1527
1536
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane/
@@ -1538,7 +1547,7 @@ A `label` is content, not a string: here the vehicle with its plate, and a badge
1538
1547
 
1539
1548
  ```tsx
1540
1549
  import { Badge, Stack, Text } from "@umriss-ui/core";
1541
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
1550
+ import { useSchedule } from "@umriss-ui/schedule";
1542
1551
  import type { Subtask, Task } from "@umriss-ui/schedule";
1543
1552
 
1544
1553
  const at = (hours: number) => new Date(2026, 2, 17, hours).getTime();
@@ -1551,8 +1560,9 @@ const LEGS: Subtask[] = [
1551
1560
  ];
1552
1561
 
1553
1562
  export default function LaneHeaders() {
1563
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(12)] } });
1554
1564
  return (
1555
- <Schedule ariaLabel="Two vans of the North depot" initialDomain={[at(6), at(12)]} height={150} headerWidth={210}>
1565
+ <Schedule ariaLabel="Two vans of the North depot" height={150} headerWidth={210}>
1556
1566
  <Lane
1557
1567
  id="v1"
1558
1568
  label={
@@ -1587,7 +1597,7 @@ export default function LaneHeaders() {
1587
1597
  Twenty vans in 300 pixels: the lanes scroll with their headers, while the time bands and the header column hold still.
1588
1598
 
1589
1599
  ```tsx
1590
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
1600
+ import { useSchedule } from "@umriss-ui/schedule";
1591
1601
  import type { Subtask, Task } from "@umriss-ui/schedule";
1592
1602
 
1593
1603
  /* `height` is the whole schedule, both bands included; `laneHeight` and
@@ -1613,10 +1623,10 @@ const ROUNDS: Subtask[] = VANS.map((van, i) => ({
1613
1623
  }));
1614
1624
 
1615
1625
  export default function ManyLanes() {
1626
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14)] } });
1616
1627
  return (
1617
1628
  <Schedule
1618
1629
  ariaLabel="Twenty vans, Tuesday morning"
1619
- initialDomain={[at(6), at(14)]}
1620
1630
  height={300}
1621
1631
  laneHeight={32}
1622
1632
  headerWidth={110}
@@ -1644,15 +1654,15 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
1644
1654
 
1645
1655
  | Prop | Type | Default | Description |
1646
1656
  |---|---|---|---|
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 |
1657
+ | `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 75 more |
1658
+ | `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 75 more |
1649
1659
 
1650
1660
  ### Lane groups
1651
1661
 
1652
1662
  Teams, depots or regions over the lanes, to any depth, folded into one row when a planner wants only the part they work on.
1653
1663
 
1654
1664
  ```ts
1655
- import { LaneGroup, Lane } from "@umriss-ui/schedule";
1665
+ import { useSchedule } from "@umriss-ui/schedule";
1656
1666
  ```
1657
1667
 
1658
1668
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane-groups/
@@ -1661,7 +1671,7 @@ A group is structure, never a lane: nothing sits on it, no finding is reported f
1661
1671
 
1662
1672
  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.
1663
1673
 
1664
- Folding is a view state and never an intent. `collapsedGroups` and `onCollapsedGroupsChange` hand it to the application; left out, the schedule keeps it.
1674
+ Folding is part of the schedule's view and never an intent: `folded` in `initialView` starts with groups folded, `toggleGroup`, `foldAll` and `unfoldAll` fold from outside, and `onViewChange` reports every fold - see [View](#/view).
1665
1675
 
1666
1676
  #### Examples
1667
1677
 
@@ -1670,7 +1680,7 @@ Folding is a view state and never an intent. `collapsedGroups` and `onCollapsedG
1670
1680
  Wrap lanes in a `LaneGroup`: a slim head names the team and counts its lanes, and its chevron folds it.
1671
1681
 
1672
1682
  ```tsx
1673
- import { Lane, LaneGroup, Schedule, Subtasks } from "@umriss-ui/schedule";
1683
+ import { useSchedule } from "@umriss-ui/schedule";
1674
1684
  import type { Subtask, Task } from "@umriss-ui/schedule";
1675
1685
 
1676
1686
  /* Declared by composition: a lane never names its group, so moving a person
@@ -1692,8 +1702,9 @@ const WORK: Subtask[] = [
1692
1702
  ];
1693
1703
 
1694
1704
  export default function AGroup() {
1705
+ const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)] } });
1695
1706
  return (
1696
- <Schedule ariaLabel="Two developers in a team, a designer and a tester outside it" initialDomain={[at(6), at(15)]} height={240} headerWidth={200}>
1707
+ <Schedule ariaLabel="Two developers in a team, a designer and a tester outside it" height={240} headerWidth={200}>
1697
1708
  <LaneGroup id="developers" label="Developers">
1698
1709
  <Lane id="arjun" label="Arjun Mehta" />
1699
1710
  <Lane id="chloe" label="Chloe Durand" />
@@ -1711,7 +1722,7 @@ export default function AGroup() {
1711
1722
  A depot holds a fleet of vans and a truck; an inner group keeps its fold while the outer one closes.
1712
1723
 
1713
1724
  ```tsx
1714
- import { Lane, LaneGroup, Schedule, Subtasks } from "@umriss-ui/schedule";
1725
+ import { useSchedule } from "@umriss-ui/schedule";
1715
1726
  import type { Subtask, Task } from "@umriss-ui/schedule";
1716
1727
 
1717
1728
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -1734,8 +1745,9 @@ const LEGS: Subtask[] = [
1734
1745
  ];
1735
1746
 
1736
1747
  export default function Nesting() {
1748
+ const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)] } });
1737
1749
  return (
1738
- <Schedule ariaLabel="North depot with its vans and a truck, and a van from Riverside" initialDomain={[at(6), at(15)]} height={330} headerWidth={200}>
1750
+ <Schedule ariaLabel="North depot with its vans and a truck, and a van from Riverside" height={330} headerWidth={200}>
1739
1751
  <LaneGroup id="north" label="North depot">
1740
1752
  <LaneGroup id="vans" label="Vans">
1741
1753
  <Lane id="fp-214" label={`Van ${plate("FP 214 K")}`} />
@@ -1753,17 +1765,15 @@ export default function Nesting() {
1753
1765
 
1754
1766
  ##### Fold groups from outside
1755
1767
 
1756
- Pass `collapsedGroups` and `onCollapsedGroupsChange` to own the fold: store it with the planner's preferences, or fold two views together.
1768
+ `foldAll`, `unfoldAll` and `toggleGroup` fold from outside, and `view.folded` says what is folded - start from `initialView`, keep what `onViewChange` reports.
1757
1769
 
1758
1770
  ```tsx
1759
- import { useState } from "react";
1760
1771
  import { Button, Stack, Text } from "@umriss-ui/core";
1761
- import { Lane, LaneGroup, Schedule, Subtasks } from "@umriss-ui/schedule";
1772
+ import { useSchedule } from "@umriss-ui/schedule";
1762
1773
  import type { Subtask, Task } from "@umriss-ui/schedule";
1763
1774
 
1764
- /* The shape `selectedTask` has: left out, the schedule keeps the state
1765
- itself. A fold is not an intent - it says what is on screen, nothing about
1766
- the plan. */
1775
+ /* The fold is part of the schedule's view, not of the plan: no intent, and
1776
+ nothing the application has to hold for the chevrons to work. */
1767
1777
 
1768
1778
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
1769
1779
 
@@ -1776,28 +1786,21 @@ const WORK: Subtask[] = [
1776
1786
  { id: "s4", task: "release", lane: "leila", from: at(13), to: at(14, 30) },
1777
1787
  ];
1778
1788
 
1779
- const ALL = ["payments", "discovery"];
1780
-
1781
- export default function Controlled() {
1782
- const [folded, setFolded] = useState<readonly string[]>(["discovery"]);
1789
+ export default function FoldFromOutside() {
1790
+ const { Schedule, Lane, LaneGroup, Subtasks, view, foldAll, unfoldAll } = useSchedule({ initialView: { domain: [at(6), at(15)], folded: ["discovery"] } });
1791
+ const folded = view.folded ?? [];
1783
1792
 
1784
1793
  return (
1785
1794
  <Stack gap={3}>
1786
1795
  <Stack direction="row" gap={2}>
1787
- <Button size="sm" variant="secondary" onClick={() => setFolded(ALL)} data-fold-all>
1796
+ <Button size="sm" variant="secondary" onClick={foldAll} data-fold-all>
1788
1797
  Fold everything
1789
1798
  </Button>
1790
- <Button size="sm" variant="secondary" onClick={() => setFolded([])} data-unfold-all>
1799
+ <Button size="sm" variant="secondary" onClick={unfoldAll} data-unfold-all>
1791
1800
  Unfold everything
1792
1801
  </Button>
1793
1802
  </Stack>
1794
- <Schedule
1795
- ariaLabel="Two teams rolling out a release, folded from outside"
1796
- initialDomain={[at(6), at(15)]}
1797
- height={280}
1798
- collapsedGroups={folded}
1799
- onCollapsedGroupsChange={setFolded}
1800
- >
1803
+ <Schedule ariaLabel="Two teams rolling out a release, folded from outside" height={280}>
1801
1804
  <LaneGroup id="payments" label="Payments">
1802
1805
  <Lane id="priya" label="Priya Raman" />
1803
1806
  <Lane id="jonas" label="Jonas Keller" />
@@ -1822,12 +1825,12 @@ Folded, the depot shows each van as a thin strip: handovers still arrive, the do
1822
1825
 
1823
1826
  ```tsx
1824
1827
  import { useState } from "react";
1825
- import { Lane, LaneGroup, Schedule, Subtasks, Dependencies, applyIntent, findings } from "@umriss-ui/schedule";
1828
+ import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
1826
1829
  import { Stack, Text } from "@umriss-ui/core";
1827
1830
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
1828
1831
 
1829
1832
  /* A drag held over the folded depot opens it for the gesture and shuts it
1830
- again when the drag ends; the application's own list is never written to.
1833
+ again when the drag ends; the schedule's view never learns of it.
1831
1834
  `findings()` is computed from the data, so the count below is the same
1832
1835
  folded or not. */
1833
1836
 
@@ -1862,7 +1865,7 @@ const HANDOVERS: Dependency[] = [
1862
1865
  const FOUND = findings(LEGS, HANDOVERS);
1863
1866
 
1864
1867
  export default function TheMiniature() {
1865
- const [folded, setFolded] = useState<readonly string[]>(["north"]);
1868
+ const { Schedule, Lane, LaneGroup, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6), at(16)], folded: ["north"] } });
1866
1869
  const [legs, setLegs] = useState<readonly Subtask[]>(LEGS);
1867
1870
  const [last, setLast] = useState("Hold a bar over the folded depot");
1868
1871
 
@@ -1870,10 +1873,7 @@ export default function TheMiniature() {
1870
1873
  <Stack gap={3}>
1871
1874
  <Schedule
1872
1875
  ariaLabel="A depot that folds into one row"
1873
- initialDomain={[at(6), at(16)]}
1874
1876
  height={240}
1875
- collapsedGroups={folded}
1876
- onCollapsedGroupsChange={setFolded}
1877
1877
  intents={["move", "lane"]}
1878
1878
  onIntent={(intent) => {
1879
1879
  if (intent.kind === "place") return;
@@ -1903,14 +1903,15 @@ export default function TheMiniature() {
1903
1903
 
1904
1904
  ##### Start with groups folded
1905
1905
 
1906
- `defaultCollapsedGroups` folds groups when the schedule mounts and leaves the rest to the planner: Discovery starts folded, and its chevron opens it.
1906
+ `folded` in `initialView` folds groups when the schedule mounts and leaves the rest to the planner: Discovery starts folded, and its chevron opens it.
1907
1907
 
1908
1908
  ```tsx
1909
- import { Lane, LaneGroup, Schedule, Subtasks } from "@umriss-ui/schedule";
1909
+ import { useSchedule } from "@umriss-ui/schedule";
1910
1910
  import type { Subtask, Task } from "@umriss-ui/schedule";
1911
1911
 
1912
- /* Uncontrolled: the schedule keeps the fold from here on. To store it or
1913
- steer it, pass `collapsedGroups` instead. */
1912
+ /* The schedule keeps the fold from here on. To store it, keep what
1913
+ `onViewChange` reports; to steer it, use the setters `useSchedule` hands
1914
+ back. */
1914
1915
 
1915
1916
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
1916
1917
 
@@ -1924,13 +1925,9 @@ const WORK: Subtask[] = [
1924
1925
  ];
1925
1926
 
1926
1927
  export default function StartWithGroupsFolded() {
1928
+ const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)], folded: ["discovery"] } });
1927
1929
  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
- >
1930
+ <Schedule ariaLabel="Two teams rolling out a release, Discovery folded at the start" height={280}>
1934
1931
  <LaneGroup id="payments" label="Payments">
1935
1932
  <Lane id="priya" label="Priya Raman" />
1936
1933
  <Lane id="jonas" label="Jonas Keller" />
@@ -1960,9 +1957,9 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
1960
1957
 
1961
1958
  | Prop | Type | Default | Description |
1962
1959
  |---|---|---|---|
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 |
1960
+ | `id` *required* | `string` | — | The group's identity - what the view's `folded` 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/fold-from-outside) and 5 more |
1961
+ | `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/fold-from-outside) and 5 more |
1962
+ | `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/fold-from-outside) and 5 more |
1966
1963
 
1967
1964
  #### Known limits
1968
1965
 
@@ -1975,7 +1972,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
1975
1972
  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).
1976
1973
 
1977
1974
  ```ts
1978
- import { Subtasks } from "@umriss-ui/schedule";
1975
+ import { useSchedule } from "@umriss-ui/schedule";
1979
1976
  ```
1980
1977
 
1981
1978
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/subtasks/
@@ -1989,7 +1986,7 @@ Lead-in and lead-out are durations beside the main time, not times of their own:
1989
1986
  Loading before a round and unloading after it: `leadIn` and `leadOut` are durations beside the main time, drawn faint in the same colour.
1990
1987
 
1991
1988
  ```tsx
1992
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
1989
+ import { useSchedule } from "@umriss-ui/schedule";
1993
1990
  import type { Subtask, Task } from "@umriss-ui/schedule";
1994
1991
 
1995
1992
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2004,8 +2001,9 @@ const ROUNDS: Subtask[] = [
2004
2001
  ];
2005
2002
 
2006
2003
  export default function LeadInAndLeadOut() {
2004
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14, 30)] } });
2007
2005
  return (
2008
- <Schedule ariaLabel="Three rounds of one van" initialDomain={[at(6), at(14, 30)]} height={110}>
2006
+ <Schedule ariaLabel="Three rounds of one van" height={110}>
2009
2007
  <Lane id="van" label="Van FP 214 K" />
2010
2008
  <Subtasks data={ROUNDS} tasks={TOURS} />
2011
2009
  </Schedule>
@@ -2018,7 +2016,7 @@ export default function LeadInAndLeadOut() {
2018
2016
  Every subtask of a task shares its `color`, and the meaning is yours: here the two projects due this week are in the danger colour.
2019
2017
 
2020
2018
  ```tsx
2021
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2019
+ import { useSchedule } from "@umriss-ui/schedule";
2022
2020
  import type { Subtask, Task } from "@umriss-ui/schedule";
2023
2021
 
2024
2022
  /* A subtask whose task is missing from `tasks` is drawn muted: it is on the
@@ -2041,8 +2039,9 @@ const WORK: Subtask[] = [
2041
2039
  ];
2042
2040
 
2043
2041
  export default function TaskColours() {
2042
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(16, 6), at(20, 20)] } });
2044
2043
  return (
2045
- <Schedule ariaLabel="Projects due this week among the rest" initialDomain={[at(16, 6), at(20, 20)]} height={150}>
2044
+ <Schedule ariaLabel="Projects due this week among the rest" height={150}>
2046
2045
  <Lane id="chloe" label="Chloe Durand" />
2047
2046
  <Lane id="eva" label="Eva Novak" />
2048
2047
  <Subtasks data={WORK} tasks={PROJECTS} />
@@ -2056,7 +2055,7 @@ export default function TaskColours() {
2056
2055
  Work items straight from the team's tracker: the person is the lane, the project the task, and `name` is what the tooltip calls each one.
2057
2056
 
2058
2057
  ```tsx
2059
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2058
+ import { useSchedule } from "@umriss-ui/schedule";
2060
2059
  import type { Subtask } from "@umriss-ui/schedule";
2061
2060
 
2062
2061
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -2146,8 +2145,9 @@ const SUBTASKS: Subtask[] = WORK.map((item): Subtask => ({
2146
2145
  const SPRINT_14: readonly [number, number] = [new Date(2026, 2, 9, 6).getTime(), new Date(2026, 2, 21).getTime()];
2147
2146
 
2148
2147
  export default function ASprint() {
2148
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: SPRINT_14 } });
2149
2149
  return (
2150
- <Schedule ariaLabel="Sprint 14, 9 to 20 March" initialDomain={SPRINT_14} height={500} label={(s) => s.name ?? s.id}>
2150
+ <Schedule ariaLabel="Sprint 14, 9 to 20 March" height={500} label={(s) => s.name ?? s.id}>
2151
2151
  {PEOPLE.map((person) => (
2152
2152
  <Lane key={person.id} id={person.id} label={person.name} />
2153
2153
  ))}
@@ -2167,20 +2167,20 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2167
2167
 
2168
2168
  | Prop | Type | Default | Description |
2169
2169
  |---|---|---|---|
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 |
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 75 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 75 more |
2172
2172
 
2173
2173
  ##### `Subtask`
2174
2174
 
2175
2175
  | Prop | Type | Default | Description |
2176
2176
  |---|---|---|---|
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 |
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 72 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 68 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 68 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 68 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 68 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 25 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 22 more |
2184
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
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
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) |
@@ -2189,9 +2189,9 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
2189
2189
 
2190
2190
  | Prop | Type | Default | Description |
2191
2191
  |---|---|---|---|
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 |
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 67 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 67 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 67 more |
2195
2195
 
2196
2196
  ##### Types on this page
2197
2197
 
@@ -2208,7 +2208,7 @@ type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";
2208
2208
  A line of text inside each bar: cut off where the bar is too narrow, left out where it would say nothing, and held at the view's edge while the bar runs on.
2209
2209
 
2210
2210
  ```ts
2211
- import { Schedule } from "@umriss-ui/schedule";
2211
+ import { useSchedule } from "@umriss-ui/schedule";
2212
2212
  ```
2213
2213
 
2214
2214
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/bar-labels/
@@ -2222,7 +2222,7 @@ The text lies on the main time, not on the lead-in. Its colour follows the bar:
2222
2222
  `label` returns the text for each bar: here the work item's name, cut where the bar is narrow and left out of the short reviews.
2223
2223
 
2224
2224
  ```tsx
2225
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2225
+ import { useSchedule } from "@umriss-ui/schedule";
2226
2226
  import type { Subtask, Task } from "@umriss-ui/schedule";
2227
2227
 
2228
2228
  /* Pan into the afternoon: a bar that begins before the view keeps its text at
@@ -2253,10 +2253,10 @@ const WORK: readonly Subtask[] = [
2253
2253
  ];
2254
2254
 
2255
2255
  export default function BarLabels() {
2256
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: DAY } });
2256
2257
  return (
2257
2258
  <Schedule
2258
2259
  ariaLabel="Tuesday, 17 March, with the work items written in"
2259
- initialDomain={DAY}
2260
2260
  height={196}
2261
2261
  label={(subtask) => subtask.name ?? subtask.id}
2262
2262
  >
@@ -2279,7 +2279,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
2279
2279
 
2280
2280
  | Prop | Type | Default | Description |
2281
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
2282
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2284
2283
  | [`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
2284
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -2325,12 +2324,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2325
2324
  | [`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
2325
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2327
2326
 
2327
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
2328
+
2329
+ | Prop | Type | Default | Description |
2330
+ |---|---|---|---|
2331
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
2332
+
2333
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
2334
+
2335
+ | Prop | Type | Default | Description |
2336
+ |---|---|---|---|
2337
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
2338
+
2328
2339
  ### Appearances
2329
2340
 
2330
2341
  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.
2331
2342
 
2332
2343
  ```ts
2333
- import { Subtasks, resolveAppearance } from "@umriss-ui/schedule";
2344
+ import { useSchedule, resolveAppearance } from "@umriss-ui/schedule";
2334
2345
  ```
2335
2346
 
2336
2347
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/appearances/
@@ -2346,7 +2357,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/appearances/
2346
2357
  Work pencilled in but not agreed: `"provisional"` leaves the bar hollow with a dashed outline, never pale like a lead-in.
2347
2358
 
2348
2359
  ```tsx
2349
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2360
+ import { useSchedule } from "@umriss-ui/schedule";
2350
2361
  import type { Subtask, Task } from "@umriss-ui/schedule";
2351
2362
 
2352
2363
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2360,8 +2371,9 @@ const WORK: Subtask[] = [
2360
2371
  ];
2361
2372
 
2362
2373
  export default function Provisional() {
2374
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2363
2375
  return (
2364
- <Schedule ariaLabel="Agreed work above, provisional work below" initialDomain={[at(6, 30), at(11)]} height={144}>
2376
+ <Schedule ariaLabel="Agreed work above, provisional work below" height={144}>
2365
2377
  <Lane id="released" label="Agreed, with lead-in" />
2366
2378
  <Lane id="draft" label="Provisional" />
2367
2379
  <Subtasks data={WORK} tasks={PROJECTS} />
@@ -2375,7 +2387,7 @@ export default function Provisional() {
2375
2387
  A delivery slot the customer booked may not move: `"fixed"` sets a cap inside each end and keeps the face clear for the label.
2376
2388
 
2377
2389
  ```tsx
2378
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2390
+ import { useSchedule } from "@umriss-ui/schedule";
2379
2391
  import type { Subtask, Task } from "@umriss-ui/schedule";
2380
2392
 
2381
2393
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2388,10 +2400,10 @@ const LEGS: Subtask[] = [
2388
2400
  ];
2389
2401
 
2390
2402
  export default function Fixed() {
2403
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2391
2404
  return (
2392
2405
  <Schedule
2393
2406
  ariaLabel="A round that may move above, a booked slot below"
2394
- initialDomain={[at(6, 30), at(11)]}
2395
2407
  height={144}
2396
2408
  label={(leg) => (leg.id === "nailed" ? "T-05 · booked slot" : "T-05")}
2397
2409
  >
@@ -2408,7 +2420,7 @@ export default function Fixed() {
2408
2420
  Another team's work, shown for context: `"muted"` mixes the colour into the surface at full height, so it cannot pass for a lead-in.
2409
2421
 
2410
2422
  ```tsx
2411
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2423
+ import { useSchedule } from "@umriss-ui/schedule";
2412
2424
  import type { Subtask, Task } from "@umriss-ui/schedule";
2413
2425
 
2414
2426
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2422,10 +2434,10 @@ const WORK: Subtask[] = [
2422
2434
  ];
2423
2435
 
2424
2436
  export default function Muted() {
2437
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2425
2438
  return (
2426
2439
  <Schedule
2427
2440
  ariaLabel="Our team's work above, another team's below"
2428
- initialDomain={[at(6, 30), at(11)]}
2429
2441
  height={144}
2430
2442
  label={() => "Checkout migration"}
2431
2443
  >
@@ -2442,7 +2454,7 @@ export default function Muted() {
2442
2454
  `progress` from 0 to 1 draws a rail inside the main time; left out, the bar claims nothing - which is not zero per cent.
2443
2455
 
2444
2456
  ```tsx
2445
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2457
+ import { useSchedule } from "@umriss-ui/schedule";
2446
2458
  import type { Subtask, Task } from "@umriss-ui/schedule";
2447
2459
 
2448
2460
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2456,8 +2468,9 @@ const WORK: Subtask[] = [
2456
2468
  ];
2457
2469
 
2458
2470
  export default function Progress() {
2471
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2459
2472
  return (
2460
- <Schedule ariaLabel="Work claiming nothing above, work at 65 per cent below" initialDomain={[at(6, 30), at(11)]} height={144}>
2473
+ <Schedule ariaLabel="Work claiming nothing above, work at 65 per cent below" height={144}>
2461
2474
  <Lane id="unclaimed" label="Says nothing" />
2462
2475
  <Lane id="started" label="65 per cent done" />
2463
2476
  <Subtasks data={WORK} tasks={PROJECTS} />
@@ -2471,7 +2484,7 @@ export default function Progress() {
2471
2484
  An incident still being worked runs past the view: `"open"` fades the bar at whichever edge it passes, and only there.
2472
2485
 
2473
2486
  ```tsx
2474
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2487
+ import { useSchedule } from "@umriss-ui/schedule";
2475
2488
  import type { Subtask, Task } from "@umriss-ui/schedule";
2476
2489
 
2477
2490
  /* The middle bar is wholly in view and carries no fade; zoom out until it
@@ -2488,8 +2501,9 @@ const WORK: Subtask[] = [
2488
2501
  ];
2489
2502
 
2490
2503
  export default function Open() {
2504
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2491
2505
  return (
2492
- <Schedule ariaLabel="Work running past the view at either edge" initialDomain={[at(6, 30), at(11)]} height={188}>
2506
+ <Schedule ariaLabel="Work running past the view at either edge" height={188}>
2493
2507
  <Lane id="runs-on" label="Runs on" />
2494
2508
  <Lane id="whole" label="Wholly in view" />
2495
2509
  <Lane id="began" label="Began before" />
@@ -2504,7 +2518,7 @@ export default function Open() {
2504
2518
  Muted, fixed and open on one bar stay three statements, each in its own channel; of `"provisional"` and `"fixed"` the later one wins.
2505
2519
 
2506
2520
  ```tsx
2507
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2521
+ import { useSchedule } from "@umriss-ui/schedule";
2508
2522
  import type { Subtask, Task } from "@umriss-ui/schedule";
2509
2523
 
2510
2524
  /* A hollow bar lies on the surface, so its label takes the page's text
@@ -2552,10 +2566,10 @@ const LANES = [
2552
2566
  ];
2553
2567
 
2554
2568
  export default function Combinations() {
2569
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
2555
2570
  return (
2556
2571
  <Schedule
2557
2572
  ariaLabel="Bars carrying several appearances at once"
2558
- initialDomain={[at(6, 30), at(11)]}
2559
2573
  height={230}
2560
2574
  label={(leg) => NAMES[leg.id] ?? leg.id}
2561
2575
  >
@@ -2578,7 +2592,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
2578
2592
 
2579
2593
  | Prop | Type | Default | Description |
2580
2594
  |---|---|---|---|
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
2595
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2583
2596
  | [`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
2597
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -2625,12 +2638,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2625
2638
  | [`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
2639
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2627
2640
 
2641
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
2642
+
2643
+ | Prop | Type | Default | Description |
2644
+ |---|---|---|---|
2645
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
2646
+
2647
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
2648
+
2649
+ | Prop | Type | Default | Description |
2650
+ |---|---|---|---|
2651
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
2652
+
2628
2653
  ### Overlap
2629
2654
 
2630
2655
  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.
2631
2656
 
2632
2657
  ```ts
2633
- import { Subtasks } from "@umriss-ui/schedule";
2658
+ import { useSchedule } from "@umriss-ui/schedule";
2634
2659
  ```
2635
2660
 
2636
2661
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/overlap/
@@ -2644,7 +2669,7 @@ The later bar is offset a few pixels and edged, and the shared time is marked ac
2644
2669
  Both bars stay where their times put them: the later one is offset and edged, and the shared time is marked across the lane.
2645
2670
 
2646
2671
  ```tsx
2647
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2672
+ import { useSchedule } from "@umriss-ui/schedule";
2648
2673
  import type { Subtask, Task } from "@umriss-ui/schedule";
2649
2674
 
2650
2675
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2661,8 +2686,9 @@ const WORK: Subtask[] = [
2661
2686
  ];
2662
2687
 
2663
2688
  export default function DoubleBooking() {
2689
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(8), at(16)] } });
2664
2690
  return (
2665
- <Schedule ariaLabel="Kofi booked on two projects at once" initialDomain={[at(8), at(16)]} height={150}>
2691
+ <Schedule ariaLabel="Kofi booked on two projects at once" height={150}>
2666
2692
  <Lane id="kofi" label="Kofi Mensah" />
2667
2693
  <Lane id="hana" label="Hana Sato" />
2668
2694
  <Subtasks data={WORK} tasks={PROJECTS} />
@@ -2676,7 +2702,7 @@ export default function DoubleBooking() {
2676
2702
  Unloading and the next loading occupy the vehicle too: where a `leadOut` runs into the next `leadIn`, that is an overlap.
2677
2703
 
2678
2704
  ```tsx
2679
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2705
+ import { useSchedule } from "@umriss-ui/schedule";
2680
2706
  import type { Subtask, Task } from "@umriss-ui/schedule";
2681
2707
 
2682
2708
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2693,8 +2719,9 @@ const LEGS: Subtask[] = [
2693
2719
  ];
2694
2720
 
2695
2721
  export default function LeadOutMeetsLeadIn() {
2722
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
2696
2723
  return (
2697
- <Schedule ariaLabel="Two rounds of one van, touching at unloading and loading" initialDomain={[at(6, 30), at(11, 30)]} height={110}>
2724
+ <Schedule ariaLabel="Two rounds of one van, touching at unloading and loading" height={110}>
2698
2725
  <Lane id="fp-402" label="Van FP 402 R" />
2699
2726
  <Subtasks data={LEGS} tasks={TOURS} />
2700
2727
  </Schedule>
@@ -2707,7 +2734,7 @@ export default function LeadOutMeetsLeadIn() {
2707
2734
  Each further bar steps down a little, three levels deep at most, and every one stays inside its own lane.
2708
2735
 
2709
2736
  ```tsx
2710
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2737
+ import { useSchedule } from "@umriss-ui/schedule";
2711
2738
  import type { Subtask, Task } from "@umriss-ui/schedule";
2712
2739
 
2713
2740
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2726,8 +2753,9 @@ const WORK: Subtask[] = [
2726
2753
  ];
2727
2754
 
2728
2755
  export default function ThreeAtOnce() {
2756
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(7), at(14)] } });
2729
2757
  return (
2730
- <Schedule ariaLabel="Priya on three incidents at once" initialDomain={[at(7), at(14)]} height={150}>
2758
+ <Schedule ariaLabel="Priya on three incidents at once" height={150}>
2731
2759
  <Lane id="priya" label="Priya Raman" />
2732
2760
  <Lane id="jonas" label="Jonas Keller" />
2733
2761
  <Subtasks data={WORK} tasks={INCIDENTS} />
@@ -2750,7 +2778,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
2750
2778
 
2751
2779
  | Prop | Type | Default | Description |
2752
2780
  |---|---|---|---|
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
2781
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
2755
2782
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
2756
2783
 
@@ -2794,6 +2821,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
2794
2821
  | [`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
2822
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
2796
2823
 
2824
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
2825
+
2826
+ | Prop | Type | Default | Description |
2827
+ |---|---|---|---|
2828
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
2829
+
2830
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
2831
+
2832
+ | Prop | Type | Default | Description |
2833
+ |---|---|---|---|
2834
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
2835
+
2797
2836
  #### Known limits
2798
2837
 
2799
2838
  - No stacked layout on request yet: an overlap is always drawn offset, as the finding it is.
@@ -2805,7 +2844,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
2805
2844
  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
2845
 
2807
2846
  ```ts
2808
- import { Dependencies } from "@umriss-ui/schedule";
2847
+ import { useSchedule } from "@umriss-ui/schedule";
2809
2848
  ```
2810
2849
 
2811
2850
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/dependencies/
@@ -2821,7 +2860,7 @@ Nothing moves to make a dependency fit. [Ripple](#/ripple) computes the moves th
2821
2860
  Each stop is a subtask, and the drive to the next is a dependency whose `lag` is the driving time.
2822
2861
 
2823
2862
  ```tsx
2824
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
2863
+ import { useSchedule } from "@umriss-ui/schedule";
2825
2864
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
2826
2865
 
2827
2866
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -2847,8 +2886,9 @@ const DRIVES: Dependency[] = [
2847
2886
  ];
2848
2887
 
2849
2888
  export default function StopsOfATour() {
2889
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7), at(10)] } });
2850
2890
  return (
2851
- <Schedule ariaLabel="Two morning tours with their stops" initialDomain={[at(7), at(10)]} height={150}>
2891
+ <Schedule ariaLabel="Two morning tours with their stops" height={150}>
2852
2892
  <Lane id="fp-214" label="Van FP 214 K" />
2853
2893
  <Lane id="fp-377" label="E-van FP 377 K" />
2854
2894
  <Dependencies data={DRIVES} />
@@ -2863,7 +2903,7 @@ export default function StopsOfATour() {
2863
2903
  By default the lag runs from the end of unloading to the start of loading; `leaves` and `arrives` set to `"main"` skip both.
2864
2904
 
2865
2905
  ```tsx
2866
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
2906
+ import { useSchedule } from "@umriss-ui/schedule";
2867
2907
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
2868
2908
 
2869
2909
  /* The anchor decides whether the dependency is violated, so it should say
@@ -2890,8 +2930,9 @@ const HANDOVERS: Dependency[] = [
2890
2930
  ];
2891
2931
 
2892
2932
  export default function Anchors() {
2933
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(16, 30)] } });
2893
2934
  return (
2894
- <Schedule ariaLabel="Two handovers with different anchors" initialDomain={[at(6, 30), at(16, 30)]} height={150}>
2935
+ <Schedule ariaLabel="Two handovers with different anchors" height={150}>
2895
2936
  <Lane id="truck" label="Truck FP 520 E" />
2896
2937
  <Lane id="van" label="Van FP 290 E" />
2897
2938
  <Dependencies data={HANDOVERS} />
@@ -2907,7 +2948,7 @@ export default function Anchors() {
2907
2948
 
2908
2949
  ```tsx
2909
2950
  import { Stack, Text } from "@umriss-ui/core";
2910
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
2951
+ import { useSchedule } from "@umriss-ui/schedule";
2911
2952
  import type { Subtask, Task, Dependency, DependencyAttachment } from "@umriss-ui/schedule";
2912
2953
 
2913
2954
  /* Picture only: `attach` changes no finding. Within one lane there is no
@@ -2933,12 +2974,13 @@ const HANDOVERS: Dependency[] = [
2933
2974
  ];
2934
2975
 
2935
2976
  function Variant({ attach }: { attach: DependencyAttachment }) {
2977
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
2936
2978
  return (
2937
2979
  <Stack gap={1}>
2938
2980
  <Text size="xs" tone="muted" mono>
2939
2981
  attach=&quot;{attach}&quot;
2940
2982
  </Text>
2941
- <Schedule ariaLabel={`A release through three engineers, lines from the ${attach}`} initialDomain={[at(6, 30), at(13, 30)]} height={188} attach={attach}>
2983
+ <Schedule ariaLabel={`A release through three engineers, lines from the ${attach}`} height={188} attach={attach}>
2942
2984
  <Lane id="priya" label="Priya Raman" />
2943
2985
  <Lane id="ada" label="Ada Mwangi" />
2944
2986
  <Lane id="sam" label="Sam Okafor" />
@@ -2965,7 +3007,7 @@ export default function Attach() {
2965
3007
 
2966
3008
  ```tsx
2967
3009
  import { Stack, Text } from "@umriss-ui/core";
2968
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
3010
+ import { useSchedule } from "@umriss-ui/schedule";
2969
3011
  import type { Subtask, Task, Dependency, DependencyEnds } from "@umriss-ui/schedule";
2970
3012
 
2971
3013
  /* Picture only, like `route` and `attach`; a single dependency may say
@@ -2988,12 +3030,13 @@ const HANDOVERS: Dependency[] = [
2988
3030
  ];
2989
3031
 
2990
3032
  function Variant({ ends }: { ends: DependencyEnds }) {
3033
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
2991
3034
  return (
2992
3035
  <Stack gap={1}>
2993
3036
  <Text size="xs" tone="muted" mono>
2994
3037
  ends=&quot;{ends}&quot;
2995
3038
  </Text>
2996
- <Schedule ariaLabel={`Design, build and test, ends ${ends}`} initialDomain={[at(6, 30), at(11, 30)]} height={188} attach="nearest" ends={ends}>
3039
+ <Schedule ariaLabel={`Design, build and test, ends ${ends}`} height={188} attach="nearest" ends={ends}>
2997
3040
  <Lane id="noah" label="Noah Fischer" />
2998
3041
  <Lane id="arjun" label="Arjun Mehta" />
2999
3042
  <Lane id="eva" label="Eva Novak" />
@@ -3019,7 +3062,7 @@ export default function Ends() {
3019
3062
  The transfer at the depot takes forty minutes and the plan leaves twenty-five: the line turns dashed in the finding colour, and nothing moves.
3020
3063
 
3021
3064
  ```tsx
3022
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
3065
+ import { useSchedule } from "@umriss-ui/schedule";
3023
3066
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
3024
3067
 
3025
3068
  /* Whether the van waits or the truck leaves earlier is the planner's
@@ -3038,8 +3081,9 @@ const LEGS: Subtask[] = [
3038
3081
  const HANDOVERS: Dependency[] = [{ id: "transfer", from: "line-haul", to: "round", lag: min(40) }];
3039
3082
 
3040
3083
  export default function ViolatedDependency() {
3084
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
3041
3085
  return (
3042
- <Schedule ariaLabel="A handover from the truck to the van that is too short" initialDomain={[at(6, 30), at(11)]} height={150}>
3086
+ <Schedule ariaLabel="A handover from the truck to the van that is too short" height={150}>
3043
3087
  <Lane id="truck" label="Truck FP 118 R" />
3044
3088
  <Lane id="van" label="Van FP 214 K" />
3045
3089
  <Dependencies data={HANDOVERS} />
@@ -3054,7 +3098,7 @@ export default function ViolatedDependency() {
3054
3098
  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
3099
 
3056
3100
  ```tsx
3057
- import { Lane, Schedule, Subtasks, Dependencies } from "@umriss-ui/schedule";
3101
+ import { useSchedule } from "@umriss-ui/schedule";
3058
3102
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
3059
3103
 
3060
3104
  /* Picture only, like the schedule's own `attach` and `ends`: the finding
@@ -3080,8 +3124,9 @@ const HANDOVERS: Dependency[] = [
3080
3124
  ];
3081
3125
 
3082
3126
  export default function OneHandoverDrawnItsOwnWay() {
3127
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(12, 30)] } });
3083
3128
  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">
3129
+ <Schedule ariaLabel="Design, build, test and a sign-off drawn its own way" height={188} attach="nearest" ends="none">
3085
3130
  <Lane id="noah" label="Noah Fischer" />
3086
3131
  <Lane id="arjun" label="Arjun Mehta" />
3087
3132
  <Lane id="eva" label="Eva Novak" />
@@ -3173,7 +3218,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
3173
3218
  The shape of a dependency's line: a curve that leaves and arrives forwards, a straight line, or orthogonal segments. Set it for the schedule, or for one dependency.
3174
3219
 
3175
3220
  ```ts
3176
- import { Dependencies } from "@umriss-ui/schedule";
3221
+ import { useSchedule } from "@umriss-ui/schedule";
3177
3222
  ```
3178
3223
 
3179
3224
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/routes/
@@ -3185,7 +3230,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/routes/
3185
3230
  The default: each line leaves forwards and arrives forwards, so it shows which way the work goes, even when it loops back up.
3186
3231
 
3187
3232
  ```tsx
3188
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3233
+ import { useSchedule } from "@umriss-ui/schedule";
3189
3234
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
3190
3235
 
3191
3236
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -3207,8 +3252,9 @@ const HANDOVERS: Dependency[] = [
3207
3252
  ];
3208
3253
 
3209
3254
  export default function Curve() {
3255
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
3210
3256
  return (
3211
- <Schedule ariaLabel="Search results, from layout to polish, curved lines" initialDomain={[at(6, 30), at(13, 30)]} height={188}>
3257
+ <Schedule ariaLabel="Search results, from layout to polish, curved lines" height={188}>
3212
3258
  <Lane id="noah" label="Noah Fischer" />
3213
3259
  <Lane id="chloe" label="Chloe Durand" />
3214
3260
  <Lane id="eva" label="Eva Novak" />
@@ -3224,7 +3270,7 @@ export default function Curve() {
3224
3270
  `route="straight"` draws the shortest line between the two ends, calm in a plan read from a distance.
3225
3271
 
3226
3272
  ```tsx
3227
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3273
+ import { useSchedule } from "@umriss-ui/schedule";
3228
3274
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
3229
3275
 
3230
3276
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -3246,8 +3292,9 @@ const HANDOVERS: Dependency[] = [
3246
3292
  ];
3247
3293
 
3248
3294
  export default function Straight() {
3295
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
3249
3296
  return (
3250
- <Schedule ariaLabel="Search results, from layout to polish, straight lines" initialDomain={[at(6, 30), at(13, 30)]} height={188} route="straight" attach="nearest">
3297
+ <Schedule ariaLabel="Search results, from layout to polish, straight lines" height={188} route="straight" attach="nearest">
3251
3298
  <Lane id="noah" label="Noah Fischer" />
3252
3299
  <Lane id="chloe" label="Chloe Durand" />
3253
3300
  <Lane id="eva" label="Eva Novak" />
@@ -3263,7 +3310,7 @@ export default function Straight() {
3263
3310
  `route="orthogonal"` leaves the bar, runs across and comes in again, like a flow chart.
3264
3311
 
3265
3312
  ```tsx
3266
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3313
+ import { useSchedule } from "@umriss-ui/schedule";
3267
3314
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
3268
3315
 
3269
3316
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -3285,8 +3332,9 @@ const HANDOVERS: Dependency[] = [
3285
3332
  ];
3286
3333
 
3287
3334
  export default function Orthogonal() {
3335
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
3288
3336
  return (
3289
- <Schedule ariaLabel="Search results, from layout to polish, orthogonal lines" initialDomain={[at(6, 30), at(13, 30)]} height={188} route="orthogonal" attach="nearest">
3337
+ <Schedule ariaLabel="Search results, from layout to polish, orthogonal lines" height={188} route="orthogonal" attach="nearest">
3290
3338
  <Lane id="noah" label="Noah Fischer" />
3291
3339
  <Lane id="chloe" label="Chloe Durand" />
3292
3340
  <Lane id="eva" label="Eva Novak" />
@@ -3302,7 +3350,7 @@ export default function Orthogonal() {
3302
3350
  A `route` on a single dependency overrides the schedule's: here only the loop back up to the designer is drawn orthogonal.
3303
3351
 
3304
3352
  ```tsx
3305
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3353
+ import { useSchedule } from "@umriss-ui/schedule";
3306
3354
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
3307
3355
 
3308
3356
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -3325,8 +3373,9 @@ const HANDOVERS: Dependency[] = [
3325
3373
  ];
3326
3374
 
3327
3375
  export default function PerDependency() {
3376
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
3328
3377
  return (
3329
- <Schedule ariaLabel="Search results, from layout to polish, straight lines but one" initialDomain={[at(6, 30), at(13, 30)]} height={188} route="straight" attach="nearest">
3378
+ <Schedule ariaLabel="Search results, from layout to polish, straight lines but one" height={188} route="straight" attach="nearest">
3330
3379
  <Lane id="noah" label="Noah Fischer" />
3331
3380
  <Lane id="chloe" label="Chloe Durand" />
3332
3381
  <Lane id="eva" label="Eva Novak" />
@@ -3347,7 +3396,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
3347
3396
 
3348
3397
  | Prop | Type | Default | Description |
3349
3398
  |---|---|---|---|
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
3399
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
3352
3400
  | [`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
3401
  | [`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. |
@@ -3408,6 +3456,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
3408
3456
  | [`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
3457
  | [`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
3458
 
3459
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
3460
+
3461
+ | Prop | Type | Default | Description |
3462
+ |---|---|---|---|
3463
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
3464
+
3465
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
3466
+
3467
+ | Prop | Type | Default | Description |
3468
+ |---|---|---|---|
3469
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
3470
+
3411
3471
  ## Time
3412
3472
 
3413
3473
  The axis the work stands on: the calendar, blocked time, the now line, and moving through it.
@@ -3417,7 +3477,7 @@ The axis the work stands on: the calendar, blocked time, the now line, and movin
3417
3477
  A day band that names the days and a time band whose ticks follow the zoom, from quarter hours to days. A working calendar cuts the hours nobody works out of the axis.
3418
3478
 
3419
3479
  ```ts
3420
- import { Schedule } from "@umriss-ui/schedule";
3480
+ import { useSchedule } from "@umriss-ui/schedule";
3421
3481
  ```
3422
3482
 
3423
3483
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/time-axis/
@@ -3431,7 +3491,7 @@ The calendar is a list of intervals in which time counts, nothing more; deriving
3431
3491
  A week of the on-call rota: the day band names each day, and the time band's step follows the zoom.
3432
3492
 
3433
3493
  ```tsx
3434
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3494
+ import { useSchedule } from "@umriss-ui/schedule";
3435
3495
  import type { Subtask, Task } from "@umriss-ui/schedule";
3436
3496
 
3437
3497
  /* Data from the operations world, written out here so the example runs on its own. */
@@ -3486,8 +3546,9 @@ const DUTIES: Subtask[] = ONCALL.map((duty) => ({ id: duty.id, task: duty.rotati
3486
3546
  const WEEK: readonly [number, number] = [new Date(2026, 2, 16).getTime(), new Date(2026, 2, 23, 12).getTime()];
3487
3547
 
3488
3548
  export default function AWeek() {
3549
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: WEEK } });
3489
3550
  return (
3490
- <Schedule ariaLabel="On-call rota, 16 to 23 March" initialDomain={WEEK} height={420}>
3551
+ <Schedule ariaLabel="On-call rota, 16 to 23 March" height={420}>
3491
3552
  {ENGINEERS.map((engineer) => (
3492
3553
  <Lane key={engineer.id} id={engineer.id} label={engineer.name} />
3493
3554
  ))}
@@ -3503,7 +3564,7 @@ The depot works 06:00 to 22:00: `calendar` lists those hours, the nights leave t
3503
3564
 
3504
3565
  ```tsx
3505
3566
  import { useState } from "react";
3506
- import { Lane, Schedule, Subtasks, Dependencies, applyIntent } from "@umriss-ui/schedule";
3567
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
3507
3568
  import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
3508
3569
 
3509
3570
  /* Three days in the width of two; a dotted line marks each seam. Deriving the
@@ -3527,11 +3588,11 @@ const HANDOVERS: Dependency[] = [
3527
3588
  ];
3528
3589
 
3529
3590
  export default function WorkingCalendar() {
3591
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(0, 6), at(2, 22)] } });
3530
3592
  const [legs, setLegs] = useState<readonly Subtask[]>(LEGS);
3531
3593
  return (
3532
3594
  <Schedule
3533
3595
  ariaLabel="Three days at East Gate depot, nights removed"
3534
- initialDomain={[at(0, 6), at(2, 22)]}
3535
3596
  calendar={OPENING_HOURS}
3536
3597
  height={220}
3537
3598
  intents={["move"]}
@@ -3552,7 +3613,7 @@ export default function WorkingCalendar() {
3552
3613
  Weekdays 09:00 to 17:00 as the `calendar`: a two-week sprint reads as ten working days, with the evenings and the weekend gone.
3553
3614
 
3554
3615
  ```tsx
3555
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
3616
+ import { useSchedule } from "@umriss-ui/schedule";
3556
3617
 
3557
3618
  /* Data from the planning world, written out here so the example runs on its own. */
3558
3619
 
@@ -3639,10 +3700,10 @@ const WEB_TEAM = PEOPLE.filter((person) => person.team === "Web");
3639
3700
  const ON_THE_WEB_TEAM = new Set(WEB_TEAM.map((person) => person.id));
3640
3701
 
3641
3702
  export default function OfficeHours() {
3703
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(9, 9), at(20, 17)] } });
3642
3704
  return (
3643
3705
  <Schedule
3644
3706
  ariaLabel="Sprint 14 of the web team, office hours only"
3645
- initialDomain={[at(9, 9), at(20, 17)]}
3646
3707
  calendar={OFFICE_HOURS}
3647
3708
  height={280}
3648
3709
  label={(item) => item.name ?? item.id}
@@ -3670,7 +3731,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
3670
3731
 
3671
3732
  | Prop | Type | Default | Description |
3672
3733
  |---|---|---|---|
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
3734
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
3675
3735
  | [`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
3736
  | [`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)). |
@@ -3739,12 +3799,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
3739
3799
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
3740
3800
  | [`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
3801
 
3802
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
3803
+
3804
+ | Prop | Type | Default | Description |
3805
+ |---|---|---|---|
3806
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
3807
+
3808
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
3809
+
3810
+ | Prop | Type | Default | Description |
3811
+ |---|---|---|---|
3812
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
3813
+
3742
3814
  ### Blocked time
3743
3815
 
3744
3816
  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.
3745
3817
 
3746
3818
  ```ts
3747
- import { BlockedTimes, findings } from "@umriss-ui/schedule";
3819
+ import { useSchedule, findings } from "@umriss-ui/schedule";
3748
3820
  ```
3749
3821
 
3750
3822
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/
@@ -3761,7 +3833,7 @@ A person on leave, a server in maintenance: `BlockedTimes` hatches that time on
3761
3833
 
3762
3834
  ```tsx
3763
3835
  import { Stack, Text } from "@umriss-ui/core";
3764
- import { BlockedTimes, Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
3836
+ import { findings, useSchedule } from "@umriss-ui/schedule";
3765
3837
  import type { BlockedTime, Subtask, Task } from "@umriss-ui/schedule";
3766
3838
 
3767
3839
  /* Blocked time is data beside the work: one list, each interval naming its
@@ -3805,11 +3877,12 @@ const time = (instant: number) =>
3805
3877
  new Date(instant).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit" });
3806
3878
 
3807
3879
  export default function Leave() {
3880
+ const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(0, 6), day(5, 0)] } });
3808
3881
  const found = findings(WORK, [], BLOCKED);
3809
3882
 
3810
3883
  return (
3811
3884
  <Stack gap={3}>
3812
- <Schedule ariaLabel="Team plan, week of 16 March" initialDomain={[day(0, 6), day(5, 0)]} height={240}>
3885
+ <Schedule ariaLabel="Team plan, week of 16 March" height={240}>
3813
3886
  {PEOPLE.map((person) => (
3814
3887
  <Lane key={person.id} id={person.id} label={person.label} />
3815
3888
  ))}
@@ -3837,7 +3910,7 @@ A drag does not put work into blocked time: the ghost stays where it was allowed
3837
3910
  ```tsx
3838
3911
  import { useState } from "react";
3839
3912
  import { Stack, Text } from "@umriss-ui/core";
3840
- import { BlockedTimes, Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
3913
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
3841
3914
  import type { BlockedTime, Intent, Subtask, Task } from "@umriss-ui/schedule";
3842
3915
 
3843
3916
  /* Blocked time is part of where a subtask may go. Drag the review towards
@@ -3862,6 +3935,7 @@ const START: readonly Subtask[] = [
3862
3935
  const BLOCKED: readonly BlockedTime[] = [{ id: "anna-leave", lane: "anna", from: day(2, 0), to: day(4, 0), label: "Leave" }];
3863
3936
 
3864
3937
  export default function DragAround() {
3938
+ const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(0, 6), day(5, 0)] } });
3865
3939
  const [work, setWork] = useState<readonly Subtask[]>(START);
3866
3940
  const [last, setLast] = useState("Drag the design review into Anna's leave");
3867
3941
 
@@ -3875,7 +3949,6 @@ export default function DragAround() {
3875
3949
  <Stack gap={3}>
3876
3950
  <Schedule
3877
3951
  ariaLabel="Two people, one on leave"
3878
- initialDomain={[day(0, 6), day(5, 0)]}
3879
3952
  height={150}
3880
3953
  intents={["move", "lane"]}
3881
3954
  onIntent={onIntent}
@@ -3899,7 +3972,7 @@ The team's holidays, sick days and training become `BlockedTimes`, and [`finding
3899
3972
 
3900
3973
  ```tsx
3901
3974
  import { Stack, Text } from "@umriss-ui/core";
3902
- import { BlockedTimes, Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
3975
+ import { findings, useSchedule } from "@umriss-ui/schedule";
3903
3976
  import type { BlockedTime } from "@umriss-ui/schedule";
3904
3977
 
3905
3978
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -4006,10 +4079,11 @@ const NAME = new Map(WORK.map((item) => [item.id, item.name]));
4006
4079
  const PERSON = new Map(PEOPLE.map((person) => [person.id, person.name]));
4007
4080
 
4008
4081
  export default function TeamLeave() {
4082
+ const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: SPRINT_14 } });
4009
4083
  const clashes = findings(WORK, [], BLOCKED).inBlockedTime;
4010
4084
  return (
4011
4085
  <Stack gap={3}>
4012
- <Schedule ariaLabel="Sprint 14 with the team's leave" initialDomain={SPRINT_14} height={500}>
4086
+ <Schedule ariaLabel="Sprint 14 with the team's leave" height={500}>
4013
4087
  {PEOPLE.map((person) => (
4014
4088
  <Lane key={person.id} id={person.id} label={person.name} />
4015
4089
  ))}
@@ -4057,7 +4131,7 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
4057
4131
  A line across the lanes at the present moment, so that whatever should have happened by now stands to its left. It follows the clock, or stays at a fixed instant for a replay.
4058
4132
 
4059
4133
  ```ts
4060
- import { Schedule } from "@umriss-ui/schedule";
4134
+ import { useSchedule } from "@umriss-ui/schedule";
4061
4135
  ```
4062
4136
 
4063
4137
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/now-line/
@@ -4069,7 +4143,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/now-line/
4069
4143
  Pass an instant to `now` and a line crosses the lanes there: a dispatcher at 10:30 sees which tours are under way.
4070
4144
 
4071
4145
  ```tsx
4072
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4146
+ import { useSchedule } from "@umriss-ui/schedule";
4073
4147
  import type { Subtask, Task } from "@umriss-ui/schedule";
4074
4148
 
4075
4149
  /* Data from the logistics world, written out here so the example runs on its own. */
@@ -4216,8 +4290,9 @@ const ROUNDS: Subtask[] = TOURS.map((tour) => ({
4216
4290
  }));
4217
4291
 
4218
4292
  export default function NowLine() {
4293
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
4219
4294
  return (
4220
- <Schedule ariaLabel="Today's tours, with the present" initialDomain={[at(5, 30), at(18)]} height={420} now={NOW}>
4295
+ <Schedule ariaLabel="Today's tours, with the present" height={420} now={NOW}>
4221
4296
  {VEHICLES.map((vehicle) => (
4222
4297
  <Lane key={vehicle.id} id={vehicle.id} label={vehicle.plate} />
4223
4298
  ))}
@@ -4232,7 +4307,7 @@ export default function NowLine() {
4232
4307
  `now` on its own reads the clock and moves on by the minute, so a plan left open on a wall screen stays true.
4233
4308
 
4234
4309
  ```tsx
4235
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4310
+ import { useSchedule } from "@umriss-ui/schedule";
4236
4311
  import type { Subtask, Task } from "@umriss-ui/schedule";
4237
4312
 
4238
4313
  const HOUR = 60 * 60_000;
@@ -4255,8 +4330,9 @@ const DUTIES: Subtask[] = [
4255
4330
  ];
4256
4331
 
4257
4332
  export default function FollowTheClock() {
4333
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [current - 6 * HOUR, current + 6 * HOUR] } });
4258
4334
  return (
4259
- <Schedule ariaLabel="Today's on-call rota, with the present" initialDomain={[current - 6 * HOUR, current + 6 * HOUR]} height={188} now>
4335
+ <Schedule ariaLabel="Today's on-call rota, with the present" height={188} now>
4260
4336
  <Lane id="ada" label="Ada Mwangi" />
4261
4337
  <Lane id="tomasz" label="Tomasz Nowak" />
4262
4338
  <Lane id="leila" label="Leila Haddad" />
@@ -4276,7 +4352,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
4276
4352
 
4277
4353
  | Prop | Type | Default | Description |
4278
4354
  |---|---|---|---|
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
4355
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4281
4356
  | [`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
4357
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -4320,19 +4395,31 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4320
4395
  | [`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
4396
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
4322
4397
 
4398
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
4399
+
4400
+ | Prop | Type | Default | Description |
4401
+ |---|---|---|---|
4402
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
4403
+
4404
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
4405
+
4406
+ | Prop | Type | Default | Description |
4407
+ |---|---|---|---|
4408
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
4409
+
4323
4410
  ### Pan and zoom
4324
4411
 
4325
4412
  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.
4326
4413
 
4327
4414
  ```ts
4328
- import { Schedule } from "@umriss-ui/schedule";
4415
+ import { useSchedule } from "@umriss-ui/schedule";
4329
4416
  ```
4330
4417
 
4331
4418
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/pan-and-zoom/
4332
4419
 
4333
4420
  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.
4334
4421
 
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).
4422
+ 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)). The span is part of the schedule's view, which `onViewChange` reports at most once per frame - see [View](#/view) and [Linked schedules](#/linked-schedules).
4336
4423
 
4337
4424
  #### Examples
4338
4425
 
@@ -4341,7 +4428,7 @@ Nothing is reported and no intent is raised: pan and zoom change the visible spa
4341
4428
  Drag, wheel, Ctrl with the wheel and a pinch need nothing switched on; `zoomLimits` only bounds the span, here two hours to two days.
4342
4429
 
4343
4430
  ```tsx
4344
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4431
+ import { useSchedule } from "@umriss-ui/schedule";
4345
4432
  import type { Subtask, Task } from "@umriss-ui/schedule";
4346
4433
 
4347
4434
  /* Without `zoomLimits` the span may run from an hour to twenty-eight days. */
@@ -4369,10 +4456,10 @@ const WORK: Subtask[] = [
4369
4456
  ];
4370
4457
 
4371
4458
  export default function PanAndZoom() {
4459
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
4372
4460
  return (
4373
4461
  <Schedule
4374
4462
  ariaLabel="A plan to pan and zoom"
4375
- initialDomain={[at(5, 30), at(18)]}
4376
4463
  height={196}
4377
4464
  zoomLimits={{ min: 2 * HOUR, max: 48 * HOUR }}
4378
4465
  >
@@ -4400,7 +4487,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
4400
4487
 
4401
4488
  | Prop | Type | Default | Description |
4402
4489
  |---|---|---|---|
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
4490
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4405
4491
  | [`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
4492
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -4445,6 +4531,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
4445
4531
  | [`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
4532
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
4447
4533
 
4534
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
4535
+
4536
+ | Prop | Type | Default | Description |
4537
+ |---|---|---|---|
4538
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
4539
+
4540
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
4541
+
4542
+ | Prop | Type | Default | Description |
4543
+ |---|---|---|---|
4544
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
4545
+
4448
4546
  ## Reading
4449
4547
 
4450
4548
  What a planner takes out of the picture: what is selected, what the pointer is on, and what a second view makes of it.
@@ -4454,7 +4552,7 @@ What a planner takes out of the picture: what is selected, what the pointer is o
4454
4552
  A click on a bar selects its whole task on every lane, so a planner sees every part of one job at once (also called highlighting). Reach for it when a list or a detail panel beside the plan should follow what was picked.
4455
4553
 
4456
4554
  ```ts
4457
- import { Schedule } from "@umriss-ui/schedule";
4555
+ import { useSchedule } from "@umriss-ui/schedule";
4458
4556
  ```
4459
4557
 
4460
4558
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/selection/
@@ -4472,7 +4570,7 @@ The selection also steps the rest back, so a planner sees at once what they are
4472
4570
  Click a leg and the whole consignment is outlined on every vehicle: the leg you clicked in full colour, its other legs halfway to grey, all other work grey. Click the empty plot to clear it.
4473
4571
 
4474
4572
  ```tsx
4475
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4573
+ import { useSchedule } from "@umriss-ui/schedule";
4476
4574
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
4477
4575
 
4478
4576
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -4497,8 +4595,9 @@ const TRANSFERS: readonly Dependency[] = [
4497
4595
  ];
4498
4596
 
4499
4597
  export default function SelectATask() {
4598
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(13, 30)] } });
4500
4599
  return (
4501
- <Schedule ariaLabel="Two consignments at North depot" initialDomain={[at(5, 30), at(13, 30)]} height={140}>
4600
+ <Schedule ariaLabel="Two consignments at North depot" height={140}>
4502
4601
  <Lane id="truck-118" label="Truck FP 118 R" />
4503
4602
  <Lane id="van-214" label="Van FP 214 K" />
4504
4603
  <Dependencies data={TRANSFERS} />
@@ -4515,7 +4614,7 @@ Hold `selectedTask` in your state to show the selection elsewhere; `onSelectedTa
4515
4614
  ```tsx
4516
4615
  import { useState } from "react";
4517
4616
  import { Stack, Text } from "@umriss-ui/core";
4518
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4617
+ import { useSchedule } from "@umriss-ui/schedule";
4519
4618
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
4520
4619
 
4521
4620
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -4554,6 +4653,7 @@ const TRANSFERS: readonly Dependency[] = [
4554
4653
  ];
4555
4654
 
4556
4655
  export default function ControlTheSelection() {
4656
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY } });
4557
4657
  const [selected, setSelected] = useState<string | null>("c-2043");
4558
4658
  const [leg, setLeg] = useState<string | null>(null);
4559
4659
  const consignment = CONSIGNMENTS.find((c) => c.id === selected);
@@ -4562,7 +4662,6 @@ export default function ControlTheSelection() {
4562
4662
  <Stack gap={3}>
4563
4663
  <Schedule
4564
4664
  ariaLabel="Legs at North depot on Tuesday, 17 March"
4565
- initialDomain={DAY}
4566
4665
  height={284}
4567
4666
  selectedTask={selected}
4568
4667
  onSelectedTaskChange={(task, subtask) => {
@@ -4607,7 +4706,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
4607
4706
 
4608
4707
  | Prop | Type | Default | Description |
4609
4708
  |---|---|---|---|
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
4709
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
4612
4710
  | [`selectedTask`](#/schedule/ScheduleProps-selectedTask) | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself. |
4613
4711
  | [`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. |
@@ -4668,6 +4766,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
4668
4766
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
4669
4767
  | [`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
4768
 
4769
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
4770
+
4771
+ | Prop | Type | Default | Description |
4772
+ |---|---|---|---|
4773
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
4774
+
4775
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
4776
+
4777
+ | Prop | Type | Default | Description |
4778
+ |---|---|---|---|
4779
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
4780
+
4671
4781
  #### Known limits
4672
4782
 
4673
4783
  - One task at a time: there is no multiple selection.
@@ -4679,7 +4789,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
4679
4789
  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
4790
 
4681
4791
  ```ts
4682
- import { Schedule } from "@umriss-ui/schedule";
4792
+ import { useSchedule } from "@umriss-ui/schedule";
4683
4793
  ```
4684
4794
 
4685
4795
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/interactions/
@@ -4697,7 +4807,7 @@ While `onInteraction` is set, a right-click opens no browser menu: your applicat
4697
4807
  ```tsx
4698
4808
  import { useState } from "react";
4699
4809
  import { Stack, Text } from "@umriss-ui/core";
4700
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
4810
+ import { useSchedule } from "@umriss-ui/schedule";
4701
4811
  import type { Dependency, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
4702
4812
 
4703
4813
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -4730,13 +4840,13 @@ const describe = (interaction: ScheduleInteraction): string => {
4730
4840
  };
4731
4841
 
4732
4842
  export default function ReportThePointer() {
4843
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
4733
4844
  const [last, setLast] = useState("Move the pointer over the plan");
4734
4845
 
4735
4846
  return (
4736
4847
  <Stack gap={3}>
4737
4848
  <Schedule
4738
4849
  ariaLabel="One consignment on a truck and a van"
4739
- initialDomain={[at(5, 30), at(18)]}
4740
4850
  height={152}
4741
4851
  onInteraction={(interaction) => setLast(describe(interaction))}
4742
4852
  >
@@ -4760,7 +4870,7 @@ On a `contextmenu` interaction, open `ContextMenu` from @umriss-ui/core at `clie
4760
4870
  ```tsx
4761
4871
  import { useState } from "react";
4762
4872
  import { ContextMenu, MenuItem } from "@umriss-ui/core";
4763
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
4873
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
4764
4874
  import type { ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
4765
4875
 
4766
4876
  /* Data from the operations world, written out here so the example runs on its own. */
@@ -4823,6 +4933,7 @@ const START: readonly Subtask[] = ONCALL.map((duty) => ({
4823
4933
  }));
4824
4934
 
4825
4935
  export default function OpenAContextMenu() {
4936
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 6), day(23, 12)] } });
4826
4937
  const [rota, setRota] = useState<readonly Subtask[]>(START);
4827
4938
  const [menu, setMenu] = useState<{ interaction: ScheduleInteraction; duty: Subtask } | null>(null);
4828
4939
 
@@ -4836,7 +4947,6 @@ export default function OpenAContextMenu() {
4836
4947
  <>
4837
4948
  <Schedule
4838
4949
  ariaLabel="On-call rota, 16 to 23 March"
4839
- initialDomain={[day(16, 6), day(23, 12)]}
4840
4950
  height={420}
4841
4951
  onInteraction={onInteraction}
4842
4952
  >
@@ -4912,7 +5022,7 @@ type ScheduleHit =
4912
5022
  Names what the pointer rests on - a subtask with its times and findings, a dependency with its lag - without a click (also called a hover card). Replace its content when your application knows more than the schedule.
4913
5023
 
4914
5024
  ```ts
4915
- import { Schedule } from "@umriss-ui/schedule";
5025
+ import { useSchedule } from "@umriss-ui/schedule";
4916
5026
  ```
4917
5027
 
4918
5028
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/tooltip/
@@ -4928,7 +5038,7 @@ The keyboard's active subtask shows the same tooltip as the pointer.
4928
5038
  Rest the pointer on a leg: the tooltip names the consignment, its times, loading and unloading, and every finding on it.
4929
5039
 
4930
5040
  ```tsx
4931
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5041
+ import { useSchedule } from "@umriss-ui/schedule";
4932
5042
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
4933
5043
 
4934
5044
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -4960,8 +5070,9 @@ const TRANSFERS: readonly Dependency[] = [
4960
5070
  ];
4961
5071
 
4962
5072
  export default function DefaultTooltip() {
5073
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
4963
5074
  return (
4964
- <Schedule ariaLabel="Three vehicles with the default tooltip" initialDomain={[at(5, 30), at(18)]} height={196}>
5075
+ <Schedule ariaLabel="Three vehicles with the default tooltip" height={196}>
4965
5076
  {VEHICLES.map((vehicle) => (
4966
5077
  <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
4967
5078
  ))}
@@ -4978,7 +5089,7 @@ Pass `tooltip` a function to show what only your application knows - here the lo
4978
5089
 
4979
5090
  ```tsx
4980
5091
  import { Badge, Stack, Text } from "@umriss-ui/core";
4981
- import { Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5092
+ import { useSchedule } from "@umriss-ui/schedule";
4982
5093
  import type { Dependency, ScheduleTooltipTarget, Subtask, Task } from "@umriss-ui/schedule";
4983
5094
 
4984
5095
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -5030,10 +5141,10 @@ function LoadTooltip({ target }: { target: ScheduleTooltipTarget }) {
5030
5141
  }
5031
5142
 
5032
5143
  export default function OwnTooltip() {
5144
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
5033
5145
  return (
5034
5146
  <Schedule
5035
5147
  ariaLabel="Three vehicles with the load in the tooltip"
5036
- initialDomain={[at(5, 30), at(18)]}
5037
5148
  height={196}
5038
5149
  tooltip={(target) => <LoadTooltip target={target} />}
5039
5150
  >
@@ -5054,7 +5165,7 @@ Set `tooltip={false}` when a panel of your own shows the item under the pointer,
5054
5165
  ```tsx
5055
5166
  import { useState } from "react";
5056
5167
  import { Badge, Card, CardBody, Stack, Text } from "@umriss-ui/core";
5057
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5168
+ import { useSchedule } from "@umriss-ui/schedule";
5058
5169
  import type { ScheduleInteraction } from "@umriss-ui/schedule";
5059
5170
 
5060
5171
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -5141,6 +5252,7 @@ const WEB = PEOPLE.filter((person) => person.team === "Web");
5141
5252
  const WEB_WORK = WORK.filter((item) => WEB.some((person) => person.id === item.lane));
5142
5253
 
5143
5254
  export default function DetailPanelInstead() {
5255
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
5144
5256
  const [item, setItem] = useState<WorkItem | null>(null);
5145
5257
 
5146
5258
  const onInteraction = (interaction: ScheduleInteraction) => {
@@ -5153,7 +5265,6 @@ export default function DetailPanelInstead() {
5153
5265
  <Stack gap={3}>
5154
5266
  <Schedule
5155
5267
  ariaLabel="Sprint 14 of the web team"
5156
- initialDomain={[day(9), day(21)]}
5157
5268
  height={260}
5158
5269
  tooltip={false}
5159
5270
  onInteraction={onInteraction}
@@ -5196,7 +5307,7 @@ The target says what is hovered: a bar brings its `subtask` and the `blocked` ti
5196
5307
 
5197
5308
  ```tsx
5198
5309
  import { Stack, Text } from "@umriss-ui/core";
5199
- import { BlockedTimes, Dependencies, Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5310
+ import { useSchedule } from "@umriss-ui/schedule";
5200
5311
  import type { BlockedTime, Dependency, ScheduleTooltipTarget, Subtask, Task } from "@umriss-ui/schedule";
5201
5312
 
5202
5313
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -5250,8 +5361,9 @@ function Covers({ target }: { target: ScheduleTooltipTarget }) {
5250
5361
  }
5251
5362
 
5252
5363
  export default function SayWhatABarCoversAndALineJoins() {
5364
+ const { Schedule, Lane, Subtasks, Dependencies, BlockedTimes } = useSchedule({ initialView: { domain: [at(6, 30), at(15, 30)] } });
5253
5365
  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} />}>
5366
+ <Schedule ariaLabel="Design, build and test, with leave under the build" height={188} tooltip={(target) => <Covers target={target} />}>
5255
5367
  <Lane id="noah" label="Noah Fischer" />
5256
5368
  <Lane id="arjun" label="Arjun Mehta" />
5257
5369
  <Lane id="eva" label="Eva Novak" />
@@ -5284,17 +5396,163 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
5284
5396
  | `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
5397
  | `violated` | [`ViolatedDependency`](#/findings/type-ViolatedDependency)` \| undefined` | — | Its finding, where it is violated.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
5286
5398
 
5399
+ ### View
5400
+
5401
+ How the planner is looking at the plan - the span in view and the folded lane groups - as one value to keep and hand back.
5402
+
5403
+ ```ts
5404
+ import { useSchedule } from "@umriss-ui/schedule";
5405
+ ```
5406
+
5407
+ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/view/
5408
+
5409
+ The schedule keeps its view itself and puts it nowhere, not in the address and not in any storage. `view` on what [`useSchedule`](#/api/useSchedule) hands back holds it, and `onViewChange` reports every change of it - a pan or zoom at most once per frame; keep it where the application keeps things and hand it back through `initialView`.
5410
+
5411
+ A view that differs in content from the last one handed in applies at once, and what it leaves out goes back to its default; the same one again changes nothing. Without a span the schedule shows the extent of its subtasks, within `zoomLimits`. A group no `LaneGroup` declares falls out.
5412
+
5413
+ `setDomain`, `toggleGroup`, `foldAll` and `unfoldAll` change the view from outside - a button of the application's own that shows the whole plan or folds every team. The selection is not part of the view (`selectedTask`).
5414
+
5415
+ #### Examples
5416
+
5417
+ ##### Keep and restore a view
5418
+
5419
+ `onViewChange` reports the span and the folded groups as one view; keep it - here in the browser's storage, so it outlives a reload - and hand it back through `initialView`. Pan, zoom, fold, keep, reload, restore.
5420
+
5421
+ ```tsx
5422
+ import { useState } from "react";
5423
+ import { Button, Stack, Text } from "@umriss-ui/core";
5424
+ import { useSchedule } from "@umriss-ui/schedule";
5425
+ import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
5426
+
5427
+ const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
5428
+ const min = (n: number) => n * 60_000;
5429
+
5430
+ const START: ScheduleView = { domain: [at(6), at(16)] };
5431
+ const KEPT = "umriss-schedule-demo-view";
5432
+
5433
+ /* Storage can be missing or refuse - a private window, a blocked site. The
5434
+ view is then simply not kept. */
5435
+ function readKept(): ScheduleView | null {
5436
+ try {
5437
+ const stored = localStorage.getItem(KEPT);
5438
+ return stored === null ? null : (JSON.parse(stored) as ScheduleView);
5439
+ } catch {
5440
+ return null;
5441
+ }
5442
+ }
5443
+
5444
+ function keep(view: ScheduleView): void {
5445
+ try {
5446
+ localStorage.setItem(KEPT, JSON.stringify(view));
5447
+ } catch {
5448
+ /* Not kept; the schedule works on regardless. */
5449
+ }
5450
+ }
5451
+
5452
+ const TEAMS: Task[] = [
5453
+ { id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
5454
+ { id: "billing", name: "Billing run", color: "light-dark(#0d9488, #3cc7b8)" },
5455
+ ];
5456
+
5457
+ const WORK: Subtask[] = [
5458
+ { id: "p1", task: "portal", lane: "noah", from: at(7), to: at(9, 30), leadOut: min(15) },
5459
+ { id: "p2", task: "portal", lane: "arjun", from: at(10), to: at(13), leadIn: min(30) },
5460
+ { id: "b1", task: "billing", lane: "mei", from: at(8), to: at(11) },
5461
+ { id: "b2", task: "billing", lane: "lukas", from: at(11, 30), to: at(14, 30), leadIn: min(15) },
5462
+ ];
5463
+
5464
+ export default function KeepAndRestore() {
5465
+ /* The application holds the view the schedule reports; handing it another
5466
+ one is all it takes to restore. */
5467
+ const [view, setView] = useState<ScheduleView>(START);
5468
+ const [kept, setKept] = useState<ScheduleView | null>(readKept);
5469
+ const { Schedule, Lane, LaneGroup, Subtasks, setDomain } = useSchedule({ initialView: view, onViewChange: setView });
5470
+
5471
+ return (
5472
+ <Stack gap={3}>
5473
+ <Schedule ariaLabel="Two teams on Tuesday, 17 March" height={290}>
5474
+ <LaneGroup id="product" label="Product">
5475
+ <Lane id="noah" label="Noah Fischer" />
5476
+ <Lane id="arjun" label="Arjun Mehta" />
5477
+ </LaneGroup>
5478
+ <LaneGroup id="finance" label="Finance">
5479
+ <Lane id="mei" label="Mei Tanaka" />
5480
+ <Lane id="lukas" label="Lukas Brandt" />
5481
+ </LaneGroup>
5482
+ <Subtasks data={WORK} tasks={TEAMS} />
5483
+ </Schedule>
5484
+ <Stack gap={1}>
5485
+ <Text as="span" size="sm" tone="secondary">
5486
+ What the application would keep
5487
+ </Text>
5488
+ {/* JSON has no space to break at: without this it ran past a phone's card. */}
5489
+ <Text as="code" size="sm" mono data-role="view" style={{ overflowWrap: "anywhere" }}>
5490
+ {JSON.stringify(view)}
5491
+ </Text>
5492
+ </Stack>
5493
+ <Stack direction="row" gap={2} wrap>
5494
+ <Button
5495
+ size="sm"
5496
+ onClick={() => {
5497
+ keep(view);
5498
+ setKept(view);
5499
+ }}
5500
+ data-keep
5501
+ >
5502
+ Keep this view
5503
+ </Button>
5504
+ <Button size="sm" variant="ghost" disabled={kept === null} onClick={() => kept && setView(kept)} data-restore>
5505
+ Restore the kept view
5506
+ </Button>
5507
+ <Button size="sm" variant="ghost" onClick={() => setDomain(null)} data-whole-plan>
5508
+ Show the whole plan
5509
+ </Button>
5510
+ </Stack>
5511
+ </Stack>
5512
+ );
5513
+ }
5514
+ ```
5515
+
5516
+ #### When to use something else
5517
+
5518
+ - Two schedules on the same hours → Linked schedules
5519
+
5520
+ #### Keyboard
5521
+
5522
+ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
5523
+
5524
+ #### API
5525
+
5526
+ ##### `ScheduleOptions`
5527
+
5528
+ | Prop | Type | Default | Description |
5529
+ |---|---|---|---|
5530
+ | `initialView` | [`ScheduleView`](#type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 75 more |
5531
+
5532
+ ###### Events
5533
+
5534
+ | Prop | Type | Default | Description |
5535
+ |---|---|---|---|
5536
+ | `onViewChange` | `(view: `[`ScheduleView`](#type-ScheduleView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another schedule as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the schedule starts with.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [Keep two schedules in step (Linked schedules)](#/linked-schedules/in-step), [Read a rota beside the incidents (Linked schedules)](#/linked-schedules/rota-beside-incidents) |
5537
+
5538
+ ##### `ScheduleView`
5539
+
5540
+ | Prop | Type | Default | Description |
5541
+ |---|---|---|---|
5542
+ | `domain` | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 75 more |
5543
+ | `folded` | `readonly string[]` | — | The **Lane group**s that are folded, by id. An id no group carries falls out. A folded outer group hides the inner ones without their ids leaving the list, so unfolding it gives back the view that was there.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside), [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Start with groups folded (Lane groups)](#/lane-groups/start-with-groups-folded) |
5544
+
5287
5545
  ### Linked schedules
5288
5546
 
5289
5547
  Keeps two schedules on the same hours: pan or zoom one and the other follows (also called synchronised views). Reach for it when two sets of lanes stand apart on a screen but belong to the same time.
5290
5548
 
5291
5549
  ```ts
5292
- import { Schedule } from "@umriss-ui/schedule";
5550
+ import { useSchedule } from "@umriss-ui/schedule";
5293
5551
  ```
5294
5552
 
5295
5553
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/linked-schedules/
5296
5554
 
5297
- `onDomainChange` reports the visible span after a pan or zoom, at most once per frame. A span handed in as `initialDomain` is not reported back, so two schedules never feed each other.
5555
+ Each schedule has its own [`useSchedule`](#/api/useSchedule). Hand both the view one of them reports, through `initialView`: a view that differs from the last one handed in applies at once, without a remount, and the same one again changes nothing, so the two never feed each other.
5298
5556
 
5299
5557
  While a drag is in flight the two can stand a frame apart; they settle as soon as it ends.
5300
5558
 
@@ -5302,13 +5560,13 @@ While a drag is in flight the two can stand a frame apart; they settle as soon a
5302
5560
 
5303
5561
  ##### Keep two schedules in step
5304
5562
 
5305
- Hand the span `onDomainChange` reports to both schedules' `initialDomain`: pan or zoom either one and the other follows.
5563
+ Hand the view `onViewChange` reports to both schedules' `initialView`: pan or zoom either one and the other follows - no `key`, no remount.
5306
5564
 
5307
5565
  ```tsx
5308
5566
  import { useState } from "react";
5309
5567
  import { Stack, Text } from "@umriss-ui/core";
5310
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5311
- import type { Subtask, Task } from "@umriss-ui/schedule";
5568
+ import { useSchedule } from "@umriss-ui/schedule";
5569
+ import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
5312
5570
 
5313
5571
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
5314
5572
  const min = (n: number) => n * 60_000;
@@ -5343,27 +5601,34 @@ const LEGS: readonly Subtask[] = [
5343
5601
  ];
5344
5602
 
5345
5603
  const on = (lanes: readonly { id: string }[]) => LEGS.filter((leg) => lanes.some((lane) => lane.id === leg.lane));
5604
+ const DIESEL_LEGS = on(DIESEL);
5605
+ const ELECTRIC_LEGS = on(ELECTRIC);
5346
5606
 
5347
5607
  const time = (instant: number) => new Date(instant).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
5348
5608
 
5349
5609
  export default function InStep() {
5350
- const [domain, setDomain] = useState<readonly [number, number]>([at(5, 30), at(18)]);
5610
+ /* One view for both: each schedule reports its own, and goes to the other's
5611
+ because it differs from the last one handed in. */
5612
+ const [view, setView] = useState<ScheduleView>({ domain: [at(5, 30), at(18)] });
5613
+ const diesel = useSchedule({ initialView: view, onViewChange: setView });
5614
+ const electric = useSchedule({ initialView: view, onViewChange: setView });
5615
+ const span = view.domain ?? [at(5, 30), at(18)];
5351
5616
  return (
5352
5617
  <Stack gap={2}>
5353
- <Schedule ariaLabel="Diesel vehicles on Tuesday, 17 March" initialDomain={domain} height={240} onDomainChange={setDomain}>
5618
+ <diesel.Schedule ariaLabel="Diesel vehicles on Tuesday, 17 March" height={240}>
5354
5619
  {DIESEL.map((vehicle) => (
5355
- <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5620
+ <diesel.Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5356
5621
  ))}
5357
- <Subtasks data={on(DIESEL)} tasks={CONSIGNMENTS} />
5358
- </Schedule>
5359
- <Schedule ariaLabel="Electric vans, the same hours" initialDomain={domain} height={152} onDomainChange={setDomain}>
5622
+ <diesel.Subtasks data={DIESEL_LEGS} tasks={CONSIGNMENTS} />
5623
+ </diesel.Schedule>
5624
+ <electric.Schedule ariaLabel="Electric vans, the same hours" height={152}>
5360
5625
  {ELECTRIC.map((vehicle) => (
5361
- <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5626
+ <electric.Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5362
5627
  ))}
5363
- <Subtasks data={on(ELECTRIC)} tasks={CONSIGNMENTS} />
5364
- </Schedule>
5628
+ <electric.Subtasks data={ELECTRIC_LEGS} tasks={CONSIGNMENTS} />
5629
+ </electric.Schedule>
5365
5630
  <Text size="xs" mono tone="muted" data-span>
5366
- {time(domain[0])} – {time(domain[1])}
5631
+ {time(span[0])} – {time(span[1])}
5367
5632
  </Text>
5368
5633
  </Stack>
5369
5634
  );
@@ -5377,8 +5642,8 @@ Two schedules with different lanes over one span: who was on call, above the inc
5377
5642
  ```tsx
5378
5643
  import { useState } from "react";
5379
5644
  import { Stack } from "@umriss-ui/core";
5380
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5381
- import type { Subtask, Task } from "@umriss-ui/schedule";
5645
+ import { useSchedule } from "@umriss-ui/schedule";
5646
+ import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
5382
5647
 
5383
5648
  /* Data from the operations world, written out here so the example runs on its own. */
5384
5649
 
@@ -5505,20 +5770,22 @@ const PAGED: readonly Subtask[] = INCIDENTS.map((incident) => ({
5505
5770
  const HIT = SERVICES.filter((service) => PAGED.some((incident) => incident.lane === service.id));
5506
5771
 
5507
5772
  export default function RotaBesideIncidents() {
5508
- const [domain, setDomain] = useState<readonly [number, number]>([day(16, 6), day(17, 14)]);
5773
+ const [view, setView] = useState<ScheduleView>({ domain: [day(16, 6), day(17, 14)] });
5774
+ const rota = useSchedule({ initialView: view, onViewChange: setView });
5775
+ const incidents = useSchedule({ initialView: view, onViewChange: setView });
5509
5776
  return (
5510
5777
  <Stack gap={2}>
5511
- <Schedule ariaLabel="Who was on call" initialDomain={domain} height={140} onDomainChange={setDomain}>
5512
- <Lane id="primary" label="Primary" />
5513
- <Lane id="secondary" label="Secondary" />
5514
- <Subtasks data={DUTIES} tasks={PEOPLE} />
5515
- </Schedule>
5516
- <Schedule ariaLabel="Incidents, the same hours" initialDomain={domain} height={290} onDomainChange={setDomain}>
5778
+ <rota.Schedule ariaLabel="Who was on call" height={140}>
5779
+ <rota.Lane id="primary" label="Primary" />
5780
+ <rota.Lane id="secondary" label="Secondary" />
5781
+ <rota.Subtasks data={DUTIES} tasks={PEOPLE} />
5782
+ </rota.Schedule>
5783
+ <incidents.Schedule ariaLabel="Incidents, the same hours" height={290}>
5517
5784
  {HIT.map((service) => (
5518
- <Lane key={service.id} id={service.id} label={service.name} />
5785
+ <incidents.Lane key={service.id} id={service.id} label={service.name} />
5519
5786
  ))}
5520
- <Subtasks data={PAGED} tasks={SEVERITIES} />
5521
- </Schedule>
5787
+ <incidents.Subtasks data={PAGED} tasks={SEVERITIES} />
5788
+ </incidents.Schedule>
5522
5789
  </Stack>
5523
5790
  );
5524
5791
  }
@@ -5538,16 +5805,9 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
5538
5805
 
5539
5806
  | Prop | Type | Default | Description |
5540
5807
  |---|---|---|---|
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
5808
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
5543
5809
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
5544
5810
 
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
5811
  ###### Accessibility
5552
5812
 
5553
5813
  | Prop | Type | Default | Description |
@@ -5589,9 +5849,27 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
5589
5849
  | [`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
5850
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
5591
5851
 
5852
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
5853
+
5854
+ | Prop | Type | Default | Description |
5855
+ |---|---|---|---|
5856
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
5857
+
5858
+ ###### Events
5859
+
5860
+ | Prop | Type | Default | Description |
5861
+ |---|---|---|---|
5862
+ | [`onViewChange`](#/view/ScheduleOptions-onViewChange) | `(view: `[`ScheduleView`](#/view/type-ScheduleView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another schedule as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the schedule starts with. |
5863
+
5864
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
5865
+
5866
+ | Prop | Type | Default | Description |
5867
+ |---|---|---|---|
5868
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
5869
+
5592
5870
  #### Known limits
5593
5871
 
5594
- - Only the span is shared - not the selection, the hover or the vertical scroll.
5872
+ - Only the view is shared - the span and the folded groups, not the selection, the hover or the vertical scroll.
5595
5873
 
5596
5874
  What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
5597
5875
 
@@ -5600,14 +5878,14 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
5600
5878
  Converts between a point on the screen and a time on a lane, so marks of your own - a cut-off, a delivery window, a pin - stand at the right place beside the plan.
5601
5879
 
5602
5880
  ```ts
5603
- import { Schedule } from "@umriss-ui/schedule";
5881
+ import { useSchedule } from "@umriss-ui/schedule";
5604
5882
  ```
5605
5883
 
5606
5884
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/handle/
5607
5885
 
5608
5886
  The handle holds three functions and nothing else: `clientPointOf(time, lane?)`, `positionAt(clientX, clientY)` and `visibleDomain()`. Everything else the schedule does is props.
5609
5887
 
5610
- Place your marks again whenever `onDomainChange` reports a new span.
5888
+ Place your marks again whenever `view.domain` changes.
5611
5889
 
5612
5890
  #### Examples
5613
5891
 
@@ -5618,7 +5896,7 @@ Place your marks again whenever `onDomainChange` reports a new span.
5618
5896
  ```tsx
5619
5897
  import { useEffect, useRef, useState } from "react";
5620
5898
  import { Stack, Text } from "@umriss-ui/core";
5621
- import { Lane, Schedule, Subtasks } from "@umriss-ui/schedule";
5899
+ import { useSchedule } from "@umriss-ui/schedule";
5622
5900
  import type { ScheduleHandle, Subtask, Task } from "@umriss-ui/schedule";
5623
5901
 
5624
5902
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -5648,9 +5926,9 @@ const CUT_OFF = at(14);
5648
5926
  const clock = (time: number) => new Date(time).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
5649
5927
 
5650
5928
  export default function PlaceAMark() {
5929
+ const { Schedule, Lane, Subtasks, view } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
5651
5930
  const plan = useRef<ScheduleHandle>(null);
5652
5931
  const host = useRef<HTMLDivElement>(null);
5653
- const [domain, setDomain] = useState<readonly [number, number]>([at(5, 30), at(18)]);
5654
5932
  const [pin, setPin] = useState<number | null>(null);
5655
5933
  const [under, setUnder] = useState("Move the pointer over the plan");
5656
5934
 
@@ -5659,7 +5937,7 @@ export default function PlaceAMark() {
5659
5937
  const point = plan.current?.clientPointOf(CUT_OFF);
5660
5938
  const left = host.current?.getBoundingClientRect().left;
5661
5939
  setPin(point == null || left === undefined ? null : point.x - left);
5662
- }, [domain]);
5940
+ }, [view.domain]);
5663
5941
 
5664
5942
  return (
5665
5943
  <Stack gap={2}>
@@ -5673,9 +5951,7 @@ export default function PlaceAMark() {
5673
5951
  <Schedule
5674
5952
  ref={plan}
5675
5953
  ariaLabel="Three vehicles with a cut-off of the application's own"
5676
- initialDomain={domain}
5677
5954
  height={196}
5678
- onDomainChange={setDomain}
5679
5955
  onInteraction={(interaction) => {
5680
5956
  /* The interaction carries time and lane already; `positionAt` is for
5681
5957
  listeners of your own, which have only a point. */
@@ -5710,7 +5986,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
5710
5986
 
5711
5987
  | Prop | Type | Default | Description |
5712
5988
  |---|---|---|---|
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
5989
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
5715
5990
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
5716
5991
 
@@ -5719,7 +5994,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
5719
5994
  | Prop | Type | Default | Description |
5720
5995
  |---|---|---|---|
5721
5996
  | [`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. |
5723
5997
 
5724
5998
  ###### Accessibility
5725
5999
 
@@ -5768,6 +6042,18 @@ From `ScheduleInteraction` — the full table stands on [Interactions](#/interac
5768
6042
  | [`clientX`](#/interactions/ScheduleInteraction-clientX) *required* | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens. |
5769
6043
  | [`clientY`](#/interactions/ScheduleInteraction-clientY) *required* | `number` | — | The pointer's vertical client coordinate. |
5770
6044
 
6045
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
6046
+
6047
+ | Prop | Type | Default | Description |
6048
+ |---|---|---|---|
6049
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
6050
+
6051
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
6052
+
6053
+ | Prop | Type | Default | Description |
6054
+ |---|---|---|---|
6055
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
6056
+
5771
6057
  ### Findings as data
5772
6058
 
5773
6059
  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.
@@ -5790,7 +6076,7 @@ Lead-in and lead-out count as occupied time; touching at a shared edge is no ove
5790
6076
 
5791
6077
  ```tsx
5792
6078
  import { Stack, Text } from "@umriss-ui/core";
5793
- import { BlockedTimes, Dependencies, Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
6079
+ import { findings, useSchedule } from "@umriss-ui/schedule";
5794
6080
  import type { BlockedTime, Dependency, Subtask, Task } from "@umriss-ui/schedule";
5795
6081
 
5796
6082
  const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
@@ -5816,11 +6102,12 @@ const when = (instant: number) => new Date(instant).toLocaleString("en-GB", { we
5816
6102
  const hours = (ms: number) => Math.round(ms / 3_600_000);
5817
6103
 
5818
6104
  export default function ListTheFindings() {
6105
+ const { Schedule, Lane, Subtasks, Dependencies, BlockedTimes } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
5819
6106
  const found = findings(WORK, HANDOVERS, LEAVE);
5820
6107
 
5821
6108
  return (
5822
6109
  <Stack gap={3}>
5823
- <Schedule ariaLabel="Three people's week with its findings" initialDomain={[day(16, 0), day(21, 0)]} height={184}>
6110
+ <Schedule ariaLabel="Three people's week with its findings" height={184}>
5824
6111
  <Lane id="noah" label="Noah Fischer" />
5825
6112
  <Lane id="chloe" label="Chloe Durand" />
5826
6113
  <Lane id="arjun" label="Arjun Mehta" />
@@ -5856,7 +6143,7 @@ When all three lists [`findings`](#/api/findings) returns are empty, say so plai
5856
6143
 
5857
6144
  ```tsx
5858
6145
  import { EmptyState, Stack } from "@umriss-ui/core";
5859
- import { Lane, Schedule, Subtasks, findings } from "@umriss-ui/schedule";
6146
+ import { findings, useSchedule } from "@umriss-ui/schedule";
5860
6147
  import type { Subtask, Task } from "@umriss-ui/schedule";
5861
6148
 
5862
6149
  const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
@@ -5869,12 +6156,13 @@ const WORK: readonly Subtask[] = [
5869
6156
  ];
5870
6157
 
5871
6158
  export default function NothingToFix() {
6159
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
5872
6160
  const found = findings(WORK, []);
5873
6161
  const count = found.overlaps.length + found.violatedDependencies.length + found.inBlockedTime.length;
5874
6162
 
5875
6163
  return (
5876
6164
  <Stack gap={3}>
5877
- <Schedule ariaLabel="Two developers' week, without findings" initialDomain={[day(16, 0), day(21, 0)]} height={140}>
6165
+ <Schedule ariaLabel="Two developers' week, without findings" height={140}>
5878
6166
  <Lane id="hana" label="Hana Sato" />
5879
6167
  <Lane id="kofi" label="Kofi Mensah" />
5880
6168
  <Subtasks data={WORK} tasks={PROJECTS} />
@@ -5891,7 +6179,7 @@ export default function NothingToFix() {
5891
6179
 
5892
6180
  ```tsx
5893
6181
  import { Badge, Stack } from "@umriss-ui/core";
5894
- import { Dependencies, Lane, Schedule, Subtasks, overlaps, violatedDependencies } from "@umriss-ui/schedule";
6182
+ import { overlaps, useSchedule, violatedDependencies } from "@umriss-ui/schedule";
5895
6183
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
5896
6184
 
5897
6185
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -5921,6 +6209,7 @@ const TRANSFERS: readonly Dependency[] = [
5921
6209
  ];
5922
6210
 
5923
6211
  export default function CountPerKind() {
6212
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
5924
6213
  const doubleBooked = overlaps(LEGS).length;
5925
6214
  const late = violatedDependencies(LEGS, TRANSFERS).length;
5926
6215
 
@@ -5930,7 +6219,7 @@ export default function CountPerKind() {
5930
6219
  <Badge tone={doubleBooked > 0 ? "warning" : "success"}>{doubleBooked} double-booked</Badge>
5931
6220
  <Badge tone={late > 0 ? "warning" : "success"}>{late} transfers too short</Badge>
5932
6221
  </Stack>
5933
- <Schedule ariaLabel="The day's legs with their findings counted" initialDomain={[at(5, 30), at(18)]} height={196}>
6222
+ <Schedule ariaLabel="The day's legs with their findings counted" height={196}>
5934
6223
  {VEHICLES.map((vehicle) => (
5935
6224
  <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
5936
6225
  ))}
@@ -5992,7 +6281,7 @@ Direct manipulation that changes nothing by itself: the schedule reports, the ap
5992
6281
  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
6282
 
5994
6283
  ```ts
5995
- import { Schedule, applyIntent } from "@umriss-ui/schedule";
6284
+ import { useSchedule, applyIntent } from "@umriss-ui/schedule";
5996
6285
  ```
5997
6286
 
5998
6287
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/
@@ -6011,7 +6300,7 @@ List `move` in `intents` and apply what `onIntent` reports with [`applyIntent`](
6011
6300
 
6012
6301
  ```tsx
6013
6302
  import { useState } from "react";
6014
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6303
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6015
6304
  import type { Subtask, Task } from "@umriss-ui/schedule";
6016
6305
 
6017
6306
  const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
@@ -6024,11 +6313,11 @@ const START: readonly Subtask[] = [
6024
6313
  ];
6025
6314
 
6026
6315
  export default function MoveInTime() {
6316
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
6027
6317
  const [work, setWork] = useState(START);
6028
6318
  return (
6029
6319
  <Schedule
6030
6320
  ariaLabel="Two people's work this week"
6031
- initialDomain={[day(16, 0), day(21, 0)]}
6032
6321
  height={140}
6033
6322
  intents={["move"]}
6034
6323
  onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
@@ -6048,7 +6337,7 @@ Add `lane` to `intents`: a drop reports a move and a lane change separately, and
6048
6337
  ```tsx
6049
6338
  import { useState } from "react";
6050
6339
  import { Stack, Text } from "@umriss-ui/core";
6051
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6340
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6052
6341
  import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
6053
6342
 
6054
6343
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6084,6 +6373,7 @@ const TRANSFERS: readonly Dependency[] = [
6084
6373
  ];
6085
6374
 
6086
6375
  export default function MoveToAnotherLane() {
6376
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
6087
6377
  const [legs, setLegs] = useState(LEGS);
6088
6378
  const [last, setLast] = useState<Intent | null>(null);
6089
6379
 
@@ -6091,7 +6381,6 @@ export default function MoveToAnotherLane() {
6091
6381
  <Stack gap={3}>
6092
6382
  <Schedule
6093
6383
  ariaLabel="Five vehicles on Tuesday, 17 March, editable"
6094
- initialDomain={[at(5, 30), at(18)]}
6095
6384
  height={284}
6096
6385
  intents={["move", "lane"]}
6097
6386
  onIntent={(intent) => {
@@ -6120,7 +6409,7 @@ Your `onIntent` decides: here it applies a drop only when [`overlaps`](#/api/ove
6120
6409
  ```tsx
6121
6410
  import { useRef, useState } from "react";
6122
6411
  import { Stack, Text } from "@umriss-ui/core";
6123
- import { Lane, Schedule, Subtasks, applyIntent, overlaps } from "@umriss-ui/schedule";
6412
+ import { applyIntent, overlaps, useSchedule } from "@umriss-ui/schedule";
6124
6413
  import type { Intent, Subtask } from "@umriss-ui/schedule";
6125
6414
 
6126
6415
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -6207,6 +6496,7 @@ const WEB = PEOPLE.filter((person) => person.team === "Web" && WORK.some((item)
6207
6496
  const START: readonly Subtask[] = WORK.filter((item) => WEB.some((person) => person.id === item.lane));
6208
6497
 
6209
6498
  export default function RefuseADoubleBooking() {
6499
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
6210
6500
  const [work, setWork] = useState<readonly Subtask[]>(START);
6211
6501
  /* A drop onto another lane at another time reports two intents in one
6212
6502
  tick; each is decided on the plan the one before it left. */
@@ -6231,7 +6521,6 @@ export default function RefuseADoubleBooking() {
6231
6521
  <Stack gap={3}>
6232
6522
  <Schedule
6233
6523
  ariaLabel="Sprint 14 of the web team, editable"
6234
- initialDomain={[day(9), day(21)]}
6235
6524
  height={240}
6236
6525
  intents={["move", "lane"]}
6237
6526
  onIntent={onIntent}
@@ -6270,7 +6559,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
6270
6559
 
6271
6560
  | Prop | Type | Default | Description |
6272
6561
  |---|---|---|---|
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
6562
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6275
6563
  | [`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
6564
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -6337,6 +6625,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
6337
6625
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
6338
6626
  | [`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
6627
 
6628
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
6629
+
6630
+ | Prop | Type | Default | Description |
6631
+ |---|---|---|---|
6632
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
6633
+
6634
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
6635
+
6636
+ | Prop | Type | Default | Description |
6637
+ |---|---|---|---|
6638
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
6639
+
6340
6640
  From `Overlap` — the full table stands on [Findings as data](#/findings/type-Overlap).
6341
6641
 
6342
6642
  | Prop | Type | Default | Description |
@@ -6357,7 +6657,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
6357
6657
  Change how long work takes, or how long its preparation and its wrap-up take, by dragging an edge (also called resizing). Each part changes on its own and is reported for your application to apply.
6358
6658
 
6359
6659
  ```ts
6360
- import { Schedule, applyIntent } from "@umriss-ui/schedule";
6660
+ import { useSchedule, applyIntent } from "@umriss-ui/schedule";
6361
6661
  ```
6362
6662
 
6363
6663
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/stretch/
@@ -6374,7 +6674,7 @@ With `stretch` in `intents`, either edge of a bar drags to a new start or end; t
6374
6674
 
6375
6675
  ```tsx
6376
6676
  import { useState } from "react";
6377
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6677
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6378
6678
  import type { Subtask, Task } from "@umriss-ui/schedule";
6379
6679
 
6380
6680
  const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
@@ -6387,11 +6687,11 @@ const START: readonly Subtask[] = [
6387
6687
  ];
6388
6688
 
6389
6689
  export default function StretchTheMainTime() {
6690
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
6390
6691
  const [work, setWork] = useState(START);
6391
6692
  return (
6392
6693
  <Schedule
6393
6694
  ariaLabel="Two estimates to adjust"
6394
- initialDomain={[day(16, 0), day(21, 0)]}
6395
6695
  height={140}
6396
6696
  intents={["stretch"]}
6397
6697
  onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
@@ -6410,7 +6710,7 @@ Add `leadIn` and `leadOut`: a selected tour shows a grip for its loading and its
6410
6710
 
6411
6711
  ```tsx
6412
6712
  import { useState } from "react";
6413
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6713
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6414
6714
  import type { Subtask, Task } from "@umriss-ui/schedule";
6415
6715
 
6416
6716
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6425,12 +6725,12 @@ const START: Subtask[] = [
6425
6725
  ];
6426
6726
 
6427
6727
  export default function ChangeLeadInAndLeadOut() {
6728
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(13, 30)] } });
6428
6729
  const [tours, setTours] = useState(START);
6429
6730
 
6430
6731
  return (
6431
6732
  <Schedule
6432
6733
  ariaLabel="Two tours from North depot"
6433
- initialDomain={[at(6), at(13, 30)]}
6434
6734
  height={150}
6435
6735
  intents={["stretch", "leadIn", "leadOut"]}
6436
6736
  onIntent={(intent) => setTours((current) => current.map((tour) => applyIntent(tour, intent)))}
@@ -6457,7 +6757,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
6457
6757
 
6458
6758
  | Prop | Type | Default | Description |
6459
6759
  |---|---|---|---|
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
6760
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6462
6761
  | [`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
6762
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -6509,6 +6808,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6509
6808
  | [`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
6809
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6511
6810
 
6811
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
6812
+
6813
+ | Prop | Type | Default | Description |
6814
+ |---|---|---|---|
6815
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
6816
+
6817
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
6818
+
6819
+ | Prop | Type | Default | Description |
6820
+ |---|---|---|---|
6821
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
6822
+
6512
6823
  #### Known limits
6513
6824
 
6514
6825
  - A main time never becomes shorter than one raster step, or a minute without a raster.
@@ -6520,7 +6831,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
6520
6831
  Makes a drag land on a raster - the axis' own step, a fixed duration, or a pattern with an offset - so dropped work starts on a time a planner can read.
6521
6832
 
6522
6833
  ```ts
6523
- import { Schedule, snapTime } from "@umriss-ui/schedule";
6834
+ import { useSchedule, snapTime } from "@umriss-ui/schedule";
6524
6835
  ```
6525
6836
 
6526
6837
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/snapping/
@@ -6537,7 +6848,7 @@ Without `snap`, a drag lands on the fine band's step: quarter hours when zoomed
6537
6848
 
6538
6849
  ```tsx
6539
6850
  import { useState } from "react";
6540
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6851
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6541
6852
  import type { Subtask, Task } from "@umriss-ui/schedule";
6542
6853
 
6543
6854
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6547,11 +6858,11 @@ const TOURS: readonly Task[] = [{ id: "river", name: "Riverside depot tours", co
6547
6858
  const START: readonly Subtask[] = [{ id: "T-04", task: "river", lane: "van-402", from: at(7, 30), to: at(12, 10) }];
6548
6859
 
6549
6860
  export default function SnapToTheTicks() {
6861
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(16)] } });
6550
6862
  const [tours, setTours] = useState(START);
6551
6863
  return (
6552
6864
  <Schedule
6553
6865
  ariaLabel="A tour on the default raster"
6554
- initialDomain={[at(6), at(16)]}
6555
6866
  height={100}
6556
6867
  intents={["move"]}
6557
6868
  onIntent={(intent) => setTours((current) => current.map((tour) => applyIntent(tour, intent)))}
@@ -6569,7 +6880,7 @@ Give `snap` a number of milliseconds - here half a day - and every drag lands on
6569
6880
 
6570
6881
  ```tsx
6571
6882
  import { useState } from "react";
6572
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6883
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6573
6884
  import type { Subtask, Task } from "@umriss-ui/schedule";
6574
6885
 
6575
6886
  const day = (d: number, hours = 0) => new Date(2026, 2, d, hours).getTime();
@@ -6583,11 +6894,11 @@ const START: readonly Subtask[] = [
6583
6894
  ];
6584
6895
 
6585
6896
  export default function SnapToADuration() {
6897
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16), day(21)] } });
6586
6898
  const [work, setWork] = useState(START);
6587
6899
  return (
6588
6900
  <Schedule
6589
6901
  ariaLabel="Work planned in half days"
6590
- initialDomain={[day(16), day(21)]}
6591
6902
  height={140}
6592
6903
  snap={HALF_DAY}
6593
6904
  intents={["move", "stretch"]}
@@ -6607,7 +6918,7 @@ A `snap` raster with a step and an offset: eight-hour watches handed over at 06:
6607
6918
 
6608
6919
  ```tsx
6609
6920
  import { useState } from "react";
6610
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6921
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6611
6922
  import type { SnapRaster, Subtask, Task } from "@umriss-ui/schedule";
6612
6923
 
6613
6924
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6621,11 +6932,11 @@ const ENGINEERS: readonly Task[] = [{ id: "ada", name: "Ada Mwangi", color: "lig
6621
6932
  const START: readonly Subtask[] = [{ id: "watch-1", task: "ada", lane: "primary", from: at(6), to: at(14) }];
6622
6933
 
6623
6934
  export default function SnapToHandovers() {
6935
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(4), at(24)] } });
6624
6936
  const [watches, setWatches] = useState(START);
6625
6937
  return (
6626
6938
  <Schedule
6627
6939
  ariaLabel="An on-call watch on the handover raster"
6628
- initialDomain={[at(4), at(24)]}
6629
6940
  height={110}
6630
6941
  snap={HANDOVERS}
6631
6942
  intents={["move"]}
@@ -6644,7 +6955,7 @@ export default function SnapToHandovers() {
6644
6955
 
6645
6956
  ```tsx
6646
6957
  import { useState } from "react";
6647
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
6958
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6648
6959
  import type { Subtask, Task } from "@umriss-ui/schedule";
6649
6960
 
6650
6961
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6654,11 +6965,11 @@ const ENGINEERS: readonly Task[] = [{ id: "sam", name: "Sam Okafor", color: "lig
6654
6965
  const START: readonly Subtask[] = [{ id: "window-1", task: "sam", lane: "notifications", from: at(13, 5), to: at(13, 50), name: "Maintenance window" }];
6655
6966
 
6656
6967
  export default function NoSnapping() {
6968
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(10), at(18)] } });
6657
6969
  const [windows, setWindows] = useState(START);
6658
6970
  return (
6659
6971
  <Schedule
6660
6972
  ariaLabel="A maintenance window placed freely"
6661
- initialDomain={[at(10), at(18)]}
6662
6973
  height={100}
6663
6974
  snap={false}
6664
6975
  intents={["move"]}
@@ -6681,7 +6992,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
6681
6992
 
6682
6993
  | Prop | Type | Default | Description |
6683
6994
  |---|---|---|---|
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
6995
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6686
6996
  | [`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
6997
  | [`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)). |
@@ -6732,12 +7042,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
6732
7042
  | [`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
7043
  | [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
6734
7044
 
7045
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
7046
+
7047
+ | Prop | Type | Default | Description |
7048
+ |---|---|---|---|
7049
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
7050
+
7051
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
7052
+
7053
+ | Prop | Type | Default | Description |
7054
+ |---|---|---|---|
7055
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
7056
+
6735
7057
  ### Placing from outside
6736
7058
 
6737
7059
  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.
6738
7060
 
6739
7061
  ```ts
6740
- import { Schedule, subtaskFromPlace } from "@umriss-ui/schedule";
7062
+ import { useSchedule, subtaskFromPlace } from "@umriss-ui/schedule";
6741
7063
  ```
6742
7064
 
6743
7065
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/placing/
@@ -6755,7 +7077,7 @@ Set `placing` on your list's `dragstart` and clear it on `dragend`; the drop rep
6755
7077
  ```tsx
6756
7078
  import { useState } from "react";
6757
7079
  import { Card, Stack, Text } from "@umriss-ui/core";
6758
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent, subtaskFromPlace } from "@umriss-ui/schedule";
7080
+ import { applyIntent, subtaskFromPlace, useSchedule } from "@umriss-ui/schedule";
6759
7081
  import type { Dependency, Intent, PlacingItem, Subtask, Task } from "@umriss-ui/schedule";
6760
7082
 
6761
7083
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6789,6 +7111,7 @@ const BACKLOG: readonly (PlacingItem & { label: string })[] = [
6789
7111
  ];
6790
7112
 
6791
7113
  export default function DragFromTheBacklog() {
7114
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
6792
7115
  const [plan, setPlan] = useState<{ work: readonly Subtask[]; placed: number; last: string }>({
6793
7116
  work: TODAY,
6794
7117
  placed: 0,
@@ -6831,7 +7154,6 @@ export default function DragFromTheBacklog() {
6831
7154
  </Stack>
6832
7155
  <Schedule
6833
7156
  ariaLabel="Today's plan of three people, with the backlog beside it"
6834
- initialDomain={[at(5, 30), at(18)]}
6835
7157
  height={196}
6836
7158
  intents={["place", "move", "lane"]}
6837
7159
  placing={placing}
@@ -6865,7 +7187,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
6865
7187
 
6866
7188
  | Prop | Type | Default | Description |
6867
7189
  |---|---|---|---|
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
7190
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
6870
7191
  | [`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
7192
  | [`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. |
@@ -6931,12 +7252,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
6931
7252
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
6932
7253
  | [`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
7254
 
7255
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
7256
+
7257
+ | Prop | Type | Default | Description |
7258
+ |---|---|---|---|
7259
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
7260
+
7261
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
7262
+
7263
+ | Prop | Type | Default | Description |
7264
+ |---|---|---|---|
7265
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
7266
+
6934
7267
  ### Where a subtask may go
6935
7268
 
6936
7269
  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.
6937
7270
 
6938
7271
  ```ts
6939
- import { Schedule, BlockedTimes } from "@umriss-ui/schedule";
7272
+ import { useSchedule } from "@umriss-ui/schedule";
6940
7273
  ```
6941
7274
 
6942
7275
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/where-it-may-go/
@@ -6956,7 +7289,7 @@ A refusal costs only what it refuses - a drop across a closed lane still reports
6956
7289
  ```tsx
6957
7290
  import { useState } from "react";
6958
7291
  import { Stack, Text } from "@umriss-ui/core";
6959
- import { Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
7292
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
6960
7293
  import type { Intent, Subtask, Task } from "@umriss-ui/schedule";
6961
7294
 
6962
7295
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -6982,6 +7315,7 @@ const START: Subtask[] = [
6982
7315
  const mayGo = (subtask: Subtask, lane: string) => subtask.task !== "chilled" || COOLED.includes(lane);
6983
7316
 
6984
7317
  export default function LanesThatFit() {
7318
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(15)] } });
6985
7319
  const [work, setWork] = useState<readonly Subtask[]>(START);
6986
7320
  const [last, setLast] = useState("Drag the vaccines onto the dry van");
6987
7321
 
@@ -6997,7 +7331,6 @@ export default function LanesThatFit() {
6997
7331
  <Stack gap={3}>
6998
7332
  <Schedule
6999
7333
  ariaLabel="Two cooled vans and a dry van"
7000
- initialDomain={[at(6, 30), at(15)]}
7001
7334
  height={190}
7002
7335
  intents={["move", "lane"]}
7003
7336
  canMoveTo={mayGo}
@@ -7023,7 +7356,7 @@ export default function LanesThatFit() {
7023
7356
  ```tsx
7024
7357
  import { useState } from "react";
7025
7358
  import { Card, Stack, Text } from "@umriss-ui/core";
7026
- import { Lane, Schedule, Subtasks, subtaskFromPlace } from "@umriss-ui/schedule";
7359
+ import { subtaskFromPlace, useSchedule } from "@umriss-ui/schedule";
7027
7360
  import type { Intent, PlacingItem, Subtask, Task } from "@umriss-ui/schedule";
7028
7361
 
7029
7362
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -7054,6 +7387,7 @@ const WAITING: PlacingItem & { label: string } = {
7054
7387
  };
7055
7388
 
7056
7389
  export default function DraggedIn() {
7390
+ const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(15)] } });
7057
7391
  const [work, setWork] = useState<readonly Subtask[]>(START);
7058
7392
  const [placing, setPlacing] = useState<PlacingItem | null>(null);
7059
7393
  const [last, setLast] = useState("Drag the chilled delivery onto the dry van");
@@ -7083,7 +7417,6 @@ export default function DraggedIn() {
7083
7417
  </Card>
7084
7418
  <Schedule
7085
7419
  ariaLabel="A chilled delivery waiting beside two cooled vans and a dry van"
7086
- initialDomain={[at(6, 30), at(15)]}
7087
7420
  height={190}
7088
7421
  intents={["place"]}
7089
7422
  placing={placing}
@@ -7110,7 +7443,7 @@ Blocked time refuses a place in time where `canMoveTo` refuses a lane; both hold
7110
7443
  ```tsx
7111
7444
  import { useState } from "react";
7112
7445
  import { Stack, Text } from "@umriss-ui/core";
7113
- import { BlockedTimes, Lane, Schedule, Subtasks, applyIntent } from "@umriss-ui/schedule";
7446
+ import { applyIntent, useSchedule } from "@umriss-ui/schedule";
7114
7447
  import type { BlockedTime, Intent, Subtask } from "@umriss-ui/schedule";
7115
7448
 
7116
7449
  /* Data from the planning world, written out here so the example runs on its own. */
@@ -7227,6 +7560,7 @@ const START_ROLE = new Map(START.map((item) => [item.id, roleOf(item.lane)]));
7227
7560
  const mayGo = (item: Subtask, lane: string) => START_ROLE.get(item.id) === roleOf(lane);
7228
7561
 
7229
7562
  export default function AroundLeave() {
7563
+ const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
7230
7564
  const [work, setWork] = useState<readonly Subtask[]>(START);
7231
7565
  const [last, setLast] = useState("Drag Freya's store screenshots into her holiday");
7232
7566
 
@@ -7240,7 +7574,6 @@ export default function AroundLeave() {
7240
7574
  <Stack gap={3}>
7241
7575
  <Schedule
7242
7576
  ariaLabel="The apps team this week, with leave"
7243
- initialDomain={[day(9), day(21)]}
7244
7577
  height={236}
7245
7578
  intents={["move", "lane"]}
7246
7579
  canMoveTo={mayGo}
@@ -7274,7 +7607,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
7274
7607
 
7275
7608
  | Prop | Type | Default | Description |
7276
7609
  |---|---|---|---|
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
7610
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
7279
7611
  | [`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
7612
  | [`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`. |
@@ -7341,6 +7673,18 @@ From `BlockedTime` — the full table stands on [Blocked time](#/blocked-time/ty
7341
7673
  | [`to`](#/blocked-time/BlockedTime-to) *required* | `number` | — | Where it ends. |
7342
7674
  | [`label`](#/blocked-time/BlockedTime-label) | `string` | — | Why the lane is blocked - "Leave", "Maintenance" -, where the schedule names it. |
7343
7675
 
7676
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
7677
+
7678
+ | Prop | Type | Default | Description |
7679
+ |---|---|---|---|
7680
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
7681
+
7682
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
7683
+
7684
+ | Prop | Type | Default | Description |
7685
+ |---|---|---|---|
7686
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
7687
+
7344
7688
  #### Known limits
7345
7689
 
7346
7690
  - A rule that changes while a drag runs is noticed at the drop, not before.
@@ -7370,7 +7714,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/ripple/
7370
7714
  ```tsx
7371
7715
  import { useState } from "react";
7372
7716
  import { Button, Stack, Text } from "@umriss-ui/core";
7373
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent, ripple } from "@umriss-ui/schedule";
7717
+ import { applyIntent, ripple, useSchedule } from "@umriss-ui/schedule";
7374
7718
  import type { Dependency, MoveIntent, Subtask, Task } from "@umriss-ui/schedule";
7375
7719
 
7376
7720
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -7396,6 +7740,7 @@ const DRIVES: readonly Dependency[] = [
7396
7740
  const LATE: MoveIntent = { kind: "move", subtask: "stop-1", from: at(8, 10), to: at(8, 20) };
7397
7741
 
7398
7742
  export default function PushTheLaterStops() {
7743
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7, 15), at(10, 30)] } });
7399
7744
  const [stops, setStops] = useState<readonly Subtask[]>(START);
7400
7745
  /* Nothing to push until the first stop runs late. */
7401
7746
  const late = stops.some((s) => s.id === LATE.subtask && s.from === LATE.from);
@@ -7403,7 +7748,7 @@ export default function PushTheLaterStops() {
7403
7748
 
7404
7749
  return (
7405
7750
  <Stack gap={3}>
7406
- <Schedule ariaLabel="Tour T-01 on van FP 214 K" initialDomain={[at(7, 15), at(10, 30)]} height={100}>
7751
+ <Schedule ariaLabel="Tour T-01 on van FP 214 K" height={100}>
7407
7752
  <Lane id="van-214" label="Van FP 214 K" />
7408
7753
  <Dependencies data={DRIVES} />
7409
7754
  <Subtasks data={stops} tasks={TOURS} />
@@ -7438,7 +7783,7 @@ export default function PushTheLaterStops() {
7438
7783
  ```tsx
7439
7784
  import { useState } from "react";
7440
7785
  import { Button, Stack } from "@umriss-ui/core";
7441
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent, shiftTask } from "@umriss-ui/schedule";
7786
+ import { applyIntent, shiftTask, useSchedule } from "@umriss-ui/schedule";
7442
7787
  import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
7443
7788
 
7444
7789
  const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
@@ -7465,13 +7810,14 @@ const DRIVES: readonly Dependency[] = [
7465
7810
  ];
7466
7811
 
7467
7812
  export default function MoveAWholeTour() {
7813
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7), at(10, 30)] } });
7468
7814
  const [stops, setStops] = useState<readonly Subtask[]>(START);
7469
7815
  const move = (by: number) =>
7470
7816
  setStops((current) => shiftTask(current, "T-01", by).reduce((data, one) => data.map((s) => applyIntent(s, one)), current));
7471
7817
 
7472
7818
  return (
7473
7819
  <Stack gap={3}>
7474
- <Schedule ariaLabel="Two tours from North depot" initialDomain={[at(7), at(10, 30)]} height={140}>
7820
+ <Schedule ariaLabel="Two tours from North depot" height={140}>
7475
7821
  <Lane id="van-214" label="Van FP 214 K" />
7476
7822
  <Lane id="van-377" label="E-van FP 377 K" />
7477
7823
  <Dependencies data={DRIVES} />
@@ -7496,7 +7842,7 @@ Run [`ripple`](#/api/ripple) inside `onIntent` and apply its moves with the drop
7496
7842
 
7497
7843
  ```tsx
7498
7844
  import { useState } from "react";
7499
- import { Dependencies, Lane, Schedule, Subtasks, applyIntent, ripple } from "@umriss-ui/schedule";
7845
+ import { applyIntent, ripple, useSchedule } from "@umriss-ui/schedule";
7500
7846
  import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
7501
7847
 
7502
7848
  const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
@@ -7516,6 +7862,7 @@ const HANDOVERS: readonly Dependency[] = [
7516
7862
  ];
7517
7863
 
7518
7864
  export default function RippleOnEveryDrag() {
7865
+ const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [day(16, 0), day(24, 0)] } });
7519
7866
  const [work, setWork] = useState<readonly Subtask[]>(START);
7520
7867
 
7521
7868
  const onIntent = (intent: Intent) =>
@@ -7527,7 +7874,6 @@ export default function RippleOnEveryDrag() {
7527
7874
  return (
7528
7875
  <Schedule
7529
7876
  ariaLabel="Design, build and test of the shop search, rippling"
7530
- initialDomain={[day(16, 0), day(24, 0)]}
7531
7877
  height={184}
7532
7878
  intents={["move", "stretch"]}
7533
7879
  onIntent={onIntent}
@@ -7552,7 +7898,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
7552
7898
 
7553
7899
  | Prop | Type | Default | Description |
7554
7900
  |---|---|---|---|
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
7901
  | [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
7557
7902
  | [`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
7903
  | [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
@@ -7617,6 +7962,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
7617
7962
  | [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
7618
7963
  | [`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
7964
 
7965
+ From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
7966
+
7967
+ | Prop | Type | Default | Description |
7968
+ |---|---|---|---|
7969
+ | [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
7970
+
7971
+ From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
7972
+
7973
+ | Prop | Type | Default | Description |
7974
+ |---|---|---|---|
7975
+ | [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
7976
+
7620
7977
  #### Known limits
7621
7978
 
7622
7979
  - 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/)).
@@ -7634,56 +7991,20 @@ Everything `@umriss-ui/schedule` exports, with its signature. A component's prop
7634
7991
 
7635
7992
  Demo page: https://romanhaendler.github.io/umriss-ui/schedule/api/
7636
7993
 
7637
- #### Components
7638
-
7639
- ##### `BlockedTimes`
7640
-
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.
7643
-
7644
- Used on [Scenarios](#/), [Blocked time](#/blocked-time), [Tooltip](#/tooltip), [Findings as data](#/findings) and [Where a subtask may go](#/where-it-may-go).
7645
-
7646
- ##### `Dependencies`
7647
-
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.
7649
-
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).
7651
-
7652
- ##### `Lane`
7653
-
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.
7656
-
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).
7658
-
7659
- ##### `LaneGroup`
7660
-
7661
- [Lane groups](#/lane-groups) – A group of lanes: structure over them, and never a lane itself.
7994
+ #### Hooks
7662
7995
 
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**.
7996
+ ##### `useSchedule`
7668
7997
 
7669
- Used on [Scenarios](#/) and [Lane groups](#/lane-groups).
7998
+ Declares a schedule: hands back `Schedule` and the parts declared inside it
7999
+ - `Lane`, `LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes` -, its
8000
+ `view` and the setters `setDomain`, `toggleGroup`, `foldAll` and
8001
+ `unfoldAll`. A ref on `Schedule` still gives the `ScheduleHandle`.
7670
8002
 
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).
8003
+ ```ts
8004
+ function useSchedule(options?: ScheduleOptions): ScheduleParts;
8005
+ ```
7685
8006
 
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).
8007
+ 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), [View](#/view), [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
8008
 
7688
8009
  #### Functions
7689
8010
 
@@ -8072,6 +8393,29 @@ Its table stands on [Interactions](#/interactions/type-ScheduleInteraction).
8072
8393
 
8073
8394
  Used on [Scenarios](#/), [Interactions](#/interactions) and [Tooltip](#/tooltip).
8074
8395
 
8396
+ ##### `ScheduleOptions`
8397
+
8398
+ Its table stands on [View](#/view/type-ScheduleOptions).
8399
+
8400
+ ##### `ScheduleParts`
8401
+
8402
+ What `useSchedule` hands back: the schedule and what is declared inside
8403
+ it, its view and the setters that change it.
8404
+
8405
+ | Prop | Type | Default | Description |
8406
+ |---|---|---|---|
8407
+ | `Schedule` *required* | `ForwardRefExoticComponent<`[`ScheduleProps`](#/schedule/type-ScheduleProps)` & RefAttributes<`[`ScheduleHandle`](#type-ScheduleHandle)`>>` | — | The schedule. One per `useSchedule`: a second schedule is a second call.<br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
8408
+ | `Lane` *required* | `typeof Lane` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
8409
+ | `LaneGroup` *required* | `typeof LaneGroup` | — | <br>Shown in: [Group lanes under a head (Lane groups)](#/lane-groups/a-group), [Nest groups to any depth (Lane groups)](#/lane-groups/nesting), [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) and 5 more |
8410
+ | `Subtasks` *required* | `typeof Subtasks` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
8411
+ | `Dependencies` *required* | `typeof Dependencies` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Plan a day in the machine shop (First schedule)](#/schedule/machine-plan) and 27 more |
8412
+ | `BlockedTimes` *required* | `typeof BlockedTimes` | — | <br>Shown in: [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Check a sprint against the leave calendar (Blocked time)](#/blocked-time/team-leave) and 5 more |
8413
+ | `view` *required* | [`ScheduleView`](#/view/type-ScheduleView) | — | How the planner is looking at the plan: the span and the folded groups.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside), [Place a mark of your own (The handle)](#/handle/place-a-mark) |
8414
+ | `setDomain` *required* | `(span: readonly [number, number] \| null) => void` | — | Puts a span of two wall-clock instants in view; `null` shows the subtasks' extent.<br>Shown in: [Keep and restore a view (View)](#/view/keep-and-restore) |
8415
+ | `toggleGroup` *required* | `(id: string) => void` | — | Folds or unfolds one **Lane group**. |
8416
+ | `foldAll` *required* | `() => void` | — | Folds every group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) |
8417
+ | `unfoldAll` *required* | `() => void` | — | Unfolds every group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) |
8418
+
8075
8419
  ##### `ScheduleProps`
8076
8420
 
8077
8421
  Its table stands on [First schedule](#/schedule/type-ScheduleProps).
@@ -8082,6 +8426,12 @@ Its table stands on [Tooltip](#/tooltip/type-ScheduleTooltipTarget).
8082
8426
 
8083
8427
  Used on [Tooltip](#/tooltip).
8084
8428
 
8429
+ ##### `ScheduleView`
8430
+
8431
+ Its table stands on [View](#/view/type-ScheduleView).
8432
+
8433
+ Used on [View](#/view) and [Linked schedules](#/linked-schedules).
8434
+
8085
8435
  ##### `SnapRaster`
8086
8436
 
8087
8437
  A raster: a step, and where it starts after local midnight.
@@ -8108,7 +8458,7 @@ Stretch the main time at one or both of its edges.
8108
8458
 
8109
8459
  Its table stands on [Subtasks](#/subtasks/type-Subtask).
8110
8460
 
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).
8461
+ 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), [View](#/view), [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
8462
 
8113
8463
  ##### `SubtaskAppearance`
8114
8464
 
@@ -8126,7 +8476,7 @@ Its table stands on [Subtasks](#/subtasks/type-SubtasksProps).
8126
8476
 
8127
8477
  Its table stands on [Subtasks](#/subtasks/type-Task).
8128
8478
 
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).
8479
+ 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), [View](#/view), [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
8480
 
8131
8481
  ##### `ViolatedDependency`
8132
8482