OPERATOR

Maintenance Planning

An industrial shutdown plan for fictional North Process Works. Nine work orders share five maintenance crews during the Unit 4 downtime window, 2026-10-06 06:00–18:00 UTC. Resource lanes show service windows and reserved crew time; the selected bearing replacement exposes a crew conflict and its dependent inspection.

Interactive product exampleOpen workspace ↗

Plant maintenance

North Process Works · Unit 4

Planned downtime · service windows and crew allocation

Planning window

2026-10-06 06:00–18:00 UTC

9 work orders · 5 resources · plan-12

Resource allocation

1 resource conflict

Conflict review

PM-2401 and PM-2402 need Mechanical crew A at the same time. Reassign a compatible resource or adjust the service window.

Resource / assignment
06:00 UTC08:0010:0012:0014:0016:0018:00

Mechanical crew A

Rotating equipment · 4 technicians

7 h reserved

Mechanical crew B

Mechanical service · 3 technicians

6 h reserved

Inspection crew

Condition & pressure · 2 inspectors

5.5 h reserved

Electrical crew

Electrical · 2 technicians

4 h reserved

Stores & isolation

Work packs · 2 coordinators

5 h reserved

All times are UTC. A resource may start its next work order exactly when the previous order ends.

Fictional maintenance plan. Changes stay in this page session.

Resolve the selected conflict

  1. Inspect Bearing replacement (PM-2402) and its overlap with Seal inspection (PM-2401) on Mechanical crew A.
  2. Choose Edit work window, then change Resource to Mechanical crew B. Keep the 2026-10-06 09:00–12:00 UTC window.
  3. Choose Review change. Compare the old and proposed resource, exact UTC interval, work order, and source revision.
  4. Choose Apply schedule change. The local adapter accepts a new revision, updates the lane, and clears the conflict.

Changing the end to 13:00 UTC instead exposes Vibration baseline (PM-2405), which starts at 12:30 UTC. The bearing replacement must finish before that inspection begins. Its own prerequisite, Bearing kit release (PM-2408), ends at 08:00 UTC. Only the two compatible mechanical crews appear in the resource editor.

Canonical usage

The recipe uses a supplied, revisioned snapshot. The application owns the save service and replaces its source only after an accepted response passes the recipe's checks.

import { useState } from "react";
import {
  MaintenancePlanning,
  type ScheduleApplyRequest,
  type ScheduleApplyResult,
  type ScheduleSnapshot,
} from "./patterns/planning-scheduling";
 
export function MaintenancePlan({
  initialSnapshot,
  applyChange,
}: {
  initialSnapshot: ScheduleSnapshot;
  applyChange: (request: ScheduleApplyRequest) => Promise<ScheduleApplyResult>;
}) {
  const [snapshot, setSnapshot] = useState(initialSnapshot);
 
  return (
    <MaintenancePlanning
      key={snapshot.id}
      snapshot={snapshot}
      initialSelectedJobId={snapshot.jobs[0]?.id}
      onApply={applyChange}
      onAccepted={setSnapshot}
    />
  );
}

snapshot contains the planning window, resource names, work orders, source ID and revision. Each work order supplies its owner, asset, start and end instants, resource ID, prerequisite IDs and optional eligibleResourceIds. When eligibility is supplied, only compatible resources appear in the editor. IDs must be unique and stable. Supply valid finite instants within the planning window and an acyclic dependency set; this recipe edits an existing valid data model.

onApply receives scheduleId, expectedRevision, requestId, jobId, resourceId, start and end. Its result is one of:

  • { status: "accepted", requestId, snapshot }, with a new revision and the exact reviewed assignment.
  • { status: "rejected", requestId, message }, for a change the service declines.
  • { status: "conflict", requestId, message }, when the source or availability has changed.

The application must validate authority, current revision, resource suitability and conflicts again at the persistence boundary. Callback invocation is not acceptance. Rejections, unknown outcomes, mismatched request IDs and inconsistent accepted records retain the proposed change. onAccepted publishes a validated accepted snapshot to the host. Omitting onApply leaves the editor in a clearly labeled review-only mode.

Time and dependency rules

All instants are epoch milliseconds. The display and form use UTC explicitly; the device's local timezone does not reinterpret the input. The form accepts minute precision and rejects incomplete values, impossible dates, an end at or before the start, and windows outside the supplied planning horizon.

Reservations use half-open intervals, [start, end). One work order may start exactly when another on the same resource ends. Overlaps are tested against exact instants. Review checks the changed work order's resource, its prerequisite end times, and the start times of its direct dependents. It does not move downstream work automatically.

Overlapping work occupies separate visual tracks within its resource lane. Each lane shows its reserved work hours; overlapping bookings count separately until their conflict is resolved. Below a 640px workspace width, the view defaults to a full-width list with the asset, crew and UTC service window on each work order. Schedule remains available in a named, horizontally scrollable timeline. List preserves start-time order and the same selection and edit route. Every work order is a named button; no drag gesture is needed.

Drafts and source changes

Selecting another work order while editing keeps the draft until the user chooses Keep editing or Discard change and open work order. A changed source ID or revision blocks review and apply. Load latest revision discards the old draft, after which a new edit uses the current source.

An in-flight response cannot replace a newer source or an unmounted session. A pending submission blocks duplicate apply and changing work orders. The host owns cancellation, routing and service-side reconciliation. Remount with a stable schedule ID when switching between independent schedules.

The reference demo accepts changes in memory and labels that boundary. Reloading resets the plan. The snapshot supplies the planning horizon; the host remains responsible for validating actual downtime approvals, isolations and crew capacity. The recipe includes no network calls, recurring jobs, automatic optimization, bulk rescheduling or capacity forecasts.

Composition and ownership

The schedule and change model live in the copied recipe. Inspector and EntityPreview show the selected work; Field, Input, Select, Button and Badge provide the controls and statuses. The dependency list is a direct impact view of the supplied records.

Unit tests cover interval boundaries, resource overlap and compatibility, invalid windows, prerequisites, downstream impact, UTC parsing and timeline positioning. Interaction tests cover selection, review/apply, rejection, source replacement, stale responses, draft preservation, keyboard focus and the public industrial conflict workflow. Private regression fixtures retain coverage for the reusable scheduling engine. Axe checks cover both the initial schedules and edit forms; contrast and responsive focus appearance require the shared token and browser checks.

Copy the recipe

import { MaintenancePlanningDemo } from "./patterns/planning-scheduling";

<MaintenancePlanningDemo />
View recipe source (6 files)

Copy all files into your app, preserving their relative paths.

Package imports: @operator-ds/react, react.

patterns/planning-scheduling/index.ts

