# Findings as data

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

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.

```ts
import { findings, overlaps, violatedDependencies, inBlockedTime } from "@umriss-ui/schedule";
```

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

`findings(subtasks, dependencies, blocked)` returns all three kinds; [`overlaps`](https://romanhaendler.github.io/umriss-ui/schedule/api/#overlaps), [`violatedDependencies`](https://romanhaendler.github.io/umriss-ui/schedule/api/#violatedDependencies) and [`inBlockedTime`](https://romanhaendler.github.io/umriss-ui/schedule/api/#inBlockedTime) return one each. They are pure functions: run them wherever the data changes, with or without a schedule on screen.

Lead-in and lead-out count as occupied time; touching at a shared edge is no overlap.

## Examples

### List the findings

[`findings`](https://romanhaendler.github.io/umriss-ui/schedule/api/#findings) returns what the schedule marks - overlaps, violated dependencies, work in blocked time - as data for a list beside it.

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

const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();

const PROJECTS: readonly Task[] = [
  { id: "shop", name: "Online shop relaunch", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
];

const WORK: readonly Subtask[] = [
  { id: "design", task: "shop", lane: "noah", from: day(16), to: day(17, 17), name: "Search results layout" },
  { id: "build", task: "shop", lane: "chloe", from: day(17), to: day(19, 17), name: "Search results page" },
  { id: "address", task: "portal", lane: "chloe", from: day(18), to: day(19, 17), name: "Change of address form" },
  { id: "profile", task: "portal", lane: "arjun", from: day(17), to: day(19, 17), name: "Profile page" },
];

/* The build may start only a morning after the design is done. */
const HANDOVERS: readonly Dependency[] = [{ id: "design-to-build", from: "design", to: "build", lag: 4 * 60 * 60_000 }];

const LEAVE: readonly BlockedTime[] = [{ id: "arjun-holiday", lane: "arjun", from: day(18, 0), to: day(21, 0), label: "Holiday" }];

const when = (instant: number) => new Date(instant).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit" });
const hours = (ms: number) => Math.round(ms / 3_600_000);

export default function ListTheFindings() {
  const { Schedule, Lane, Subtasks, Dependencies, BlockedTimes } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
  const found = findings(WORK, HANDOVERS, LEAVE);

  return (
    <Stack gap={3}>
      <Schedule ariaLabel="Three people's week with its findings" height={184}>
        <Lane id="noah" label="Noah Fischer" />
        <Lane id="chloe" label="Chloe Durand" />
        <Lane id="arjun" label="Arjun Mehta" />
        <BlockedTimes data={LEAVE} />
        <Dependencies data={HANDOVERS} />
        <Subtasks data={WORK} tasks={PROJECTS} />
      </Schedule>
      <ul style={{ margin: 0, paddingLeft: 18 }}>
        {found.overlaps.map((o) => (
          <Text as="li" size="sm" key={`${o.first}/${o.second}`}>
            Double-booked: {o.lane} has {o.first} and {o.second} from {when(o.from)} to {when(o.to)}
          </Text>
        ))}
        {found.violatedDependencies.map((v) => (
          <Text as="li" size="sm" key={v.dependency}>
            Too early: {v.dependency} leaves {when(v.departure)} for a start at {when(v.arrival)}, {hours(v.shortBy)} hours short
          </Text>
        ))}
        {found.inBlockedTime.map((b) => (
          <Text as="li" size="sm" key={`${b.subtask}/${b.blocked}`}>
            In leave: {b.subtask} covers {b.blocked} from {when(b.from)} to {when(b.to)}
          </Text>
        ))}
      </ul>
    </Stack>
  );
}
```

### Say when there is nothing to fix

When all three lists [`findings`](https://romanhaendler.github.io/umriss-ui/schedule/api/#findings) returns are empty, say so plainly instead of showing an empty list.

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

const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();

const PROJECTS: readonly Task[] = [{ id: "booking", name: "Booking app", color: "light-dark(#0d9488, #3cc7b8)" }];

const WORK: readonly Subtask[] = [
  { id: "w-111", task: "booking", lane: "hana", from: day(16), to: day(18, 17), name: "Push notifications" },
  { id: "w-113", task: "booking", lane: "kofi", from: day(16), to: day(18, 17), name: "Calendar sync" },
];

export default function NothingToFix() {
  const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
  const found = findings(WORK, []);
  const count = found.overlaps.length + found.violatedDependencies.length + found.inBlockedTime.length;

  return (
    <Stack gap={3}>
      <Schedule ariaLabel="Two developers' week, without findings" height={140}>
        <Lane id="hana" label="Hana Sato" />
        <Lane id="kofi" label="Kofi Mensah" />
        <Subtasks data={WORK} tasks={PROJECTS} />
      </Schedule>
      {count === 0 && <EmptyState title="Nothing to fix" description="Nobody is double-booked, every hand-over fits, and no work falls into leave." />}
    </Stack>
  );
}
```

### Count the findings per kind

[`overlaps`](https://romanhaendler.github.io/umriss-ui/schedule/api/#overlaps) and [`violatedDependencies`](https://romanhaendler.github.io/umriss-ui/schedule/api/#violatedDependencies) are the halves of [`findings`](https://romanhaendler.github.io/umriss-ui/schedule/api/#findings) on their own - here as counts above the day's legs.

```tsx
import { Badge, Stack } from "@umriss-ui/core";
import { overlaps, useSchedule, violatedDependencies } 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 VEHICLES = [
  { id: "van-402", label: "Van FP 402 R" },
  { id: "truck-520", label: "Truck FP 520 E" },
  { id: "van-455", label: "E-van FP 455 R" },
];

const CONSIGNMENTS: readonly Task[] = [
  { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
];

const LEGS: readonly Subtask[] = [
  { id: "c-2041-2", task: "c-2041", lane: "van-402", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
  { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
  { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(10), to: at(11, 30), leadIn: min(15) },
  { id: "c-2043-3", task: "c-2043", lane: "van-455", from: at(12), to: at(14), leadIn: min(20), leadOut: min(20) },
];

const TRANSFERS: readonly Dependency[] = [
  { id: "t-2043-1", from: "c-2043-1", to: "c-2043-2", lag: min(45) },
  { id: "t-2043-2", from: "c-2043-2", to: "c-2043-3", lag: min(25) },
];

export default function CountPerKind() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
  const doubleBooked = overlaps(LEGS).length;
  const late = violatedDependencies(LEGS, TRANSFERS).length;

  return (
    <Stack gap={3}>
      <Stack direction="row" gap={2}>
        <Badge tone={doubleBooked > 0 ? "warning" : "success"}>{doubleBooked} double-booked</Badge>
        <Badge tone={late > 0 ? "warning" : "success"}>{late} transfers too short</Badge>
      </Stack>
      <Schedule ariaLabel="The day's legs with their findings counted" height={196}>
        {VEHICLES.map((vehicle) => (
          <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
        ))}
        <Dependencies data={TRANSFERS} />
        <Subtasks data={LEGS} tasks={CONSIGNMENTS} />
      </Schedule>
    </Stack>
  );
}
```

## Keyboard

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

## API

### `Overlap`

| Prop | Type | Default | Description |
|---|---|---|---|
| `lane` *required* | `string` | — | The lane both sit on.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Refuse a move that double-books someone (Move and lane)](https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/#refuse-a-double-booking) |
| `first` *required* | `string` | — | The subtask that starts earlier (lead-in included); on a tie, the one that stands first in the data.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Refuse a move that double-books someone (Move and lane)](https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/#refuse-a-double-booking) |
| `second` *required* | `string` | — | The other one.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Refuse a move that double-books someone (Move and lane)](https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/#refuse-a-double-booking) |
| `from` *required* | `number` | — | Where the covering begins.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |
| `to` *required* | `number` | — | Where it ends.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |

### `ViolatedDependency`

| Prop | Type | Default | Description |
|---|---|---|---|
| `dependency` *required* | `string` | — | The id of the dependency.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |
| `departure` *required* | `number` | — | When it leaves, by its anchor.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |
| `arrival` *required* | `number` | — | When it has to have arrived, by its anchor.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |
| `shortBy` *required* | `number` | — | How much time is missing: departure plus lag, minus arrival.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings) |

### `InBlockedTime`

| Prop | Type | Default | Description |
|---|---|---|---|
| `subtask` *required* | `string` | — | The id of the subtask.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Block out leave and maintenance (Blocked time)](https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/#leave), [Check a sprint against the leave calendar (Blocked time)](https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/#team-leave) and 2 more |
| `blocked` *required* | `string` | — | The id of the blocked time it covers.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Block out leave and maintenance (Blocked time)](https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/#leave), [Plan the sprint around leave (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#plan-the-sprint-around-leave) |
| `lane` *required* | `string` | — | The lane both are on.<br>Shown in: [Hand over the on-call week (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#hand-over-the-on-call-week), [Plan the sprint around leave (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#plan-the-sprint-around-leave) |
| `from` *required* | `number` | — | Where the covering begins.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Block out leave and maintenance (Blocked time)](https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/#leave), [Hand over the on-call week (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#hand-over-the-on-call-week) |
| `to` *required* | `number` | — | Where it ends.<br>Shown in: [List the findings](https://romanhaendler.github.io/umriss-ui/schedule/findings/#list-the-findings), [Block out leave and maintenance (Blocked time)](https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/#leave), [Hand over the on-call week (Scenarios)](https://romanhaendler.github.io/umriss-ui/schedule/#hand-over-the-on-call-week) |

## Known limits

- Overlapping work is marked, not packed into sub-lanes; stacked overlap is not built yet.

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