{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "plan-editor",
  "title": "PlanEditor",
  "description": "Reorder, edit, remove and add steps before anything runs. Alt+Up and Alt+Down move a step.",
  "registryDependencies": [
    "https://creance.pages.dev/r/button.json",
    "https://creance.pages.dev/r/creance-base.json",
    "https://creance.pages.dev/r/icon-button.json",
    "https://creance.pages.dev/r/input.json"
  ],
  "files": [
    {
      "path": "plan-editor/PlanEditor.css",
      "content": ".cr-plan {\n  display: grid;\n  gap: var(--cr-space-4);\n  padding: var(--cr-space-6);\n  border-radius: var(--cr-radius-xl);\n  color: var(--cr-color-text-default);\n  font-family: var(--cr-font-family-sans);\n  font-size: var(--cr-font-size-md);\n}\n\n.cr-plan__head {\n  display: grid;\n  gap: var(--cr-space-1);\n}\n\n.cr-plan__title {\n  margin: 0;\n  font-family: var(--cr-font-family-display);\n  font-size: var(--cr-font-size-2xl);\n  font-weight: var(--cr-font-weight-bold);\n  letter-spacing: var(--cr-font-tracking-title);\n}\n\n.cr-plan__summary,\n.cr-plan__hint {\n  color: var(--cr-color-text-muted);\n  font-size: var(--cr-font-size-sm);\n}\n\n.cr-plan__steps {\n  display: grid;\n  gap: var(--cr-space-2);\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n\n.cr-plan__step {\n  display: flex;\n  align-items: center;\n  gap: var(--cr-space-3);\n  padding: 6px 6px 6px var(--cr-space-3);\n  border-radius: 14px;\n  background: var(--cr-color-glass-tile);\n}\n\n.cr-plan__step[data-removed] {\n  background: transparent;\n  box-shadow: inset 0 0 0 1px var(--cr-color-border-default);\n}\n\n.cr-plan__num {\n  display: grid;\n  flex: none;\n  place-items: center;\n  width: 26px;\n  height: 26px;\n  border-radius: var(--cr-radius-full);\n  background: var(--cr-color-bg-subtle);\n  font-size: var(--cr-font-size-xs);\n  font-weight: var(--cr-font-weight-bold);\n}\n\n.cr-plan__text {\n  flex: 1;\n  min-width: 0;\n  min-height: var(--cr-control-height-sm);\n  padding: 0 var(--cr-space-2);\n  border: 0;\n  border-radius: var(--cr-radius-sm);\n  background: none;\n  color: inherit;\n  font: inherit;\n  text-align: start;\n  cursor: text;\n}\n\n.cr-plan__text:hover:not(:disabled) {\n  background: var(--cr-color-bg-subtle);\n}\n\n.cr-plan__text:disabled {\n  color: var(--cr-color-text-muted);\n  text-decoration: line-through;\n  cursor: default;\n}\n\n.cr-plan__text:focus-visible {\n  outline: var(--cr-focus-width) solid var(--cr-color-focus);\n  outline-offset: var(--cr-focus-offset);\n}\n\n.cr-plan__step .cr-input {\n  flex: 1;\n}\n\n.cr-plan__tag {\n  flex: none;\n  padding: 3px 10px;\n  border-radius: var(--cr-radius-full);\n  background: var(--cr-color-ink-subtle);\n  font-size: var(--cr-font-size-xs);\n  font-weight: var(--cr-font-weight-semibold);\n}\n\n.cr-plan__tools {\n  display: flex;\n  flex: none;\n}\n\n.cr-plan__up {\n  transform: rotate(180deg);\n}\n\n.cr-plan__add {\n  justify-self: start;\n}\n\n.cr-plan__actions {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: flex-end;\n  gap: var(--cr-space-2);\n}\n\n.cr-plan__hint {\n  margin-inline-end: auto;\n}\n",
      "type": "registry:file",
      "target": "@components/creance/plan-editor/PlanEditor.css"
    },
    {
      "path": "plan-editor/PlanEditor.tsx",
      "content": "'use client';\n\nimport { useEffect, useId, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';\nimport { Button } from '@/components/creance/button/Button';\nimport { IconButton } from '@/components/creance/icon-button/IconButton';\nimport { Input } from '@/components/creance/input/Input';\nimport { cx } from '@/lib/creance/cx';\nimport { ArrowDownIcon, CrossIcon, PlusIcon } from '@/lib/creance/icons';\nimport { LiveRegion } from '@/lib/creance/LiveRegion';\nimport './PlanEditor.css';\n\nexport interface PlanStep {\n  id: string;\n  text: string;\n}\n\nexport interface PlanEditorProps {\n  /** The plan as the agent proposed it. */\n  steps: PlanStep[];\n  agentName: string;\n  /** Called with the plan the person approved. Nothing runs before this. */\n  onStart: (steps: PlanStep[]) => void;\n  /** Default \"{agentName}'s plan\". */\n  title?: ReactNode;\n  headingLevel?: 2 | 3 | 4;\n  className?: string;\n}\n\ninterface Row extends PlanStep {\n  origin: 'agent' | 'you';\n  edited: boolean;\n  removed: boolean;\n}\n\nconst fromAgent = (steps: PlanStep[]): Row[] => steps.map((s) => ({ ...s, origin: 'agent', edited: false, removed: false }));\nlet added = 0;\n\n/**\n * The agent proposes a plan; the person reorders, edits, removes or adds steps before anything runs.\n * Alt+Up and Alt+Down move the focused step; every move is announced.\n */\nexport function PlanEditor({ steps, agentName, onStart, title, headingLevel = 3, className }: PlanEditorProps) {\n  const id = useId();\n  const H = `h${headingLevel}` as const;\n  const [rows, setRows] = useState(() => fromAgent(steps));\n  const [editing, setEditing] = useState<string | null>(null);\n  const [draft, setDraft] = useState('');\n  const [message, setMessage] = useState('');\n  const refocus = useRef<string | null>(null);\n  const live = rows.filter((r) => !r.removed);\n  const changes = rows.filter((r) => r.origin === 'you' || r.edited || r.removed).length;\n\n  useEffect(() => {\n    if (refocus.current) document.getElementById(refocus.current)?.focus();\n    refocus.current = null;\n  });\n\n  const move = (rowId: string, dir: -1 | 1) => {\n    const order = live.map((r) => r.id);\n    const at = order.indexOf(rowId);\n    const to = at + dir;\n    if (to < 0 || to >= order.length) return;\n    const swap = order[to]!;\n    setRows((prev) => {\n      const next = [...prev];\n      const a = next.findIndex((r) => r.id === rowId);\n      const b = next.findIndex((r) => r.id === swap);\n      [next[a], next[b]] = [next[b]!, next[a]!];\n      return next;\n    });\n    setMessage(`Step moved to position ${to + 1} of ${order.length}`);\n    refocus.current = `${id}-text-${rowId}`;\n  };\n\n  const save = (rowId: string) => {\n    const text = draft.trim();\n    setRows((prev) =>\n      text ? prev.map((r) => (r.id === rowId ? { ...r, text, edited: r.origin === 'agent' && text !== steps.find((s) => s.id === rowId)?.text } : r)) : prev.filter((r) => r.id !== rowId || r.origin === 'agent'),\n    );\n    setEditing(null);\n    refocus.current = `${id}-text-${rowId}`;\n  };\n\n  const keys = (e: KeyboardEvent, rowId: string) => {\n    if (e.altKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n      e.preventDefault();\n      move(rowId, e.key === 'ArrowUp' ? -1 : 1);\n    }\n  };\n\n  return (\n    <section aria-labelledby={id} className={cx('cr-plan cr-glass', className)}>\n      <div className=\"cr-plan__head\">\n        <H id={id} className=\"cr-plan__title\">\n          {title ?? `${agentName}'s plan`}\n        </H>\n        <span className=\"cr-plan__summary\">\n          Nothing runs until you start it.{changes ? ` ${changes} ${changes === 1 ? 'change' : 'changes'} from the agent's plan.` : ''}\n        </span>\n      </div>\n      <ol className=\"cr-plan__steps\">\n        {rows.map((r) => {\n          const n = live.indexOf(r) + 1;\n          const tag = r.removed ? 'Removed' : r.origin === 'you' ? 'Added by you' : r.edited ? 'Edited' : null;\n          return (\n            <li key={r.id} className=\"cr-plan__step\" data-removed={r.removed || undefined}>\n              <span className=\"cr-plan__num\" aria-hidden=\"true\">\n                {r.removed ? '−' : n}\n              </span>\n              {editing === r.id ? (\n                <Input\n                  autoFocus\n                  aria-label={`Step ${n}`}\n                  value={draft}\n                  onChange={(e) => setDraft(e.currentTarget.value)}\n                  onBlur={() => save(r.id)}\n                  onKeyDown={(e) => {\n                    if (e.key === 'Enter') save(r.id);\n                    if (e.key === 'Escape') {\n                      setEditing(null);\n                      refocus.current = `${id}-text-${r.id}`;\n                    }\n                  }}\n                />\n              ) : (\n                <button\n                  type=\"button\"\n                  id={`${id}-text-${r.id}`}\n                  className=\"cr-plan__text\"\n                  disabled={r.removed}\n                  aria-label={r.removed ? undefined : `Edit step ${n}: ${r.text}`}\n                  onClick={() => {\n                    setDraft(r.text);\n                    setEditing(r.id);\n                  }}\n                  onKeyDown={(e) => keys(e, r.id)}\n                >\n                  {r.text}\n                </button>\n              )}\n              {tag && <span className=\"cr-plan__tag\">{tag}</span>}\n              {r.removed ? (\n                <Button size=\"sm\" variant=\"ghost\" onClick={() => setRows((p) => p.map((x) => (x.id === r.id ? { ...x, removed: false } : x)))}>\n                  Restore\n                </Button>\n              ) : (\n                <span className=\"cr-plan__tools\">\n                  <IconButton size=\"sm\" tooltip={false} label={`Move step ${n} up`} icon={<ArrowDownIcon className=\"cr-plan__up\" />} disabled={n === 1} onClick={() => move(r.id, -1)} />\n                  <IconButton size=\"sm\" tooltip={false} label={`Move step ${n} down`} icon={<ArrowDownIcon />} disabled={n === live.length} onClick={() => move(r.id, 1)} />\n                  <IconButton\n                    size=\"sm\"\n                    tooltip={false}\n                    label={`Remove step ${n}`}\n                    icon={<CrossIcon />}\n                    onClick={() => {\n                      setRows((p) => (r.origin === 'you' ? p.filter((x) => x.id !== r.id) : p.map((x) => (x.id === r.id ? { ...x, removed: true } : x))));\n                      setMessage(`Step ${n} removed`);\n                    }}\n                  />\n                </span>\n              )}\n            </li>\n          );\n        })}\n      </ol>\n      <Button\n        variant=\"ghost\"\n        className=\"cr-plan__add\"\n        onClick={() => {\n          const rowId = `added-${++added}`;\n          setRows((p) => [...p, { id: rowId, text: '', origin: 'you', edited: false, removed: false }]);\n          setDraft('');\n          setEditing(rowId);\n        }}\n      >\n        <PlusIcon />\n        Add a step\n      </Button>\n      <div className=\"cr-plan__actions\">\n        <span className=\"cr-plan__hint\">Alt + ↑ ↓ moves the focused step.</span>\n        <Button disabled={!changes} onClick={() => setRows(fromAgent(steps))}>\n          Reset to the agent&apos;s plan\n        </Button>\n        <Button variant=\"primary\" disabled={!live.length || editing !== null} onClick={() => onStart(live.map(({ id: i, text }) => ({ id: i, text })))}>\n          Start with this plan\n        </Button>\n      </div>\n      <LiveRegion message={message} />\n    </section>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/creance/plan-editor/PlanEditor.tsx"
    }
  ],
  "categories": [
    "organism",
    "agent"
  ],
  "type": "registry:ui"
}