useAnsweringApprovals
ProThe guard every chat with permission prompts needs: while an answer is being sent, that request's buttons wait, so Allow can't be pressed twice; and an answer that fails to send leaves the prompt answerable again and says so, instead of an unhandled rejection. respond is the chat's own (useCandorChat, useClaudeAgent, useAgUi).
const { answering, answer, failed } = useAnsweringApprovals(respond)
<PermissionPrompt onAllow={() => answer(a.id, true)} onDeny={() => answer(a.id, false)} busy={answering.has(a.id)} />
{(error ?? failed) && <p role="alert">{error ?? failed}</p>}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 and 2.8.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/use-answering-approvals
Usage
"use client"
import * as React from "react"
import { PermissionPrompt } from "@/components/candor/permission-prompt"
import { useAnsweringApprovals } from "@/lib/candor/use-answering-approvals"
const request = { id: "ap-1", tool: "set_flag", title: "The agent wants to turn on new_checkout in production", input: { flag: "new_checkout", environment: "production", on: true } }
export default function Example() {
const [answered, setAnswered] = React.useState<string>()
// Stands in for the chat's own respond (useCandorChat, useClaudeAgent): here the first answer fails to send.
const tries = React.useRef(0)
const respond = React.useCallback(async (id: string, approved: boolean) => {
await new Promise((r) => setTimeout(r, 800))
if (tries.current++ === 0) throw new Error("The network dropped.")
setAnswered(`${id}: ${approved ? "allowed" : "denied"}`)
}, [])
const { answering, answer, failed } = useAnsweringApprovals(respond)
if (answered) return <p className="text-sm text-muted-foreground">Sent. {answered}</p>
return (
<div className="flex max-w-xl flex-col gap-2">
<PermissionPrompt request={request} onAllow={() => answer(request.id, true)} onDeny={() => answer(request.id, false, "Not now")} busy={answering.has(request.id)} />
{failed && (
<p role="alert" className="text-sm text-destructive-text">
{failed}
</p>
)}
</div>
)
}Functions
useAnsweringApprovals(respond: (id: string, approved: boolean, reason?: string) => unknown)Strings
The fixed words, in English by default. Change any of them under answeringApprovals in <LocaleProvider strings={{ … }}>; the table is exported as answeringApprovalsStrings.
| Key | English |
|---|---|
| failed | Your answer wasn’t sent, so the request is still waiting. Answer it again. |
Types
- AnsweringApprovals
- What `useAnsweringApprovals` gives back.
{ /** The ids of the requests whose answer is being sent. Pass `answering.has(id)` as the prompt's `busy`. */ answering: ReadonlySet<string>; /** * Sends one answer through `respond`. While it's being sent, a second answer to the same request is ignored, so a * double press can't answer twice. When it settles the request leaves `answering`, whether it was sent or not. */ answer: (id: string, approved: boolean, reason?: string | undefined) => void; /** * The sentence to show when the last answer failed to send (`respond` threw or its promise rejected), in * LocaleProvider's words; undefined otherwise. The request is still waiting and can be answered again. It clears * when the next answer is sent. */ failed: string | undefined; }