export {
  PlanningScheduling,
  PlanningScheduling as MaintenancePlanning,
  type PlanningSchedulingProps,
  type PlanningSchedulingProps as MaintenancePlanningProps,
} from "./planning-scheduling";
export { MaintenancePlanningDemo, PlanningSchedulingDemo } from "./demo";
export { createScheduleFixture } from "./fixtures";
export {
  conflictsFor,
  layoutLane,
  parseUtcInput,
  utcInput,
  utcTime,
  utcDate,
  utcWindow,
  validateChange,
} from "./schedule-state";
export type {
  ScheduleResource,
  ScheduleJob,
  ScheduleSnapshot,
  ScheduleChange,
  ScheduleApplyRequest,
  ScheduleApplyResult,
  ScheduleIssue,
} from "./schedule-state";

patterns/planning-scheduling/planning-scheduling.tsx

"use client";

import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import {
  Badge,
  Button,
  EntityPreview,
  Field,
  Input,
  Inspector,
  Select,
  SelectOption,
} from "@operator-ds/react";
import {
  conflictsFor,
  layoutLane,
  parseUtcInput,
  utcInput,
  utcTime,
  utcWindow,
  validateChange,
  type ScheduleApplyRequest,
  type ScheduleApplyResult,
  type ScheduleChange,
  type ScheduleIssue,
  type ScheduleJob,
  type ScheduleSnapshot,
} from "./schedule-state";

export type PlanningSchedulingProps = {
  snapshot: ScheduleSnapshot;
  initialSelectedJobId?: string;
  onApply?: (request: ScheduleApplyRequest) => Promise<ScheduleApplyResult>;
  onAccepted?: (snapshot: ScheduleSnapshot) => void;
  acceptanceMessage?: string;
};
type Draft = {
  scheduleId: string;
  jobId: string;
  revision: string;
  resourceId: string;
  start: string;
  end: string;
};
const focusStyle = "outline-none focus-visible:shadow-oprt-focus focus-visible:z-10";
/** The selected work order is shown by a 3 px bar (Highlight in forced colors) as well as a tint. */
const selectedBar =
  "before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-oprt-accent forced-colors:before:bg-[Highlight]";
/** Layout is measured before paint where there is a document, so a phone never shows the timeline first. */
const useBrowserLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
/** A number stays with its unit. */
const hours = (value: number) => `${value}\u00A0h`;

