# Dependencies

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/dependencies/>. 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).

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.

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

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

`leaves` and `arrives` say what a dependency connects - the main time, or the outer edge of the lead-out and the lead-in - and so decide whether it is violated. `attach`, `ends` and `route` only change how the line is drawn.

Nothing moves to make a dependency fit. [Ripple](https://romanhaendler.github.io/umriss-ui/schedule/ripple/) computes the moves that would, for the application to run or not.

## Examples

### Join the stops of a tour

Each stop is a subtask, and the drive to the next is a dependency whose `lag` is the driving time.

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

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

const TOURS: Task[] = [
  { id: "t-01", name: "T-01 Harbour", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "t-02", name: "T-02 Northfold", color: "light-dark(#0d9488, #3cc7b8)" },
];

const STOPS: Subtask[] = [
  { id: "t-01-1", task: "t-01", lane: "fp-214", from: at(7, 30), to: at(7, 50), name: "Holloway Garden Supplies" },
  { id: "t-01-2", task: "t-01", lane: "fp-214", from: at(8, 15), to: at(8, 30), name: "Marlow & Finch Books" },
  { id: "t-01-3", task: "t-01", lane: "fp-214", from: at(9), to: at(9, 20), name: "Oakridge Pharmacy" },
  { id: "t-02-1", task: "t-02", lane: "fp-377", from: at(8), to: at(8, 15), name: "Brixley Cycles" },
  { id: "t-02-2", task: "t-02", lane: "fp-377", from: at(8, 45), to: at(9, 10), name: "Tamsin's Bakery" },
];

const DRIVES: Dependency[] = [
  { id: "d-01-1", from: "t-01-1", to: "t-01-2", lag: min(20) },
  { id: "d-01-2", from: "t-01-2", to: "t-01-3", lag: min(25) },
  { id: "d-02-1", from: "t-02-1", to: "t-02-2", lag: min(25) },
];

export default function StopsOfATour() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7), at(10)] } });
  return (
    <Schedule ariaLabel="Two morning tours with their stops" height={150}>
      <Lane id="fp-214" label="Van FP 214 K" />
      <Lane id="fp-377" label="E-van FP 377 K" />
      <Dependencies data={DRIVES} />
      <Subtasks data={STOPS} tasks={TOURS} />
    </Schedule>
  );
}
```

### Choose what a dependency connects

By default the lag runs from the end of unloading to the start of loading; `leaves` and `arrives` set to `"main"` skip both.

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

/* The anchor decides whether the dependency is violated, so it should say
   what really has to wait for what. */

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

const TOURS: Task[] = [
  { id: "outer", name: "T-07 East Gate", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "inner", name: "T-08 Canal Road", color: "light-dark(#4d7c0f, #8fc43e)" },
];

const LEGS: Subtask[] = [
  { id: "o-1", task: "outer", lane: "truck", from: at(7), to: at(8, 30), leadOut: min(30) },
  { id: "o-2", task: "outer", lane: "van", from: at(10, 30), to: at(12), leadIn: min(30) },
  { id: "i-1", task: "inner", lane: "truck", from: at(12), to: at(13, 30), leadOut: min(30) },
  { id: "i-2", task: "inner", lane: "van", from: at(15), to: at(16), leadIn: min(30) },
];

const HANDOVERS: Dependency[] = [
  { id: "outer-handover", from: "o-1", to: "o-2", lag: min(60) },
  { id: "inner-handover", from: "i-1", to: "i-2", lag: min(60), leaves: "main", arrives: "main" },
];

export default function Anchors() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(16, 30)] } });
  return (
    <Schedule ariaLabel="Two handovers with different anchors" height={150}>
      <Lane id="truck" label="Truck FP 520 E" />
      <Lane id="van" label="Van FP 290 E" />
      <Dependencies data={HANDOVERS} />
      <Subtasks data={LEGS} tasks={TOURS} />
    </Schedule>
  );
}
```

### Choose where a line touches its bars

`attach="nearest"` meets the facing edges, so a short step between neighbouring lanes stays short; `"centre"` joins the middles.

