PixelDosa UI

Agent Schedule

When the agent runs on its own, written as sentences anyone can check.

When this agent runs

2 of 3 on

  • Every weekday at 9:00

    Triages the support inbox and drafts replies

    Next: Tomorrow at 9:00Ran today · 24 drafted, 0 sentStops to ask you

  • When an invoice arrives from a new supplier

    Checks it against the purchase order and flags mismatches

    Next: Whenever one arrivesFailed yesterday · the ledger refused the connection

  • Every Monday at 7:00

    Sends the weekly competitor digest to the team

    Last ran 3 weeks ago

Installation

bash
npx shadcn@latest add @pixeldosa/agent-schedule

Requires `cn` at `@/lib/utils`. Write `when` as a sentence a non-technical colleague could check, and keep the cron expression in your backend.

Usage

"use client";

import { AgentSchedule } from "@/components/ui/agent-schedule";

<AgentSchedule
  triggers={[
    {
      id: "1",
      when: "Every weekday at 9:00",
      does: "Triages the support inbox and drafts replies",
      enabled: true,
      nextRun: "Tomorrow at 9:00",
      lastRun: "Ran today · 24 drafted, 0 sent",
      asksFirst: true,
    },
  ]}
  onEnabledChange={toggle}
  onRunNow={runNow}
/>

Keep the cron expression in your backend. This surface is the sentence a colleague can check.

Why it exists

Scheduled agents are where trust quietly breaks. The thing that runs at 9:00 every morning is the thing nobody is watching, and a schedule that has been failing for a week usually looks exactly like one that's fine.

Features

  • Sentences, not cron. "When an invoice arrives from a new supplier", not 0 9 * * 1-5.
  • A failed last run is visible on the row, in the blocked colour, without opening anything.
  • The next run is stated, so "is this still on?" has an answer.
  • "Stops to ask you" is shown while you're switching a trigger on, not discovered later.
  • One control to turn it off, with the trigger's own sentence in its accessible name.

Props

PropTypeNotes
triggers{ id, when, does, enabled, nextRun?, lastRun?, lastFailed?, asksFirst? }[]You write when and does.
onEnabledChange(id, enabled) => voidRequired.
onRunNow(id) => voidRuns one now.
onEdit(id) => void
emptyLabelstring
titlestringDefaults to "When this agent runs".
headingLevel2 | 3 | 4 | 5 | 6Defaults to 3.

Accessibility

  • The on/off state lives in aria-checked only, never in the name — a name that carries its own state gets read twice and renames the control every time it's used.
  • Each toggle is a real role="switch" whose accessible name includes the trigger's sentence, so you hear which schedule you're turning off.
  • A failed last run is text as well as colour.
  • Only the switch animates, and that stops under prefers-reduced-motion.

When not to use

  • A cron editor for engineers: that's a different audience and a different surface.
  • A calendar of past and future runs: use Run Inbox for what happened.
  • One-off runs: those belong wherever the user starts work.

why it's built this way

Engineering Notes

Every trigger is caller-written prose rather than a structure the component renders into English, which is a deliberate trade: generated sentences from a rule object read fluently for simple cases and become nonsense at the first compound condition, and the person this surface protects is the one who has to verify that the sentence matches what the system does. The failed last run is rendered inline in the blocked colour rather than behind a status filter, because the state this component exists to prevent is a silent schedule failing unnoticed for a week. `asksFirst` is shown on the row so the approval behaviour of an unattended run is visible while it is being switched on, not discovered later. The switch is a real `role="switch"` with an accessible name that includes the trigger's own sentence, so a screen-reader user hears which schedule they are turning off.

how it moves, and when it doesn't

Motion Notes

Only the switch moves, sliding its thumb over `--pd-duration-fast` so flipping one reads as a physical change rather than a repaint. Nothing else animates: this is a settings surface, and a schedule row that pulsed would imply the agent is running right now. Under `prefers-reduced-motion` the slide is dropped and the state is carried by position, colour and the accessible name.