/** A controlled schedule. The host owns source revisions and accepted persistence. */
export function PlanningScheduling({
  snapshot,
  initialSelectedJobId,
  onApply,
  onAccepted,
  acceptanceMessage = "Schedule change accepted by the supplied service.",
}: PlanningSchedulingProps) {
  const instance = useId();
  const [selectedId, setSelectedId] = useState(
    () =>
      snapshot.jobs.find((job) => job.id === initialSelectedJobId)?.id ??
      snapshot.jobs[0]?.id ??
      "",
  );
  // No fallback to another order: a revision that drops the selection says so (see the inspector).
  const selected = snapshot.jobs.find((job) => job.id === selectedId);
  const [viewChoice, setView] = useState<"schedule" | "list" | null>(null);
  const [narrow, setNarrow] = useState(false);
  const workspace = useRef<HTMLElement>(null);
  const view = viewChoice ?? (narrow ? "list" : "schedule");
  useBrowserLayoutEffect(() => {
    const element = workspace.current;
    if (!element) return;
    // Measured now, before paint; a box with no width yet (hidden, or no layout) says nothing.
    if (element.clientWidth > 0) setNarrow(element.clientWidth < 640);
    if (typeof ResizeObserver === "undefined") return;
    const observer = new ResizeObserver(([entry]) => {
      if (entry) setNarrow(entry.contentRect.width < 640);
    });
    observer.observe(element);
    return () => observer.disconnect();
  }, []);
  const [draft, setDraft] = useState<Draft | null>(null);
  const [review, setReview] = useState<ScheduleChange | null>(null);
  const [issues, setIssues] = useState<ScheduleIssue[]>([]);
  const [failure, setFailure] = useState("");
  const [notice, setNotice] = useState("");
  const [pending, setPending] = useState(false);
  const [requestedSelection, setRequestedSelection] = useState<string | null>(null);
  const inspector = useRef<HTMLElement>(null);
  const keepEditing = useRef<HTMLButtonElement>(null);
  const selectionRequested = useRef(false);
  useLayoutEffect(() => {
    if (selectionRequested.current) {
      inspector.current?.focus();
      selectionRequested.current = false;
    }
  }, [selectedId]);
  const source = useRef(snapshot);
  source.current = snapshot;
  const mounted = useRef(true);
  const sequence = useRef(0);
  const editButton = useRef<HTMLButtonElement>(null);
  const formRef = useRef<HTMLFormElement>(null);
  const reviewRef = useRef<HTMLElement>(null);
  const returnToEdit = useRef(false);
  const stale =
    draft !== null && (draft.revision !== snapshot.revision || draft.scheduleId !== snapshot.id);
  useEffect(() => {
    mounted.current = true;
    return () => {
      mounted.current = false;
      sequence.current += 1;
    };
  }, []);
  useEffect(() => {
    sequence.current += 1;
    setPending(false);
  }, [snapshot.id, snapshot.revision]);
  // The form takes focus when an edit starts (or a different order is edited) and when review
  // returns to editing, not on every keystroke: the effect reads two primitives, not the draft.
  const editingJobId = draft?.jobId ?? null;
  const editing = draft !== null;
  useEffect(() => {
    if (editing && !review) formRef.current?.querySelector<HTMLButtonElement>("button")?.focus();
  }, [editing, editingJobId, review]);
  useEffect(() => {
    if (review) reviewRef.current?.focus();
  }, [review]);
  // A tap on another order while a draft is open is answered where the operator can see it.
  useEffect(() => {
    if (!requestedSelection) return;
    keepEditing.current?.focus({ preventScroll: true });
    keepEditing.current?.scrollIntoView?.({ block: "nearest" });
  }, [requestedSelection]);
  useEffect(() => {
    if (!draft && returnToEdit.current) {
      editButton.current?.focus();
      returnToEdit.current = false;
    }
  }, [draft]);

  const conflictedJobs = snapshot.jobs.filter((job) => conflictsFor(snapshot.jobs, job).length > 0);
  const conflictPairs =
    conflictedJobs.reduce((total, job) => total + conflictsFor(snapshot.jobs, job).length, 0) / 2;
  const firstConflict = conflictedJobs[0];
  const firstConflictPeer = firstConflict
    ? snapshot.jobs.find((job) => job.id === conflictsFor(snapshot.jobs, firstConflict)[0])
    : undefined;
  const resourceName = (id: string) =>
    snapshot.resources.find((resource) => resource.id === id)?.name ?? id;
  const jobLabel = (job: ScheduleJob) =>
    `Select ${job.id}, ${job.name}, ${utcWindow(job)}, ${resourceName(job.resourceId)}${
      conflictsFor(snapshot.jobs, job).length ? ", resource conflict" : ""
    }`;
  const choose = (id: string) => {
    if (draft && id !== selected?.id) {
      setRequestedSelection(id);
      return;
    }
    selectionRequested.current = id !== selectedId;
    if (id === selectedId) inspector.current?.focus();
    setSelectedId(id);
    setNotice("");
    setFailure("");
  };
  const closeEditor = () => {
    sequence.current += 1;
    returnToEdit.current = true;
    setPending(false);
    setDraft(null);
    setReview(null);
    setIssues([]);
    setFailure("");
    setRequestedSelection(null);
  };
  const beginEdit = () => {
    if (!selected) return;
    setDraft({
      scheduleId: snapshot.id,
      jobId: selected.id,
      revision: snapshot.revision,
      resourceId: selected.resourceId,
      start: utcInput(selected.start),
      end: utcInput(selected.end),
    });
    setReview(null);
    setIssues([]);
    setFailure("");
    setNotice("");
  };
  const reviewChange = () => {
    if (!draft || stale) return;
    const proposed = {
      jobId: draft.jobId,
      resourceId: draft.resourceId,
      start: parseUtcInput(draft.start) ?? NaN,
      end: parseUtcInput(draft.end) ?? NaN,
    };
    const nextIssues = validateChange(snapshot, proposed);
    setIssues(nextIssues);
    setFailure("");
    if (nextIssues.length === 0) setReview(proposed);
  };
  const apply = async () => {
    if (!review || !draft || stale || pending || !onApply) return;
    const nextIssues = validateChange(snapshot, review);
    if (nextIssues.length) {
      setReview(null);
      setIssues(nextIssues);
      return;
    }
    const requestSequence = ++sequence.current;
    const request: ScheduleApplyRequest = {
      ...review,
      scheduleId: snapshot.id,
      expectedRevision: draft.revision,
      requestId: `${instance}-${requestSequence}`,
    };
    setPending(true);
    setFailure("");
    try {
      const result = await onApply(request);
      if (
        !mounted.current ||
        sequence.current !== requestSequence ||
        source.current.id !== request.scheduleId ||
        source.current.revision !== request.expectedRevision
      )
        return;
      if (!result || result.requestId !== request.requestId) {
        setFailure(
          "The service response did not match this request. Your proposed change is retained.",
        );
        return;
      }
      if (result.status === "rejected" || result.status === "conflict") {
        setFailure(
          result.message || "The change was not accepted. Review the source and try again.",
        );
        return;
      }
      if (result.status !== "accepted") {
        setFailure("The service returned an unknown outcome. Your proposed change is retained.");
        return;
      }
      const accepted = result.snapshot;
      const job = accepted?.jobs?.find((item) => item.id === request.jobId);
      if (
        !accepted ||
        accepted.id !== request.scheduleId ||
        typeof accepted.revision !== "string" ||
        !accepted.revision ||
        accepted.revision === request.expectedRevision ||
        !Array.isArray(accepted.resources) ||
        !job ||
        job.resourceId !== request.resourceId ||
        job.start !== request.start ||
        job.end !== request.end ||
        validateChange(accepted, request).length > 0
      ) {
        setFailure(
          "The service returned an inconsistent schedule. Your proposed change is retained.",
        );
        return;
      }
      onAccepted?.(accepted);
      returnToEdit.current = true;
      setDraft(null);
      setReview(null);
      setNotice(acceptanceMessage);
    } catch {
      if (mounted.current && sequence.current === requestSequence)
        setFailure(
          "The schedule service could not be reached. Your proposed change is retained; retry or return to editing.",
        );
    } finally {
      if (mounted.current && sequence.current === requestSequence) setPending(false);
    }
  };

  return (
    <section
      ref={workspace}
      aria-label={snapshot.title}
      className="not-prose min-w-0 border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base normal-case text-oprt-text"
    >
      <header className="flex flex-wrap items-start justify-between gap-3u border-b border-oprt-border p-3u">
        <div className="min-w-0 space-y-1u">
          <h2 className="m-0 text-oprt-xl font-semibold normal-case tracking-normal">
            {snapshot.title}
          </h2>
          <p className="m-0 text-oprt-text-dim">{snapshot.location}</p>
          <p className="m-0 text-oprt-text-dim">{snapshot.summary}</p>
        </div>
        <div className="space-y-1u text-oprt-text-dim">
          <p className="m-0 text-oprt-sm font-medium uppercase tracking-oprt-sm">Planning window</p>
          <p className="m-0 font-mono text-oprt-text">{utcWindow(snapshot)}</p>
          <p className="m-0">
            {snapshot.jobs.length} work orders · {snapshot.resources.length} resources ·{" "}
            <span className="font-mono">{snapshot.revision}</span>
          </p>
        </div>
      </header>
      {/* Beside or under the schedule by the container's width, like the list/timeline switch. A 1 px gap over
          the border color is the divider, so it is right in both arrangements. */}
      <div className="flex min-w-0 flex-wrap gap-px bg-oprt-border forced-colors:bg-[CanvasText]">
        <div className="min-w-0 flex-[999_1_36rem] bg-oprt-surface-1">
          <div className="flex flex-wrap items-center justify-between gap-2u border-b border-oprt-border px-3u py-2u">
            <div className="flex flex-wrap items-center gap-2u">
              <h3 className="m-0 text-oprt-md font-medium">Resource allocation</h3>
              <Badge appearance="text" variant={conflictPairs ? "warning" : "success"}>
                {conflictPairs
                  ? `${conflictPairs} resource conflict${conflictPairs === 1 ? "" : "s"}`
                  : "No resource conflicts"}
              </Badge>
            </div>
            {requestedSelection && selected && draft && (
              <p className="m-0 text-oprt-warning">
                Draft open on {selected.id}: keep or discard it to open {requestedSelection}.
              </p>
            )}
            <div role="group" aria-label="Schedule view" className="flex gap-1u">
              <Button
                size="sm"
                variant={view === "schedule" ? "secondary" : "ghost"}
                aria-pressed={view === "schedule"}
                onClick={() => setView("schedule")}
              >
                Schedule
              </Button>
              <Button
                size="sm"
                variant={view === "list" ? "secondary" : "ghost"}
                aria-pressed={view === "list"}
                onClick={() => setView("list")}
              >
                List
              </Button>
            </div>
          </div>
          <section
            aria-label="Conflict review"
            className="space-y-1u border-b border-oprt-border px-3u py-2u"
          >
            <h4 className="m-0 text-oprt-base font-medium">Conflict review</h4>
            {firstConflict && firstConflictPeer ? (
              <div className="flex flex-wrap items-center justify-between gap-2u">
                <p className="m-0 max-w-[65ch] text-oprt-text-dim">
                  <span className="font-mono text-oprt-text">{firstConflict.id}</span> and{" "}
                  <span className="font-mono text-oprt-text">{firstConflictPeer.id}</span> need{" "}
                  {resourceName(firstConflict.resourceId)} at the same time. Reassign a compatible
                  resource or adjust the service window.
                </p>
                <Button size="sm" variant="tertiary" onClick={() => choose(firstConflictPeer.id)}>
                  Inspect conflict
                </Button>
              </div>
            ) : (
              <p className="m-0 text-oprt-text-dim">
                Resource windows are clear. Prerequisites and downstream work are checked before
                each change is applied.
              </p>
            )}
          </section>
          {view === "schedule" ? (
            <div
              role="region"
              aria-label="Resource timeline, scroll horizontally for later hours"
              tabIndex={0}
              className={`overflow-x-auto ${focusStyle}`}
            >
              <div className="min-w-[860px]">
                <div className="grid grid-cols-[150px_minmax(0,1fr)] border-b border-oprt-border bg-oprt-surface-2">
                  <div className="px-3u py-2u font-medium text-oprt-text-dim">
                    Resource / assignment
                  </div>
                  <div className="relative h-5u font-mono text-oprt-text-dim">
                    {Array.from({ length: 7 }, (_, index) => (
                      <span
                        key={index}
                        style={{
                          left: `${(index / 6) * 100}%`,
                          transform: index === 6 ? "translateX(-100%)" : "none",
                        }}
                        className="absolute top-2u px-1u"
                      >
                        {utcTime(snapshot.start + ((snapshot.end - snapshot.start) * index) / 6)}
                        {index === 0 ? "\u00A0UTC" : ""}
                      </span>
                    ))}
                  </div>
                </div>
                {snapshot.resources.map((resource) => {
                  const lane = layoutLane(snapshot, resource.id);
                  const height = lane.trackCount * 78 + 16;
                  const reservedHours = lane.items.reduce(
                    (total, { job }) => total + (job.end - job.start) / 3_600_000,
                    0,
                  );
                  return (
                    <div
                      key={resource.id}
                      className="grid grid-cols-[150px_minmax(0,1fr)] border-b border-oprt-border last:border-b-0"
                    >
                      <div className="border-r border-oprt-border px-3u py-3u">
                        <p className="m-0 font-medium">{resource.name}</p>
                        <p className="m-0 mt-1u text-oprt-text-dim">{resource.detail}</p>
                        <p className="m-0 mt-1u font-mono text-oprt-text-dim">
                          {hours(reservedHours)} reserved
                        </p>
                      </div>
                      <div className="relative" style={{ height }}>
                        <div
                          aria-hidden="true"
                          className="pointer-events-none absolute inset-0 grid grid-cols-6"
                        >
                          {Array.from({ length: 6 }, (_, index) => (
                            <div
                              key={index}
                              className="border-r border-oprt-border last:border-r-0"
                            />
                          ))}
                        </div>
                        {lane.items.map(({ job, track, left, width }) => (
                          <button
                            type="button"
                            key={job.id}
                            aria-label={jobLabel(job)}
                            aria-pressed={selected?.id === job.id}
                            onClick={() => choose(job.id)}
                            style={{
                              left: `calc(${left}% + 4px)`,
                              width: `calc(${width}% - 8px)`,
                              top: track * 78 + 8,
                              height: 70,
                            }}
                            className={`absolute flex min-w-0 flex-col items-start justify-center gap-0.5u border px-1u text-left text-oprt-base font-medium transition-colors hover:bg-oprt-surface-3 ${focusStyle} ${selected?.id === job.id ? `border-oprt-accent bg-oprt-surface-3 ${selectedBar}` : "border-oprt-border bg-oprt-surface-2"}`}
                          >
                            <span className="flex w-full justify-between gap-1u font-mono text-oprt-text-dim">
                              <span>{job.id}</span>
                              {conflictsFor(snapshot.jobs, job).length > 0 && (
                                <span className="font-sans text-oprt-warning">Conflict</span>
                              )}
                            </span>
                            <span className="w-full leading-tight">{job.name}</span>
                            <span className="font-mono text-oprt-text-dim">
                              {utcTime(job.start)}–{utcTime(job.end)}&nbsp;UTC
                            </span>
                          </button>
                        ))}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          ) : narrow ? (
            <ul aria-label="Work order list" className="m-0 list-none p-0">
              {[...snapshot.jobs]
                .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))
                .map((job) => (
                  <li key={job.id} className="border-b border-oprt-border last:border-b-0">
                    <button
                      type="button"
                      aria-label={jobLabel(job)}
                      aria-pressed={selected?.id === job.id}
                      onClick={() => choose(job.id)}
                      className={`relative flex w-full flex-col gap-1u p-2u text-left text-oprt-base font-medium hover:bg-oprt-surface-3 ${focusStyle} ${selected?.id === job.id ? `bg-oprt-surface-3 ${selectedBar}` : ""}`}
                    >
                      <span className="flex w-full flex-wrap justify-between gap-1u">
                        <span className="font-mono text-oprt-text-dim">{job.id}</span>
                        <Badge
                          appearance="text"
                          variant={conflictsFor(snapshot.jobs, job).length ? "warning" : "neutral"}
                        >
                          {conflictsFor(snapshot.jobs, job).length ? "Conflict" : "Scheduled"}
                        </Badge>
                      </span>
                      <span>{job.name}</span>
                      <span className="text-oprt-text-dim">{job.asset}</span>
                      <span className="text-oprt-text-dim">{resourceName(job.resourceId)}</span>
                      <span className="font-mono">
                        {utcTime(job.start)}–{utcTime(job.end)}&nbsp;UTC
                      </span>
                    </button>
                  </li>
                ))}
            </ul>
          ) : (
            <div className="overflow-x-auto">
              <table
                aria-label="Work order list"
                className="w-full min-w-[640px] border-collapse text-left text-oprt-base"
              >
                <thead className="bg-oprt-surface-2">
                  <tr>
                    {["Work order", "Resource", "Window · UTC", "Status"].map((heading) => (
                      <th
                        key={heading}
                        scope="col"
                        className="border-b border-oprt-border px-2u py-2u font-medium text-oprt-text-dim"
                      >
                        {heading}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {[...snapshot.jobs]
                    .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))
                    .map((job) => (
                      <tr
                        key={job.id}
                        className={selected?.id === job.id ? "bg-oprt-surface-3" : ""}
                      >
                        <td
                          className={`border-b border-oprt-border p-2u ${selected?.id === job.id ? `relative ${selectedBar}` : ""}`}
                        >
                          <button
                            type="button"
                            aria-label={jobLabel(job)}
                            aria-pressed={selected?.id === job.id}
                            onClick={() => choose(job.id)}
                            className={`flex flex-col gap-0.5u text-left font-medium ${focusStyle}`}
                          >
                            <span>{job.name}</span>
                            <span className="font-mono text-oprt-text-dim">{job.id}</span>
                          </button>
                        </td>
                        <td className="border-b border-oprt-border p-2u">
                          {resourceName(job.resourceId)}
                        </td>
                        <td className="border-b border-oprt-border p-2u font-mono">
                          {utcTime(job.start)}–{utcTime(job.end)}
                        </td>
                        <td className="border-b border-oprt-border p-2u">
                          <Badge
                            appearance="text"
                            variant={
                              conflictsFor(snapshot.jobs, job).length ? "warning" : "neutral"
                            }
                          >
                            {conflictsFor(snapshot.jobs, job).length ? "Conflict" : "Scheduled"}
                          </Badge>
                        </td>
                      </tr>
                    ))}
                </tbody>
              </table>
            </div>
          )}
          <div className="border-t border-oprt-border px-3u py-2u">
            <p className="m-0 text-oprt-text-dim">
              All times are UTC. A resource may start its next work order exactly when the previous
              order ends.
            </p>
          </div>
        </div>
        <Inspector
          ref={inspector}
          tabIndex={-1}
          title="Selected work order"
          className={`min-w-0 flex-[1_1_20rem] border-0 p-3u ${focusStyle}`}
        >
          {selected ? (
            <>
              <EntityPreview
                entity={{
                  id: selected.id,
                  name: selected.name,
                  typeLabel: selected.asset,
                  summary: selected.summary,
                  condition: {
                    label:
                      conflictsFor(snapshot.jobs, selected).length > 0
                        ? "Resource conflict"
                        : "Scheduled",
                    variant:
                      conflictsFor(snapshot.jobs, selected).length > 0 ? "warning" : "neutral",
                  },
                  facts: [
                    { id: "resource", label: "Resource", value: resourceName(selected.resourceId) },
                    { id: "owner", label: "Work lead", value: selected.owner },
                    {
                      id: "window",
                      label: "Service window",
                      value: <span className="font-mono">{utcWindow(selected)}</span>,
                    },
                    {
                      id: "duration",
                      label: "Duration",
                      value: (
                        <span className="font-mono">
                          {hours((selected.end - selected.start) / 3_600_000)}
                        </span>
                      ),
                    },
                  ],
                  quality: {
                    state: "current",
                    source: `Supplied plan · ${snapshot.revision}`,
                    detail: "Freshness reflects the supplied snapshot.",
                  },
                }}
              />
              <Dependencies selected={selected} snapshot={snapshot} />
              {requestedSelection && (
                <div className="space-y-1u border-t border-oprt-border pt-2u">
                  <p role="alert" className="m-0">
                    {pending
                      ? "The service is processing this change. Wait for its outcome before opening another work order."
                      : `Keep this draft or discard it to open ${requestedSelection}.`}
                  </p>
                  <div className="flex flex-wrap gap-1u">
                    <Button
                      size="sm"
                      variant="tertiary"
                      disabled={pending}
                      onClick={() => {
                        const next = requestedSelection;
                        closeEditor();
                        setSelectedId(next);
                      }}
                    >
                      Discard change and open work order
                    </Button>
                    <Button
                      ref={keepEditing}
                      size="sm"
                      variant="ghost"
                      onClick={() => setRequestedSelection(null)}
                    >
                      Keep editing
                    </Button>
                  </div>
                </div>
              )}
              {notice && (
                <p role="status" className="m-0 border-t border-oprt-border pt-2u">
                  {notice}
                </p>
              )}
              {draft ? (
                <div className="space-y-2u border-t border-oprt-border pt-2u">
                  {stale && (
                    <div role="alert" className="space-y-1u">
                      <p className="m-0 text-oprt-warning">
                        The source revision changed. Load the latest revision before editing again.
                      </p>
                      <Button size="sm" variant="tertiary" onClick={closeEditor}>
                        Load latest revision
                      </Button>
                    </div>
                  )}
                  {failure && (
                    <p role="alert" className="m-0 text-oprt-error">
                      {failure}
                    </p>
                  )}
                  {review ? (
                    <section
                      ref={reviewRef}
                      tabIndex={-1}
                      aria-label="Review schedule change"
                      className={`space-y-2u ${focusStyle}`}
                    >
                      <h4 className="m-0 text-oprt-md font-medium">Review schedule change</h4>
                      <p className="m-0 font-mono">
                        {selected.id} · {draft.revision}
                      </p>
                      <div className="space-y-1u">
                        <p className="m-0 font-medium">Current</p>
                        <p className="m-0">{resourceName(selected.resourceId)}</p>
                        <p className="m-0 font-mono">{utcWindow(selected)}</p>
                      </div>
                      <div className="space-y-1u">
                        <p className="m-0 font-medium">Proposed</p>
                        <p className="m-0">{resourceName(review.resourceId)}</p>
                        <p className="m-0 font-mono">{utcWindow(review)}</p>
                      </div>
                      <p className="m-0 text-oprt-text-dim">
                        No resource overlap. Prerequisites and downstream work remain in sequence.
                      </p>
                      {!onApply && (
                        <p className="m-0 text-oprt-text-dim">
                          Review only: connect a schedule service to apply changes.
                        </p>
                      )}
                      <div className="flex flex-wrap gap-1u">
                        {onApply && (
                          <Button disabled={pending || stale} onClick={() => void apply()}>
                            {pending ? "Applying change…" : "Apply schedule change"}
                          </Button>
                        )}
                        <Button
                          variant="tertiary"
                          disabled={pending}
                          onClick={() => {
                            setReview(null);
                            setFailure("");
                          }}
                        >
                          Back to editing
                        </Button>
                      </div>
                    </section>
                  ) : (
                    <form
                      ref={formRef}
                      aria-label="Edit work window"
                      className="space-y-2u"
                      onSubmit={(event) => {
                        event.preventDefault();
                        reviewChange();
                      }}
                      noValidate
                    >
                      <h4 className="m-0 text-oprt-md font-medium">Edit work window</h4>
                      <Field label="Resource" controlId={`${instance}-resource`}>
                        <Select
                          value={draft.resourceId}
                          onValueChange={(resourceId) => setDraft({ ...draft, resourceId })}
                          disabled={stale}
                          className="w-full"
                        >
                          <>
                            {snapshot.resources
                              .filter(
                                (resource) =>
                                  !selected.eligibleResourceIds ||
                                  selected.eligibleResourceIds.includes(resource.id),
                              )
                              .map((resource) => (
                                <SelectOption key={resource.id} value={resource.id}>
                                  {resource.name}
                                </SelectOption>
                              ))}
                          </>
                        </Select>
                      </Field>
                      <Field label="Start · UTC" controlId={`${instance}-start`}>
                        <Input
                          type="datetime-local"
                          step={60}
                          value={draft.start}
                          disabled={stale}
                          onChange={(event) => setDraft({ ...draft, start: event.target.value })}
                          className="w-full"
                        />
                      </Field>
                      <Field label="End · UTC" controlId={`${instance}-end`}>
                        <Input
                          type="datetime-local"
                          step={60}
                          value={draft.end}
                          disabled={stale}
                          onChange={(event) => setDraft({ ...draft, end: event.target.value })}
                          className="w-full"
                        />
                      </Field>
                      <p className="m-0 text-oprt-text-dim">
                        Enter UTC values within {utcWindow(snapshot)}. Local device time is not
                        used.
                      </p>
                      {issues.length > 0 && (
                        <div role="alert" className="space-y-1u text-oprt-error">
                          <p className="m-0 font-medium">Resolve before review</p>
                          <ul className="m-0 list-disc space-y-1u pl-2u">
                            {issues.map((issue, index) => (
                              <li key={index}>{issue.message}</li>
                            ))}
                          </ul>
                        </div>
                      )}
                      <div className="flex flex-wrap gap-1u">
                        <Button type="submit" disabled={stale}>
                          Review change
                        </Button>
                        <Button variant="tertiary" onClick={closeEditor}>
                          Cancel edit
                        </Button>
                      </div>
                    </form>
                  )}
                </div>
              ) : (
                <div className="border-t border-oprt-border pt-2u">
                  <Button ref={editButton} onClick={beginEdit}>
                    Edit work window
                  </Button>
                </div>
              )}
            </>
          ) : snapshot.jobs.length && selectedId ? (
            <p role="status" className="m-0 text-oprt-text-dim">
              {selectedId} is no longer in this revision. Choose a work order.
            </p>
          ) : (
            <p className="m-0 text-oprt-text-dim">No work orders in this planning window.</p>
          )}
        </Inspector>
      </div>
    </section>
  );
}

function Dependencies({
  selected,
  snapshot,
}: {
  selected: ScheduleJob;
  snapshot: ScheduleSnapshot;
}) {
  const prerequisites = selected.prerequisites.map((id) =>
    snapshot.jobs.find((job) => job.id === id),
  );
  const downstream = snapshot.jobs.filter((job) => job.prerequisites.includes(selected.id));
  return (
    <section
      aria-label="Dependencies and impact"
      className="space-y-2u border-t border-oprt-border pt-2u"
    >
      <h4 className="m-0 text-oprt-md font-medium">Dependencies & impact</h4>
      <div className="space-y-1u">
        <p className="m-0 font-medium text-oprt-text-dim">Before this work</p>
        {prerequisites.length ? (
          prerequisites.map((job, index) => (
            <p key={selected.prerequisites[index]} className="m-0">
              {job ? (
                <>
                  {job.name}
                  <span className="mt-0.5u block font-mono text-oprt-text-dim">
                    {job.id} · ends {utcTime(job.end)} UTC
                  </span>
                </>
              ) : (
                `Missing prerequisite: ${selected.prerequisites[index]}`
              )}
            </p>
          ))
        ) : (
          <p className="m-0">No prerequisites assigned.</p>
        )}
      </div>
      <div className="space-y-1u">
        <p className="m-0 font-medium text-oprt-text-dim">Work that depends on this</p>
        {downstream.length ? (
          downstream.map((job) => (
            <p key={job.id} className="m-0">
              {job.name}
              <span className="mt-0.5u block font-mono text-oprt-text-dim">
                {job.id} · starts {utcTime(job.start)} UTC
              </span>
            </p>
          ))
        ) : (
          <p className="m-0">No downstream work assigned.</p>
        )}
      </div>
    </section>
  );
}

patterns/planning-scheduling/schedule-state.ts

export type ScheduleResource = { id: string; name: string; detail: string };
export type ScheduleJob = {
  id: string;
  name: string;
  asset: string;
  owner: string;
  resourceId: string;
  eligibleResourceIds?: readonly string[];
  start: number;
  end: number;
  prerequisites: readonly string[];
  summary: string;
};
export type ScheduleSnapshot = {
  id: string;
  revision: string;
  title: string;
  location: string;
  summary: string;
  start: number;
  end: number;
  resources: readonly ScheduleResource[];
  jobs: readonly ScheduleJob[];
};
export type ScheduleChange = { jobId: string; resourceId: string; start: number; end: number };
export type ScheduleApplyRequest = ScheduleChange & {
  scheduleId: string;
  expectedRevision: string;
  requestId: string;
};
export type ScheduleApplyResult =
  | { status: "accepted"; requestId: string; snapshot: ScheduleSnapshot }
  | { status: "rejected" | "conflict"; requestId: string; message: string };
export type ScheduleIssue = {
  kind: "window" | "resource" | "dependency";
  message: string;
  jobId?: string;
};

/** Resource reservations are half-open intervals: [start, end). */
export function conflictsFor(jobs: readonly ScheduleJob[], job: ScheduleJob): string[] {
  return jobs
    .filter(
      (other) =>
        other.id !== job.id &&
        other.resourceId === job.resourceId &&
        other.start < job.end &&
        job.start < other.end,
    )
    .map((other) => other.id);
}

export function validateChange(
  snapshot: ScheduleSnapshot,
  change: ScheduleChange,
): ScheduleIssue[] {
  const original = snapshot.jobs.find((job) => job.id === change.jobId);
  if (!original)
    return [
      {
        kind: "window",
        message: "This work order is no longer in the schedule. Load the latest revision.",
      },
    ];
  if (!Number.isFinite(change.start) || !Number.isFinite(change.end) || change.end <= change.start)
    return [{ kind: "window", message: "Enter a complete window with the end after the start." }];
  if (change.start < snapshot.start || change.end > snapshot.end)
    return [{ kind: "window", message: "Keep both times within the displayed planning window." }];
  if (!snapshot.resources.some((resource) => resource.id === change.resourceId))
    return [{ kind: "resource", message: "Choose an available resource." }];
  if (original.eligibleResourceIds && !original.eligibleResourceIds.includes(change.resourceId))
    return [
      {
        kind: "resource",
        message: "This resource cannot perform the work. Choose a compatible resource.",
      },
    ];
  const proposed = { ...original, ...change, id: original.id };
  const issues: ScheduleIssue[] = conflictsFor(snapshot.jobs, proposed).map((id) => ({
    kind: "resource",
    jobId: id,
    message: `Resource overlaps ${id} · ${snapshot.jobs.find((job) => job.id === id)!.name}. Choose another resource or window.`,
  }));
  for (const id of original.prerequisites) {
    const prerequisite = snapshot.jobs.find((job) => job.id === id);
    if (!prerequisite || prerequisite.end > change.start)
      issues.push({
        kind: "dependency",
        jobId: id,
        message: prerequisite
          ? `${id} · ${prerequisite.name} must finish before this work starts.`
          : `Prerequisite ${id} is missing. Restore the source record before applying.`,
      });
  }
  for (const dependent of snapshot.jobs.filter((job) => job.prerequisites.includes(original.id))) {
    if (dependent.start < change.end)
      issues.push({
        kind: "dependency",
        jobId: dependent.id,
        message: `${dependent.id} · ${dependent.name} starts before this work would finish. Keep its prerequisite available.`,
      });
  }
  return issues;
}

/** Track packing preserves exact time positions while making overlaps selectable. */
export function layoutLane(snapshot: ScheduleSnapshot, resourceId: string) {
  const ends: number[] = [];
  const duration = snapshot.end - snapshot.start;
  const items = snapshot.jobs
    .filter((job) => job.resourceId === resourceId)
    .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))
    .map((job) => {
      let track = ends.findIndex((end) => end <= job.start);
      if (track === -1) track = ends.length;
      ends[track] = job.end;
      return {
        job,
        track,
        left: ((job.start - snapshot.start) / duration) * 100,
        width: ((job.end - job.start) / duration) * 100,
      };
    });
  return { items, trackCount: Math.max(1, ends.length) };
}

export function parseUtcInput(value: string): number | null {
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) return null;
  const instant = Date.parse(`${value}:00Z`);
  return Number.isFinite(instant) && new Date(instant).toISOString().slice(0, 16) === value
    ? instant
    : null;
}
export const utcInput = (instant: number) => new Date(instant).toISOString().slice(0, 16);
export const utcTime = (instant: number) => new Date(instant).toISOString().slice(11, 16);
export const utcDate = (instant: number) => new Date(instant).toISOString().slice(0, 10);
export const utcWindow = (job: { start: number; end: number }) =>
  `${utcDate(job.start)} ${utcTime(job.start)}–${utcDate(job.start) === utcDate(job.end) ? "" : `${utcDate(job.end)} `}${utcTime(job.end)} UTC`;

