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.

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

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 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.

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.

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.

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.

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.

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.

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

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 apply here.

API

DependenciesProps

NameTypeDefaultDescription
datarequiredreadonly Dependency[]—The dependencies, as the caller holds them.

Shown in: Join the stops of a tour, Choose what a dependency connects, Choose where a line touches its bars and 28 more

Dependency

NameTypeDefaultDescription
idrequiredstring—The caller's identity of the dependency.

Shown in: Join the stops of a tour, Choose what a dependency connects, Choose where a line touches its bars and 27 more

fromrequiredstring—The id of the subtask it leaves.

Shown in: Join the stops of a tour, Choose what a dependency connects, Choose where a line touches its bars and 27 more

torequiredstring—The id of the subtask it arrives at.

Shown in: Join the stops of a tour, Choose what a dependency connects, Choose where a line touches its bars and 27 more

lagrequirednumber—The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends.

Shown in: Join the stops of a tour, Choose what a dependency connects, Choose where a line touches its bars 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.

Shown in: Choose what a dependency connects, Plan a day in the machine shop (First schedule), Replan the day on the line (Scenarios)

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.

Shown in: Choose what a dependency connects, A day of tours (First schedule), Plan a day in the machine shop (First schedule) and 1 more

routeDependencyRoute
| "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.

Shown in: Per dependency (Routes)

attachDependencyAttachment
"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.

Shown in: Draw one handover its own way

endsDependencyEnds
"dot" | "none"
—Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only.

Shown in: Draw one handover 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.

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).

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.

type DependencyEnds = "dot" | "none";

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/schedule

Getting started

Plan

Time

Reading

Editing

Measuring

API index