# Now line

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/now-line/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms-full.txt).

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.

```ts
import { useSchedule } from "@umriss-ui/schedule";
```

Demo page: https://romanhaendler.github.io/umriss-ui/schedule/now-line/

## Examples

### Mark the present

Pass an instant to `now` and a line crosses the lanes there: a dispatcher at 10:30 sees which tours are under way.

```tsx
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";

/* Data from the logistics world, written out here so the example runs on its own. */

/** A small LCG - the same numbers on every computer. */
function random(seed: number): () => number {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const MINUTE = 60_000;

/** Tuesday, 17 March 2026, 10:30 - the moment the screens are read at. */
const NOW = at(10, 30);

interface Vehicle {
  id: string;
  plate: string;
  type: "van" | "e-van" | "truck";
  /** Payload, in kg. */
  capacity: number;
  depot: string;
}

const VEHICLES: readonly Vehicle[] = [
  { id: "v1", plate: "FP 214 K", type: "van", capacity: 1200, depot: "north" },
  { id: "v2", plate: "FP 377 K", type: "e-van", capacity: 900, depot: "north" },
  { id: "v3", plate: "FP 118 R", type: "truck", capacity: 7500, depot: "north" },
  { id: "v4", plate: "FP 402 R", type: "van", capacity: 1200, depot: "river" },
  { id: "v5", plate: "FP 455 R", type: "e-van", capacity: 900, depot: "river" },
  { id: "v6", plate: "FP 290 E", type: "van", capacity: 1200, depot: "east" },
  { id: "v7", plate: "FP 311 E", type: "e-van", capacity: 900, depot: "east" },
  { id: "v8", plate: "FP 520 E", type: "truck", capacity: 7500, depot: "east" },
];

const DRIVERS = [
  { id: "d1", name: "Martin Hale", depot: "north" },
  { id: "d2", name: "Nadia Petrova", depot: "north" },
  { id: "d3", name: "Owen Carter", depot: "north" },
  { id: "d4", name: "Lucia Romero", depot: "river" },
  { id: "d5", name: "Ben Adeyemi", depot: "river" },
  { id: "d6", name: "Hanna Berg", depot: "east" },
  { id: "d7", name: "Yusuf Demir", depot: "east" },
  { id: "d8", name: "Clara Wendt", depot: "east" },
] as const;

const CUSTOMERS = [
  "Holloway Garden Supplies",
  "Marlow & Finch Books",
  "Oakridge Pharmacy",
  "Brixley Cycles",
  "Tamsin's Bakery",
  "Northfold Office",
  "Pellham Hardware",
  "Greywick Studio",
  "Juniper Lane Florist",
  "Ashcombe Dental",
] as const;

interface Stop {
  id: string;
  shipment: string;
  customer: string;
  /** The window the customer was promised, `[from, to]`. */
  window: readonly [number, number];
  /** When the tour plans to be there. */
  arrival: number;
}

interface Tour {
  id: string;
  vehicle: string;
  driver: string;
  depot: string;
  /** Leaves the depot, loaded; back at the depot. */
  from: number;
  to: number;
  /** Minutes of loading before `from`. */
  loading: number;
  km: number;
  stops: readonly Stop[];
}

/** One tour per vehicle. Stops every half hour or so, each with a two-hour
    window; every seventh arrival misses its window - the late ones a
    dispatcher looks for. */
function tour(index: number): Tour {
  const r = random(700 + index);
  const vehicle = VEHICLES[index]!;
  const truck = vehicle.type === "truck";
  const from = at(truck ? 6 : 7, index % 2 === 0 ? 0 : 30);
  const count = truck ? 5 : 8 + Math.floor(r() * 4);
  const stops: Stop[] = [];
  let arrival = from + (20 + Math.floor(r() * 15)) * MINUTE;
  for (let i = 0; i < count; i++) {
    const n = index * 20 + i;
    const slot = Math.floor((arrival - at(0)) / (2 * 60 * MINUTE)) * 2;
    /* A late stop's window closed half an hour or more before the arrival. */
    const late = i >= 2 && n % 7 === 3;
    const halfHour = 30 * MINUTE;
    const windowFrom = late ? Math.floor(arrival / halfHour) * halfHour - 5 * halfHour : at(slot);
    stops.push({
      id: `s-${index + 1}-${i + 1}`,
      shipment: `FP-${(1_004_210 + n * 13).toString()}`,
      customer: CUSTOMERS[(n * 3) % CUSTOMERS.length]!,
      window: [windowFrom, windowFrom + 2 * 60 * MINUTE],
      arrival,
    });
    arrival += (truck ? 45 : 22) * MINUTE + Math.floor(r() * 18) * MINUTE;
  }
  return {
    id: `T-${String(index + 1).padStart(2, "0")}`,
    vehicle: vehicle.id,
    driver: DRIVERS[index]!.id,
    depot: vehicle.depot,
    from,
    to: arrival + 30 * MINUTE,
    loading: truck ? 45 : 30,
    km: Math.round((truck ? 140 : 70) + r() * 40),
    stops,
  };
}

const TOURS: readonly Tour[] = VEHICLES.map((_, index) => tour(index));

/* A fixed instant suits a replay and a picture that must hold still; in a
   live application `now` alone follows the clock - the next example. */

const COLORS = ["light-dark(#2563eb, #6b9bff)", "light-dark(#0d9488, #3cc7b8)", "light-dark(#c2410c, #f08a52)", "light-dark(#7c3aed, #a98bfa)"];

const TASKS: Task[] = TOURS.map((tour, i) => ({ id: tour.id, name: tour.id, color: COLORS[i % COLORS.length]! }));

const ROUNDS: Subtask[] = TOURS.map((tour) => ({
  id: tour.id,
  task: tour.id,
  lane: tour.vehicle,
  from: tour.from,
  to: tour.to,
  leadIn: tour.loading * 60_000,
}));

export default function NowLine() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
  return (
    <Schedule ariaLabel="Today's tours, with the present" height={420} now={NOW}>
      {VEHICLES.map((vehicle) => (
        <Lane key={vehicle.id} id={vehicle.id} label={vehicle.plate} />
      ))}
      <Subtasks data={ROUNDS} tasks={TASKS} />
    </Schedule>
  );
}
```