patterns/planning-scheduling/demo.tsx

"use client";

import { useState } from "react";
import type { OperationalDomain } from "../operational-domain";
import { createScheduleFixture } from "./fixtures";
import { PlanningScheduling } from "./planning-scheduling";
import {
  validateChange,
  type ScheduleApplyRequest,
  type ScheduleApplyResult,
} from "./schedule-state";

export function PlanningSchedulingDemo({
  initialDomain = "industrial",
}: { initialDomain?: OperationalDomain } = {}) {
  return (
    <div className="not-prose min-w-0 space-y-2u">
      <LocalSchedule key={initialDomain} domain={initialDomain} />
      <p className="m-0 font-sans text-oprt-base text-oprt-text-dim">
        Fictional maintenance plan. Changes stay in this page session.
      </p>
    </div>
  );
}

/** Public industrial example; legacy fixtures remain available to regression tests. */
export function MaintenancePlanningDemo() {
  return <PlanningSchedulingDemo initialDomain="industrial" />;
}

function LocalSchedule({ domain }: { domain: OperationalDomain }) {
  const [snapshot, setSnapshot] = useState(() => createScheduleFixture(domain));
  const apply = async (request: ScheduleApplyRequest): Promise<ScheduleApplyResult> => {
    if (request.scheduleId !== snapshot.id || request.expectedRevision !== snapshot.revision)
      return {
        status: "conflict",
        requestId: request.requestId,
        message: "The source revision changed. Load the latest revision before applying.",
      };
    const issues = validateChange(snapshot, request);
    if (issues.length)
      return {
        status: "rejected",
        requestId: request.requestId,
        message: issues.map((issue) => issue.message).join(" "),
      };
    return {
      status: "accepted",
      requestId: request.requestId,
      snapshot: {
        ...snapshot,
        revision: `plan-${String(Number(snapshot.revision.slice(5)) + 1).padStart(2, "0")}`,
        jobs: snapshot.jobs.map((job) =>
          job.id === request.jobId
            ? { ...job, resourceId: request.resourceId, start: request.start, end: request.end }
            : job,
        ),
      },
    };
  };
  return (
    <PlanningScheduling
      snapshot={snapshot}
      initialSelectedJobId={domain === "military" ? "WO-1842" : "PM-2402"}
      onApply={apply}
      onAccepted={setSnapshot}
      acceptanceMessage="Schedule updated locally. The resource conflict is resolved for this work order."
    />
  );
}

