# Task tray

Work running in the background, as one pill: how many are running and how many need you. Opens to the list, needs-you first. Progress is shown only when the service knows the total; otherwise the task just says it's running. A task with `lastHeardAt` that has been silent past `staleAfter`, or whose host is offline, isn't counted as running: it says "No update since 10:42" or that its host is offline, and the pill counts it as not heard from.

- Registry name: `task-tray` (`@candor/task-tray`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/task-tray

## Install

```bash
npx candor-ui add task-tray
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/task-tray`.
It brings in: agent-avatar, locale, popover, relative-time, run-heartbeat, status, status-dot, step-list, work, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import type { BackgroundTask } from "@/lib/work"
import { TaskTray } from "@/components/task-tray"

const ago = (m: number) => new Date(Date.now() - m * 60_000).toISOString()

const tasks: BackgroundTask[] = [
  { id: "t1", title: "Add region to customers", status: "running", agent: { id: "a-dev", name: "Dev agent", role: "dev" }, progress: { done: 4, total: 6 }, startedAt: ago(3) },
  { id: "t2", title: "Drop partner_tier_old", status: "waiting", agent: { id: "a-orch", name: "Keel", role: "orchestrator" }, startedAt: ago(60 * 20), needs: "Waiting for another approver" },
  { id: "t3", title: "Audit API rate limits", status: "running", agent: { id: "a-qa", name: "QA agent", role: "qa" }, startedAt: ago(12) },
  // Silent for 9 minutes: not counted as running, it says when it was last heard from.
  { id: "t5", title: "Backfill order totals", status: "running", startedAt: ago(30), lastHeardAt: ago(9) },
  { id: "t4", title: "Explain the invoice retry job", status: "succeeded", startedAt: ago(40) },
]

export default function Example() {
  return <TaskTray tasks={tasks} onOpenTask={() => {}} />
}
```

## <TaskTray>

Props:

- `tasks: BackgroundTask[]`: The background tasks; with none, the tray renders nothing.
- `onOpenTask?: ((id: string) => void) | undefined`: Called with a task's id when it's clicked in the list.
- `staleAfter?: number | undefined` (default `STALE_AFTER`): How long a running task may be silent before it's no longer called running, in milliseconds. Defaults to two minutes.
- `className?: string | undefined`

## Strings (`taskTray`)

The fixed words, English by default. Override any of them under `taskTray` in `<LocaleProvider strings={{ taskTray: { … } }}>`; the table is exported as `taskTrayStrings`.

- `running`: {count} running
- `notHeard`: { one: "{count} not heard from", other: "{count} not heard from" }
- `startedCount`: {count} started
- `needsYou`: { one: "{count} needs you", other: "{count} need you" }
- `allDone`: All done
- `backgroundWork`: Background work
- `progress`: Step {done} of {total}
- `statusRunning`: Running
- `statusFinished`: Finished
- `statusQueued`: Queued
- `started`: {status} · started

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