### Follow the clock

`now` on its own reads the clock and moves on by the minute, so a plan left open on a wall screen stays true.

```tsx
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";

const HOUR = 60 * 60_000;
const today = new Date();
today.setHours(0, 0, 0, 0);
const at = (hours: number) => today.getTime() + hours * HOUR;
const current = Date.now();

const TASKS: Task[] = [
  { id: "primary", name: "Primary", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "secondary", name: "Secondary", color: "light-dark(#0d9488, #3cc7b8)" },
];

/* Today's on-call hand-over at 09:00. */
const DUTIES: Subtask[] = [
  { id: "p-1", task: "primary", lane: "ada", from: at(-15), to: at(9) },
  { id: "p-2", task: "primary", lane: "tomasz", from: at(9), to: at(33) },
  { id: "s-1", task: "secondary", lane: "tomasz", from: at(-15), to: at(9) },
  { id: "s-2", task: "secondary", lane: "leila", from: at(9), to: at(33) },
];

export default function FollowTheClock() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [current - 6 * HOUR, current + 6 * HOUR] } });
  return (
    <Schedule ariaLabel="Today's on-call rota, with the present" height={188} now>
      <Lane id="ada" label="Ada Mwangi" />
      <Lane id="tomasz" label="Tomasz Nowak" />
      <Lane id="leila" label="Leila Haddad" />
      <Subtasks data={DUTIES} tasks={TASKS} />
    </Schedule>
  );
}
```

## Keyboard

The keys of [First schedule](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#keyboard-schedule) apply here.

## Props on this page

From `ScheduleProps` — the full table stands on [First schedule](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-ScheduleProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`height`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
| [`now`](https://romanhaendler.github.io/umriss-ui/schedule/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. |
| [`children`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |

#### Accessibility

| Prop | Type | Default | Description |
|---|---|---|---|
| [`ariaLabel`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |

From `LaneProps` — the full table stands on [Lanes](https://romanhaendler.github.io/umriss-ui/schedule/lane/#type-LaneProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/lane/#LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
| [`label`](https://romanhaendler.github.io/umriss-ui/schedule/lane/#LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |

From `SubtasksProps` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-SubtasksProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`data`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#SubtasksProps-data) *required* | `readonly `[`Subtask`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
| [`tasks`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#SubtasksProps-tasks) *required* | `readonly `[`Task`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |

From `Subtask` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Subtask).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
| [`task`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
| [`lane`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
| [`from`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-from) *required* | `number` | — | Start of the main time. |
| [`to`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-to) *required* | `number` | — | End of the main time. |
| [`leadIn`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |

From `Task` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Task).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Task-id) *required* | `string` | — | The caller's identity of the task. |
| [`color`](https://romanhaendler.github.io/umriss-ui/schedule/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. |
| [`name`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Task-name) | `string` | — | What the task is called, where the schedule names it. |

From `ScheduleOptions` — the full table stands on [View](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-ScheduleOptions).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`initialView`](https://romanhaendler.github.io/umriss-ui/schedule/view/#ScheduleOptions-initialView) | [`ScheduleView`](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-ScheduleView) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The schedule remembers none: where a view is kept is the application's decision. |

From `ScheduleView` — the full table stands on [View](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-ScheduleView).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`domain`](https://romanhaendler.github.io/umriss-ui/schedule/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`. |