patterns/operational-domain.ts

/** Fixture context used by reusable application recipes. */
export type OperationalDomain = "military" | "industrial";

export interface UtcInstantOptions {
  /**
   * Whether the milliseconds are shown. "auto" (the default) shows them only when the supplied
   * instant carries a non-zero fraction, because sub-second timing matters only where it exists.
   */
  milliseconds?: "auto" | "always" | "never";
}

const INSTANT =
  /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})$/;

/**
 * Reads an ISO 8601 instant as "YYYY-MM-DD HH:MM:SS UTC", with ".mmm" after the seconds where the
 * options ask for it. Text that is not a real instant (an invented date such as 30 February, or
 * free text) is returned as supplied rather than reinterpreted. It never uses Intl, so the server
 * and every browser print the same characters.
 */
export function formatUtcInstant(value: string, options: UtcInstantOptions = {}): string {
  const parts = INSTANT.exec(value);
  if (!parts) return value;
  const [year, month, day, hour, minute, second] = parts.slice(1, 7).map(Number) as [
    number,
    number,
    number,
    number,
    number,
    number,
  ];
  const calendar = new Date(Date.UTC(year, month - 1, day));
  // Date rolls an invented day into the next month; compare it back to what was written.
  if (
    calendar.getUTCFullYear() !== year ||
    calendar.getUTCMonth() !== month - 1 ||
    calendar.getUTCDate() !== day ||
    hour > 23 ||
    minute > 59 ||
    second > 59
  )
    return value;
  const fraction = (parts[7] ?? "").padEnd(3, "0");
  const parsed = new Date(`${value.slice(0, 19)}.${fraction}${parts[8]}`);
  if (Number.isNaN(parsed.getTime())) return value;
  const { milliseconds = "auto" } = options;
  const showFraction = milliseconds === "always" || (milliseconds === "auto" && fraction !== "000");
  const iso = parsed.toISOString();
  return `${iso.slice(0, 10)} ${iso.slice(11, showFraction ? 23 : 19)} UTC`;
}

