{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "triage-desk",
  "type": "registry:component",
  "title": "Triage Desk",
  "description": "Work through agent output one card at a time, riskiest first, with the shaky parts marked and explained and a single key to approve, edit, reject or skip.",
  "categories": [
    "component",
    "Trust & Control"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://coss.com/ui/r/button.json",
    "https://coss.com/ui/r/kbd.json"
  ],
  "files": [
    {
      "path": "registry/devl/triage-desk.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { CheckIcon, RotateCcwIcon } from \"lucide-react\";\nimport { AnimatePresence, LayoutGroup, motion, useReducedMotion } from \"motion/react\";\nimport { type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface TriageSegment {\n  text: string;\n  risk?: \"high\" | \"medium\";\n  note?: string;\n}\n\nexport interface TriageItem {\n  id: string;\n  title: string;\n  agent: string;\n  kind: string;\n  risk: number;\n  body: TriageSegment[];\n}\n\nexport type TriageAction = \"approve\" | \"edit\" | \"reject\" | \"skip\";\n\nexport interface TriageDeskProps {\n  items: TriageItem[];\n  onResolve?: (item: TriageItem, action: Exclude<TriageAction, \"skip\">, text: string) => void;\n  title?: string;\n  className?: string;\n}\n\nconst stamps: Record<TriageAction, { label: string; tone: string }> = {\n  approve: { label: \"Approved\", tone: \"border-success text-success-foreground\" },\n  edit: { label: \"Edited\", tone: \"border-info text-info-foreground\" },\n  reject: { label: \"Rejected\", tone: \"border-destructive text-destructive-foreground\" },\n  skip: { label: \"Later\", tone: \"border-muted-foreground text-muted-foreground\" },\n};\n\nconst exits: Record<TriageAction, { x: number; y: number; rotate: number }> = {\n  approve: { x: 28, y: -4, rotate: 2 },\n  edit: { x: 28, y: -4, rotate: 2 },\n  reject: { x: -28, y: -4, rotate: -2 },\n  skip: { x: 0, y: -16, rotate: 0 },\n};\n\nconst press = \"transition-[scale,box-shadow,background-color] duration-150 ease-out active:scale-[0.97]\";\nconst easeOut = [0.23, 1, 0.32, 1] as const;\n\nfunction riskTone(risk: number) {\n  if (risk >= 70) return \"bg-destructive/12 text-destructive-foreground\";\n  if (risk >= 40) return \"bg-warning/14 text-warning-foreground\";\n  return \"bg-success/12 text-success-foreground\";\n}\n\nconst plain = (item: TriageItem) => item.body.map((s) => s.text).join(\"\");\n\nexport function TriageDesk({ items, onResolve, title = \"Needs review\", className }: TriageDeskProps) {\n  const reduce = useReducedMotion();\n  const uid = useId();\n  const sorted = useMemo(() => [...items].sort((a, b) => b.risk - a.risk), [items]);\n  const [queue, setQueue] = useState(sorted);\n  const [tally, setTally] = useState({ approved: 0, rejected: 0 });\n  const [stamp, setStamp] = useState<TriageAction | null>(null);\n  const [leaving, setLeaving] = useState<TriageAction>(\"skip\");\n  const [editing, setEditing] = useState(false);\n  const [draft, setDraft] = useState(\"\");\n  const rootRef = useRef<HTMLDivElement>(null);\n  const textRef = useRef<HTMLTextAreaElement>(null);\n  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n  useEffect(() => () => clearTimeout(timer.current), []);\n\n  const current = queue[0];\n\n  const act = (action: TriageAction) => {\n    if (!current || stamp) return;\n    if (action === \"edit\" && !editing) {\n      setDraft(plain(current));\n      setEditing(true);\n      requestAnimationFrame(() => textRef.current?.focus());\n      return;\n    }\n    const final = editing ? \"edit\" : action;\n    setStamp(final);\n    setLeaving(final);\n    timer.current = setTimeout(\n      () => {\n        if (final !== \"skip\") onResolve?.(current, final, final === \"edit\" ? draft : plain(current));\n        setTally((t) => ({\n          approved: t.approved + (final === \"approve\" || final === \"edit\" ? 1 : 0),\n          rejected: t.rejected + (final === \"reject\" ? 1 : 0),\n        }));\n        setQueue((q) => (final === \"skip\" && q.length > 1 ? [...q.slice(1), q[0]] : q.slice(1)));\n        setStamp(null);\n        setEditing(false);\n        rootRef.current?.focus();\n      },\n      reduce ? 150 : 420,\n    );\n  };\n\n  const onKeyDown = (e: KeyboardEvent) => {\n    if (e.metaKey || e.ctrlKey || e.altKey) {\n      if (editing && e.key === \"Enter\") {\n        e.preventDefault();\n        act(\"approve\");\n      }\n      return;\n    }\n    if (editing) {\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        setEditing(false);\n        rootRef.current?.focus();\n      }\n      return;\n    }\n    const map: Record<string, TriageAction> = { a: \"approve\", e: \"edit\", r: \"reject\", s: \"skip\" };\n    const action = map[e.key.toLowerCase()];\n    if (action) {\n      e.preventDefault();\n      act(action);\n    }\n  };\n\n  const reload = () => {\n    setQueue(sorted);\n    setTally({ approved: 0, rejected: 0 });\n    requestAnimationFrame(() => rootRef.current?.focus());\n  };\n\n  const spring = reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 380, damping: 32 };\n  let n = 0;\n\n  return (\n    <div\n      aria-label=\"Review queue. Keys: A approve, E edit, R reject, S skip.\"\n      className={cn(\n        \"group/review w-full max-w-[460px] rounded-2xl border bg-card text-card-foreground shadow-xs/5 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        className,\n      )}\n      onKeyDown={onKeyDown}\n      ref={rootRef}\n      role=\"region\"\n      tabIndex={0}\n    >\n      <div className=\"flex items-center justify-between gap-3 px-4 pt-4\">\n        <h3 className=\"font-medium text-sm\">{title}</h3>\n        <p className=\"text-muted-foreground text-xs tabular-nums\">\n          {queue.length} left · {tally.approved} approved · {tally.rejected} rejected\n        </p>\n      </div>\n\n      <LayoutGroup id={uid}>\n        <ol aria-label=\"Queue, highest risk first\" className=\"flex min-h-6 flex-wrap items-center gap-1 px-4 pt-2.5\">\n          <AnimatePresence initial={false} mode=\"popLayout\">\n            {queue.map((item, i) => (\n              <motion.li\n                animate={{ opacity: 1, scale: 1 }}\n                aria-current={i === 0 ? \"true\" : undefined}\n                className={cn(\n                  \"flex h-6 items-center gap-1.5 rounded-full text-[11px]\",\n                  i === 0 ? \"border bg-muted pr-2 pl-0.5 text-foreground\" : \"text-muted-foreground\",\n                )}\n                exit={{ opacity: 0, scale: 0.8 }}\n                initial={{ opacity: 0, scale: 0.8 }}\n                key={item.id}\n                layout\n                title={`${item.kind} · ${item.title}`}\n                transition={spring}\n              >\n                <motion.span\n                  className={cn(\"rounded-full px-1.5 py-px font-medium font-mono tabular-nums\", riskTone(item.risk))}\n                  layout=\"position\"\n                  transition={spring}\n                >\n                  {item.risk}\n                </motion.span>\n                {i === 0 ? (\n                  <motion.span className=\"max-w-32 truncate\" layout=\"position\" transition={spring}>\n                    {item.kind}\n                  </motion.span>\n                ) : (\n                  <span className=\"sr-only\">{item.kind}</span>\n                )}\n              </motion.li>\n            ))}\n          </AnimatePresence>\n        </ol>\n      </LayoutGroup>\n\n      <div className=\"relative p-2 pt-3\">\n        <AnimatePresence custom={leaving} initial={false} mode=\"popLayout\">\n          {current ? (\n            <motion.article\n              animate={{ opacity: 1, x: 0, y: 0, rotate: 0, scale: 1 }}\n              className=\"relative overflow-hidden rounded-lg border bg-background p-4\"\n              custom={leaving}\n              exit={\n                reduce\n                  ? { opacity: 0, transition: { duration: 0.12 } }\n                  : {\n                      opacity: 0,\n                      ...exits[leaving],\n                      transition: { duration: 0.2, ease: easeOut, opacity: { duration: 0.12, ease: \"easeOut\" } },\n                    }\n              }\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: 12, scale: 0.98 }}\n              key={current.id}\n              transition={reduce ? { duration: 0.12 } : { duration: 0.28, ease: easeOut }}\n            >\n              <div className=\"flex items-start justify-between gap-3\">\n                <div className=\"min-w-0\">\n                  <p className=\"text-muted-foreground text-xs\">\n                    {current.kind} · by {current.agent}\n                  </p>\n                  <h4 className=\"mt-0.5 font-medium text-sm\">{current.title}</h4>\n                </div>\n                <span className={cn(\"shrink-0 rounded-full px-2 py-0.5 font-medium font-mono text-xs tabular-nums\", riskTone(current.risk))}>\n                  risk {current.risk}\n                </span>\n              </div>\n\n              {editing ? (\n                <textarea\n                  aria-label=\"Edit before approving\"\n                  className=\"mt-3 block min-h-28 w-full resize-none rounded-lg border border-input bg-card p-2.5 text-sm leading-relaxed outline-none ring-ring/24 focus-visible:border-ring focus-visible:ring-[3px]\"\n                  onChange={(e) => setDraft(e.target.value)}\n                  ref={textRef}\n                  value={draft}\n                />\n              ) : (\n                <>\n                  <p className=\"mt-3 text-sm leading-relaxed\">\n                    {current.body.map((seg, i) => {\n                      if (!seg.risk) return <span key={i}>{seg.text}</span>;\n                      n++;\n                      return (\n                        <mark\n                          className={cn(\n                            \"rounded-[calc(var(--radius)*0.4)] px-0.5 text-foreground underline decoration-2 underline-offset-[3px]\",\n                            seg.risk === \"high\"\n                              ? \"bg-destructive/12 decoration-destructive/70 decoration-wavy\"\n                              : \"bg-warning/16 decoration-warning/80\",\n                          )}\n                          key={i}\n                          title={seg.note}\n                        >\n                          {seg.text}\n                          <sup className=\"ml-0.5 font-medium font-mono text-[9px] text-muted-foreground\">{n}</sup>\n                        </mark>\n                      );\n                    })}\n                  </p>\n                  {current.body.some((s) => s.note) && (\n                    <ol className=\"mt-3 flex flex-col gap-1 border-t border-dashed pt-2.5 text-xs\">\n                      {current.body\n                        .filter((s) => s.risk)\n                        .map((s, i) => (\n                          <li className=\"flex items-start gap-2 text-muted-foreground\" key={i}>\n                            <span\n                              className={cn(\n                                \"mt-1.5 size-1.5 shrink-0 rounded-full\",\n                                s.risk === \"high\" ? \"bg-destructive\" : \"bg-warning\",\n                              )}\n                            />\n                            <span>\n                              <span className=\"font-mono text-[10px]\">{i + 1}</span> {s.note}\n                            </span>\n                          </li>\n                        ))}\n                    </ol>\n                  )}\n                </>\n              )}\n\n              <AnimatePresence>\n                {stamp && (\n                  <motion.div\n                    animate={{ opacity: 1, scale: 1, rotate: -10 }}\n                    aria-live=\"assertive\"\n                    className=\"pointer-events-none absolute inset-0 flex items-center justify-center bg-background/40\"\n                    initial={reduce ? { opacity: 0, rotate: -10 } : { opacity: 0, scale: 1.25, rotate: -10 }}\n                    transition={{ duration: reduce ? 0.1 : 0.16, ease: easeOut }}\n                  >\n                    <span\n                      className={cn(\n                        \"rounded-lg border-[3px] bg-background/80 px-4 py-1.5 font-bold font-mono text-xl uppercase tracking-[0.2em]\",\n                        stamps[stamp].tone,\n                      )}\n                    >\n                      {stamps[stamp].label}\n                    </span>\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </motion.article>\n          ) : (\n            <motion.div\n              animate={{ opacity: 1, y: 0 }}\n              className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed px-4 py-10 text-center\"\n              initial={{ opacity: 0, y: reduce ? 0 : 12 }}\n              key=\"empty\"\n              transition={{ duration: 0.28, ease: easeOut }}\n            >\n              <span className=\"flex size-9 items-center justify-center rounded-full bg-success text-white\">\n                <CheckIcon className=\"size-4\" strokeWidth={3} />\n              </span>\n              <p className=\"font-medium text-sm\">Queue clear</p>\n              <p className=\"text-muted-foreground text-xs\">\n                {tally.approved} approved, {tally.rejected} rejected.\n              </p>\n              <Button className={cn(\"mt-1\", press)} onClick={reload} size=\"xs\" variant=\"outline\">\n                <RotateCcwIcon />\n                Reload queue\n              </Button>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n\n      {current && (\n        <div className=\"flex items-center gap-1.5 border-t px-3 py-2.5\">\n          {editing ? (\n            <>\n              <Button\n                className={cn(\"mr-auto\", press)}\n                onClick={() => {\n                  setEditing(false);\n                  rootRef.current?.focus();\n                }}\n                size=\"sm\"\n                variant=\"ghost\"\n              >\n                Cancel\n                <Kbd className=\"max-sm:hidden\">Esc</Kbd>\n              </Button>\n              <Button className={press} onClick={() => act(\"approve\")} size=\"sm\">\n                <CheckIcon />\n                Save & approve\n              </Button>\n            </>\n          ) : (\n            <>\n              <ActionButton className=\"mr-auto\" k=\"S\" label=\"Skip\" onClick={() => act(\"skip\")} variant=\"ghost\" />\n              <ActionButton k=\"R\" label=\"Reject\" onClick={() => act(\"reject\")} variant=\"destructive-outline\" />\n              <ActionButton k=\"E\" label=\"Edit\" onClick={() => act(\"edit\")} />\n              <ActionButton k=\"A\" label=\"Approve\" onClick={() => act(\"approve\")} variant=\"default\" />\n            </>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction ActionButton({\n  label,\n  k,\n  onClick,\n  variant = \"outline\",\n  className,\n}: {\n  label: string;\n  k: string;\n  onClick: () => void;\n  variant?: \"default\" | \"outline\" | \"destructive-outline\" | \"ghost\";\n  className?: string;\n}) {\n  return (\n    <Button aria-keyshortcuts={k} className={cn(press, className)} onClick={onClick} size=\"sm\" variant={variant}>\n      {label}\n      <Kbd\n        className={cn(\n          \"opacity-48 transition-opacity group-focus-within/review:opacity-100 max-sm:hidden\",\n          variant === \"default\" && \"bg-current/20 text-current\",\n        )}\n      >\n        {k}\n      </Kbd>\n    </Button>\n  );\n}\n"
    }
  ]
}