Assignee picker
ProChoose a person or an agent for a task. Each one shows their current load ("3 of 5 open tasks") and availability ("Away until Mon 16 Nov, 09:00") as the app passes them; what the app doesn't pass reads "Load not known" or "Availability not known", never zero or available. People and agents are listed apart, each in the app's order: the picker doesn't sort by load or suggest anyone, and nothing is chosen until the person chooses. An agent is named by the app and marked as an agent.
The list is searchable by name and by an agent's role. Someone who can't take this work stays in the list with the reason (locked). The button names who is assigned, and with onUnassign the list ends with Unassign. With updatedAt, the list says when the load and availability were measured.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0 and 2.9.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/assignee-picker
Brings in: agent-avatar, avatar, bidi, button, chat, command, locale, locked, popover, tokens, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { AssigneePicker, type AssigneeCandidate, type AssigneeWho } from "@/components/candor/assignee-picker"
const inDays = (d: number) => new Date(Date.now() + d * 86_400_000).toISOString()
const measured = new Date().toISOString()
/* Load and availability come from the app: what it doesn't know (Lee's) reads as not known. */
const team: AssigneeCandidate[] = [
{ who: { person: { id: "u-ana", name: "Ana Ruiz" } }, availability: "available", load: { open: 3, limit: 6 } },
{ who: { person: { id: "u-jo", name: "Jo Okafor" } }, availability: "away", until: inDays(3), load: { open: 1 } },
{ who: { person: { id: "u-lee", name: "Lee Park" } } },
{ who: { person: { id: "u-kim", name: "Kim Sato" } }, availability: "available", load: { open: 0 }, locked: "Kim is a viewer here and can’t own tasks." },
{ who: { agent: { id: "a-recon", name: "Reconciler", role: "analyst" } }, availability: "busy", load: { open: 8, limit: 8 } },
{ who: { agent: { id: "a-check", name: "Checker", role: "reviewer" } }, availability: "available", load: { open: 2, limit: 10 } },
]
export default function Example() {
const [assigned, setAssigned] = React.useState<AssigneeWho | null>(null)
const [saving, setSaving] = React.useState(false)
const save = (next: AssigneeWho | null) => {
setSaving(true)
// Your app saves the assignment, then passes the saved assignee back.
window.setTimeout(() => {
setAssigned(next)
setSaving(false)
}, 600)
}
return (
<div className="flex max-w-md items-center justify-between gap-3 rounded-lg border bg-card px-4 py-3">
<span className="min-w-0">
<span className="block truncate text-sm font-medium">Match March payments to invoices</span>
<span className="block text-xs text-muted-foreground">Due Friday</span>
</span>
<AssigneePicker
candidates={team}
assigned={assigned}
busy={saving}
updatedAt={measured}
onAssign={(c) => save(c.who)}
onUnassign={() => save(null)}
/>
</div>
)
}<AssigneePicker>
| Prop | Type | Default | Description |
|---|---|---|---|
| candidatesrequired | AssigneeCandidate[] | null | — | Who the work can go to, people and agents, in the order to show them. `null` when the app doesn't know (its directory is down). |
| assigned | AssigneeWho | null | undefined | — | Who has the work now, from the app's record; `null` or left out when no one does. They needn't be among `candidates`. |
| onAssignrequired | (candidate: AssigneeCandidate) => void | — | Called with the candidate the person chose. Not called for the one already assigned, or one that's `locked`. |
| onUnassign | (() => void) | undefined | — | Called on Unassign. Without it there is no Unassign. |
| busy | boolean | undefined | false | The assignment is being saved: the button is locked, with the reason. |
| locked | string | null | undefined | — | Why this person can't change the assignee, when they can't. Locks the button with it. |
| updatedAt | Date | string | undefined | — | When the app measured the load and availability shown. Without it, no time is shown. |
| label | string | undefined | — | What is being assigned, as the button's label: "Owner", "Reviewer". Defaults to "Assignee". |
| className | string | undefined | — | Classes for the button. |
Strings
The fixed words, in English by default. Change any of them under assigneePicker in <LocaleProvider strings={{ … }}>; the table is exported as assigneePickerStrings.
| Key | English |
|---|---|
| label | Assignee |
| trigger | {label}: {name} |
| unassigned | Unassigned |
| search | Search people and agents… |
| people | People |
| agents | Agents |
| agent | {noun} agent |
| assigned | Assigned |
| unassign | Unassign |
| noMatch | No one matches. |
| nobody | No one can be assigned. |
| unknownWho | Who can be assigned isn’t known. |
| available | Available |
| busy | Busy |
| away | Away |
| offline | Offline |
| busyUntil | Busy until {time} |
| awayUntil | Away until {time} |
| offlineUntil | Offline until {time} |
| availabilityUnknown | Availability not known |
| load | { one: "{count} open task", other: "{count} open tasks" } |
| loadLimit | { one: "{count} of {limit} open task", other: "{count} of {limit} open tasks" } |
| loadUnknown | Load not known |
| asOf | Load and availability as of {time} |
| lockedAssigning | The assignment is being saved. |
Types
- AssigneePerson
- A person who can be assigned.
{ id: string; name: string; image?: string | undefined }- AssigneeWho
- Who is assigned, or can be: a person, or an agent named by the app (never by model output).
{ person: AssigneePerson } | { agent: AgentIdentity }- AssigneeAvailability
- Whether someone can take work now, as the app knows it.
"available" | "busy" | "away" | "offline"- AssigneeLoad
- How much is assigned to someone now, as the app counted it.
{ /** Their open tasks (or whatever the app counts), measured. */ open: number; /** The most the app lets them hold, when it sets one. */ limit?: number | undefined; }- AssigneeCandidate
- One person or agent the work can go to, with what the app knows of their load and availability.
{ who: AssigneeWho; /** Whether they can take work now. Left out, it reads "Availability not known": nothing is guessed. */ availability?: AssigneeAvailability | undefined; /** When that changes back, for "Away until Mon 16 Nov, 09:00". Ignored for `available`. */ until?: Date | string | undefined; /** How much they hold now. Left out, it reads "Load not known", never zero. */ load?: AssigneeLoad | undefined; /** Why they can't be assigned this work (not a member here, lacks the role). Printed on them; choosing them does nothing. */ locked?: string | null | undefined; }
Types from other items
The props above take these. Each is defined by another item; import it from the path shown.
- AgentIdentity from @/lib/candor/chat
{ id: string; /** The displayed name, e.g. "Architect agent". Never derived from model output. */ name: string; role: AgentRole; specialty?: string | undefined; }- AgentRole from @/lib/candor/chat
- What an agent does: "assistant", "coder", "reviewer"… The app registers each role's glyph and hue (AgentRolesProvider).
string