```tsx
import { Stack, Text } from "@umriss-ui/core";
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task, Dependency, DependencyAttachment } from "@umriss-ui/schedule";

/* Picture only: `attach` changes no finding. Within one lane there is no
   nearer edge, and both mean the middle. */

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

const TASKS: Task[] = [{ id: "release", name: "Release 4.12", color: "light-dark(#2563eb, #6b9bff)" }];

const STEPS: Subtask[] = [
  { id: "build", task: "release", lane: "priya", from: at(7), to: at(8, 30), name: "Build" },
  { id: "review", task: "release", lane: "ada", from: at(9), to: at(10, 30), name: "Security review" },
  { id: "deploy", task: "release", lane: "sam", from: at(11), to: at(12), name: "Deploy" },
  /* Back up to Priya's lane: the step whose edges `attach` decides. */
  { id: "verify", task: "release", lane: "priya", from: at(12, 30), to: at(13), name: "Verify" },
];

const HANDOVERS: Dependency[] = [
  { id: "to-review", from: "build", to: "review", lag: min(20) },
  { id: "to-deploy", from: "review", to: "deploy", lag: min(20) },
  { id: "to-verify", from: "deploy", to: "verify", lag: min(20) },
];

function Variant({ attach }: { attach: DependencyAttachment }) {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
  return (
    <Stack gap={1}>
      <Text size="xs" tone="muted" mono>
        attach=&quot;{attach}&quot;
      </Text>
      <Schedule ariaLabel={`A release through three engineers, lines from the ${attach}`} height={188} attach={attach}>
        <Lane id="priya" label="Priya Raman" />
        <Lane id="ada" label="Ada Mwangi" />
        <Lane id="sam" label="Sam Okafor" />
        <Dependencies data={HANDOVERS} />
        <Subtasks data={STEPS} tasks={TASKS} />
      </Schedule>
    </Stack>
  );
}

export default function Attach() {
  return (
    <Stack gap={4}>
      <Variant attach="centre" />
      <Variant attach="nearest" />
    </Stack>
  );
}
```

### Mark the ends with a dot

`ends="dot"` marks where each line is anchored, a help while reading; `"none"` keeps a plan of many short handovers quiet.

```tsx
import { Stack, Text } from "@umriss-ui/core";
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task, Dependency, DependencyEnds } from "@umriss-ui/schedule";

/* Picture only, like `route` and `attach`; a single dependency may say
   otherwise for itself. */

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

const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#2563eb, #6b9bff)" }];

const WORK: Subtask[] = [
  { id: "design", task: "portal", lane: "noah", from: at(7), to: at(8), name: "Design" },
  { id: "build", task: "portal", lane: "arjun", from: at(8, 30), to: at(9, 30), name: "Build" },
  { id: "test", task: "portal", lane: "eva", from: at(10), to: at(11), name: "Test" },
];

const HANDOVERS: Dependency[] = [
  { id: "to-build", from: "design", to: "build", lag: min(15) },
  { id: "to-test", from: "build", to: "test", lag: min(15) },
];

function Variant({ ends }: { ends: DependencyEnds }) {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
  return (
    <Stack gap={1}>
      <Text size="xs" tone="muted" mono>
        ends=&quot;{ends}&quot;
      </Text>
      <Schedule ariaLabel={`Design, build and test, ends ${ends}`} height={188} attach="nearest" ends={ends}>
        <Lane id="noah" label="Noah Fischer" />
        <Lane id="arjun" label="Arjun Mehta" />
        <Lane id="eva" label="Eva Novak" />
        <Dependencies data={HANDOVERS} />
        <Subtasks data={WORK} tasks={PROJECTS} />
      </Schedule>
    </Stack>
  );
}

export default function Ends() {
  return (
    <Stack gap={4}>
      <Variant ends="dot" />
      <Variant ends="none" />
    </Stack>
  );
}
```

### Show a lag that does not fit

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.

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

/* Whether the van waits or the truck leaves earlier is the planner's
   decision - `ripple` offers the arithmetic for the first. */

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

const TOURS: Task[] = [{ id: "t-03", name: "T-03 Old Town", color: "light-dark(#c2410c, #f08a52)" }];

const LEGS: Subtask[] = [
  { id: "line-haul", task: "t-03", lane: "truck", from: at(7), to: at(8, 30), leadOut: min(5) },
  { id: "round", task: "t-03", lane: "van", from: at(9, 15), to: at(10, 30), leadIn: min(15) },
];

const HANDOVERS: Dependency[] = [{ id: "transfer", from: "line-haul", to: "round", lag: min(40) }];

export default function ViolatedDependency() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
  return (
    <Schedule ariaLabel="A handover from the truck to the van that is too short" height={150}>
      <Lane id="truck" label="Truck FP 118 R" />
      <Lane id="van" label="Van FP 214 K" />
      <Dependencies data={HANDOVERS} />
      <Subtasks data={LEGS} tasks={TOURS} />
    </Schedule>
  );
}
```

### Draw one handover its own way

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.

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

/* Picture only, like the schedule's own `attach` and `ends`: the finding
   follows from `leaves` and `arrives` alone. */

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

const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#2563eb, #6b9bff)" }];

const WORK: Subtask[] = [
  { id: "design", task: "portal", lane: "noah", from: at(7), to: at(8), name: "Design" },
  { id: "build", task: "portal", lane: "arjun", from: at(8, 30), to: at(9, 30), name: "Build" },
  { id: "test", task: "portal", lane: "eva", from: at(10), to: at(11), name: "Test" },
  { id: "sign-off", task: "portal", lane: "noah", from: at(11, 30), to: at(12), name: "Sign-off" },
];

const HANDOVERS: Dependency[] = [
  { id: "to-build", from: "design", to: "build", lag: min(15) },
  { id: "to-test", from: "build", to: "test", lag: min(15) },
  /* The one that matters: the designer signs off what was tested. */
  { id: "to-sign-off", from: "test", to: "sign-off", lag: min(15), attach: "centre", ends: "dot" },
];

export default function OneHandoverDrawnItsOwnWay() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(12, 30)] } });
  return (
    <Schedule ariaLabel="Design, build, test and a sign-off drawn its own way" height={188} attach="nearest" ends="none">
      <Lane id="noah" label="Noah Fischer" />
      <Lane id="arjun" label="Arjun Mehta" />
      <Lane id="eva" label="Eva Novak" />
      <Dependencies data={HANDOVERS} />
      <Subtasks data={WORK} tasks={PROJECTS} />
    </Schedule>
  );
}
```