/**
 * Reads epoch milliseconds as "YYYY-MM-DD HH:MM:SS UTC", with the same options as
 * formatUtcInstant. A value that is not a real time reads "Unknown time" rather than throwing.
 */
export function formatUtcEpoch(ms: number, options: UtcInstantOptions = {}): string {
  const date = new Date(ms);
  if (!Number.isFinite(ms) || Number.isNaN(date.getTime())) return "Unknown time";
  return formatUtcInstant(date.toISOString(), options);
}

patterns/planning-scheduling/fixtures.ts

import type { OperationalDomain } from "../operational-domain";
import type { ScheduleJob, ScheduleSnapshot } from "./schedule-state";

const at = (time: string) => Date.parse(`2026-10-06T${time}:00Z`);
const job = (
  id: string,
  name: string,
  asset: string,
  owner: string,
  resourceId: string,
  start: string,
  end: string,
  prerequisites: string[],
  summary: string,
): ScheduleJob => ({
  id,
  name,
  asset,
  owner,
  resourceId,
  eligibleResourceIds:
    resourceId === "bay-a" || resourceId === "bay-b"
      ? ["bay-a", "bay-b"]
      : resourceId === "mechanical-a" || resourceId === "mechanical-b"
        ? ["mechanical-a", "mechanical-b"]
        : [resourceId],
  start: at(start),
  end: at(end),
  prerequisites,
  summary,
});

export function createScheduleFixture(domain: OperationalDomain): ScheduleSnapshot {
  if (domain === "industrial")
    return {
      id: "north-process",
      revision: "plan-12",
      title: "Plant maintenance",
      location: "North Process Works · Unit 4",
      summary: "Planned downtime · service windows and crew allocation",
      start: at("06:00"),
      end: at("18:00"),
      resources: [
        {
          id: "mechanical-a",
          name: "Mechanical crew A",
          detail: "Rotating equipment · 4 technicians",
        },
        {
          id: "mechanical-b",
          name: "Mechanical crew B",
          detail: "Mechanical service · 3 technicians",
        },
        {
          id: "inspection",
          name: "Inspection crew",
          detail: "Condition & pressure · 2 inspectors",
        },
        { id: "electrical", name: "Electrical crew", detail: "Electrical · 2 technicians" },
        { id: "isolation", name: "Stores & isolation", detail: "Work packs · 2 coordinators" },
      ],
      jobs: [
        job(
          "PM-2401",
          "Seal inspection",
          "P-204 transfer pump",
          "Mara Ellis",
          "mechanical-a",
          "06:00",
          "10:00",
          [],
          "Inspect the mechanical seal and document wear before the unit restart.",
        ),
        job(
          "PM-2402",
          "Bearing replacement",
          "P-208 feed pump",
          "Noah Voss",
          "mechanical-a",
          "09:00",
          "12:00",
          ["PM-2408"],
          "Replace the drive-end bearing and record the installation checks before the vibration baseline.",
        ),
        job(
          "PM-2403",
          "Valve service",
          "XV-410 bypass valve",
          "Lina Chen",
          "mechanical-b",
          "06:00",
          "09:00",
          [],
          "Inspect the actuator travel and replace the stem packing.",
        ),
        job(
          "PM-2404",
          "Coupling alignment",
          "M-212 agitator",
          "Lina Chen",
          "mechanical-b",
          "13:00",
          "16:00",
          [],
          "Check alignment against the work-pack tolerances and record the final readings.",
        ),
        job(
          "PM-2405",
          "Vibration baseline",
          "P-208 feed pump",
          "Evan Patel",
          "inspection",
          "12:30",
          "15:00",
          ["PM-2402"],
          "Collect the post-maintenance vibration baseline after bearing replacement.",
        ),
        job(
          "PM-2406",
          "Pressure verification",
          "HX-406 exchanger",
          "Evan Patel",
          "inspection",
          "15:00",
          "18:00",
          [],
          "Witness the planned pressure verification and file the inspection record.",
        ),
        job(
          "PM-2407",
          "Starter inspection",
          "MCC-04 panel",
          "Sofia Reed",
          "electrical",
          "08:00",
          "12:00",
          [],
          "Inspect the starter contacts under the supplied isolation work pack.",
        ),
        job(
          "PM-2408",
          "Bearing kit release",
          "P-208 work pack",
          "Ari Morgan",
          "isolation",
          "06:00",
          "08:00",
          [],
          "Verify the bearing kit and release the isolation pack to the assigned mechanical crew.",
        ),
        job(
          "PM-2409",
          "Spares reconciliation",
          "Unit 4 stores",
          "Ari Morgan",
          "isolation",
          "14:00",
          "17:00",
          [],
          "Reconcile used parts against the shutdown work orders.",
        ),
      ],
    };
  return {
    id: "cedar-fleet",
    revision: "plan-07",
    title: "Fleet availability",
    location: "Cedar Support Depot · Maintenance section",
    summary: "Tuesday day shift · vehicle maintenance and crew training",
    start: at("06:00"),
    end: at("18:00"),
    resources: [
      { id: "bay-a", name: "Maintenance bay 1", detail: "Vehicle service · 4 technicians" },
      { id: "bay-b", name: "Maintenance bay 2", detail: "Vehicle service · 3 technicians" },
      { id: "test", name: "Validation pad", detail: "Vehicle checks · 2 technicians" },
      { id: "training", name: "Training cell", detail: "Crew instruction · 12 seats" },
      { id: "stores", name: "Parts & dispatch", detail: "Support · 2 coordinators" },
    ],
    jobs: [
      job(
        "WO-1841",
        "Brake inspection",
        "Hawk 21 · utility vehicle",
        "Sgt. Ellis",
        "bay-a",
        "06:00",
        "10:00",
        [],
        "Inspect the braking system and record service findings before the next driver-training rotation.",
      ),
      job(
        "WO-1842",
        "Cooling service",
        "Hawk 24 · utility vehicle",
        "Cpl. Voss",
        "bay-a",
        "09:00",
        "12:00",
        ["WO-1848"],
        "Replace the coolant hose and perform a leak check before the return-to-service inspection.",
      ),
      job(
        "WO-1843",
        "Battery service",
        "Hawk 18 · utility vehicle",
        "Cpl. Chen",
        "bay-b",
        "06:00",
        "09:00",
        [],
        "Inspect battery connections and record the load-test result.",
      ),
      job(
        "WO-1844",
        "Suspension check",
        "Hawk 27 · utility vehicle",
        "Cpl. Chen",
        "bay-b",
        "13:00",
        "16:00",
        [],
        "Inspect the suspension and complete the scheduled service checklist.",
      ),
      job(
        "WO-1845",
        "Return-to-service check",
        "Hawk 24 · utility vehicle",
        "Sgt. Patel",
        "test",
        "12:30",
        "15:00",
        ["WO-1842"],
        "Verify the cooling repair before returning the vehicle to the training pool.",
      ),
      job(
        "WO-1846",
        "Driver checks",
        "Hawk 31 · utility vehicle",
        "Sgt. Patel",
        "test",
        "15:00",
        "18:00",
        [],
        "Complete routine driver checks with the afternoon training crew.",
      ),
      job(
        "WO-1847",
        "Crew refresher",
        "Support section Bravo",
        "Lt. Reed",
        "training",
        "08:00",
        "12:00",
        [],
        "Review vehicle care, defect reporting and handover procedures with the support crew.",
      ),
      job(
        "WO-1848",
        "Service kit release",
        "Hawk 24 · parts kit",
        "Spc. Morgan",
        "stores",
        "06:00",
        "08:00",
        [],
        "Verify the replacement hose and release the service kit to the maintenance team.",
      ),
      job(
        "WO-1849",
        "Fleet handover",
        "Evening support crew",
        "Spc. Morgan",
        "stores",
        "14:00",
        "17:00",
        [],
        "Reconcile service records and brief the evening support crew on vehicle availability.",
      ),
    ],
  };
}