## When to use something else

- The shape of the line → Routes

## Keyboard

| Key | Action |
|---|---|
| `] or t` | Out along the active subtask's dependency; pressed again, on to the subtask it arrives at. |
| `[ or Shift+T` | Back along the dependency to the subtask it leaves. |

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

## API

### `DependenciesProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `data` *required* | `readonly `[`Dependency`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#type-Dependency)`[]` | — | The dependencies, as the caller holds them.<br>Shown in: [Join the stops of a tour](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour), [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Choose where a line touches its bars](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#attach) and 28 more |

### `Dependency`

| Prop | Type | Default | Description |
|---|---|---|---|
| `id` *required* | `string` | — | The caller's identity of the dependency.<br>Shown in: [Join the stops of a tour](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour), [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Choose where a line touches its bars](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#attach) and 27 more |
| `from` *required* | `string` | — | The id of the subtask it leaves.<br>Shown in: [Join the stops of a tour](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour), [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Choose where a line touches its bars](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#attach) and 27 more |
| `to` *required* | `string` | — | The id of the subtask it arrives at.<br>Shown in: [Join the stops of a tour](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour), [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Choose where a line touches its bars](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#attach) and 27 more |
| `lag` *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends.<br>Shown in: [Join the stops of a tour](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#stops-of-a-tour), [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Choose where a line touches its bars](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#attach) and 27 more |
| `leaves` | `"main" \| "leadOut"` | `"leadOut"` | Where it leaves: at the end of the main time, or after the lead-out. The predecessor counts as ended once its lead-out is.<br>Shown in: [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [Plan a day in the machine shop (First schedule)](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#machine-plan), [Replan the day on the line (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#replan-the-day-on-the-line) |
| `arrives` | `"main" \| "leadIn"` | `"leadIn"` | Where it arrives: at the start of the main time, or before the lead-in. The lag must have run out before the successor's lead-in begins.<br>Shown in: [Choose what a dependency connects](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#anchors), [A day of tours (First schedule)](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#first-schedule), [Plan a day in the machine shop (First schedule)](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#machine-plan) and 1 more |
| `route` | [`DependencyRoute`](https://romanhaendler.github.io/umriss-ui/schedule/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.<br>Shown in: [Per dependency (Routes)](https://romanhaendler.github.io/umriss-ui/schedule/routes/#per-dependency) |
| `attach` | [`DependencyAttachment`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#type-DependencyAttachment)<br>`"centre" \| "nearest"` | — | Where on the bars this one's line attaches, where it does not attach like the rest. Also picture only, and not to be confused with the two anchors above: those say what the dependency connects, this says where the line touches.<br>Shown in: [Draw one handover its own way](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#one-handover-drawn-its-own-way) |
| `ends` | [`DependencyEnds`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#type-DependencyEnds)<br>`"dot" \| "none"` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only.<br>Shown in: [Draw one handover its own way](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#one-handover-drawn-its-own-way) |

### Types on this page

#### `DependencyRoute`

How a dependency is drawn between its two ends: a curve that leaves and
arrives forwards, a straight line, or axis-parallel segments.

```ts
type DependencyRoute = "curve" | "straight" | "orthogonal";
```

#### `DependencyAttachment`

Where on its bars a dependency's line attaches: the middle of both, or the
edge that faces the other stop - which is the shortest line between them.

Deliberately not called an anchor: a dependency already HAS two anchors
(`leaves`, `arrives`), and those decide what it connects and therefore
whether it is violated. This decides where the line touches, and nothing else.
The tree has an **Anchor** of its own as well (CONTEXT.md).

```ts
type DependencyAttachment = "centre" | "nearest";
```

#### `DependencyEnds`

Whether a dependency's two ends carry a dot. The dot says where the line is
anchored, which is worth saying while a plan is being read and is noise in a
plan full of short moves - so it is the caller's choice.

```ts
type DependencyEnds = "dot" | "none";
```

## Known limits

- Only finish-to-start within one task; no dependency types and no critical path ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
