{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "env-editor",
  "type": "registry:ui",
  "title": "Env Editor",
  "description": "Environment variable rows with masked values, paste-a-.env import and duplicate warnings.",
  "categories": [
    "developer"
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json",
    "https://www.stealth.pm/r/motion.json",
    "https://www.stealth.pm/r/use-controllable-state.json",
    "https://www.stealth.pm/r/copy-button.json"
  ],
  "files": [
    {
      "path": "components/ui/env-editor.tsx",
      "content": "\"use client\";\nimport { Tooltip } from \"@base-ui/react/tooltip\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { useCopy } from \"@/components/ui/copy-button\";\nimport { cn } from \"@/lib/cn\";\nimport { Check, Copy, Eye, EyeOff, Plus, Trash, Warning } from \"@/lib/icons\";\nimport { ease, spring } from \"@/lib/motion\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\n\nexport type EnvVar = { id: string; key: string; value: string };\n\nconst KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;\nlet seq = 0;\n/** A new row with a stable id. Call it in event handlers, not during render. */\nexport const createEnvVar = (key = \"\", value = \"\"): EnvVar => ({ id: `env-${Date.now().toString(36)}-${(seq++).toString(36)}`, key, value });\n\n/** Reads KEY=value lines: skips blanks and comments, drops `export`, unquotes, strips trailing # comments. */\nexport function parseEnv(text: string): { key: string; value: string }[] {\n  const out: { key: string; value: string }[] = [];\n  for (const raw of text.split(/\\r?\\n/)) {\n    const line = raw.trim().replace(/^export\\s+/, \"\");\n    if (!line || line.startsWith(\"#\")) continue;\n    const at = line.indexOf(\"=\");\n    if (at <= 0) continue;\n    const key = line.slice(0, at).trim();\n    let value = line.slice(at + 1).trim();\n    const q = value[0];\n    if ((q === '\"' || q === \"'\" || q === \"`\") && value.lastIndexOf(q) > 0) {\n      value = value.slice(1, value.lastIndexOf(q));\n      if (q === '\"') value = value.replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"');\n    } else {\n      value = value.replace(/\\s+#.*$/, \"\");\n    }\n    out.push({ key, value });\n  }\n  return out;\n}\n\n/** Writes rows back out as a .env file, quoting values that need it. */\nexport function toEnv(rows: EnvVar[]) {\n  return rows\n    .filter((r) => r.key.trim())\n    .map((r) => `${r.key.trim()}=${/[\\s#\"'=]/.test(r.value) || r.value === \"\" ? JSON.stringify(r.value) : r.value}`)\n    .join(\"\\n\");\n}\n\nexport type EnvEditorProps = Omit<React.ComponentProps<\"div\">, \"children\" | \"defaultValue\" | \"onChange\"> & {\n  /** The rows (controlled). */\n  value?: EnvVar[];\n  defaultValue?: EnvVar[];\n  onValueChange?: (rows: EnvVar[]) => void;\n  /** Heading above the rows. */\n  title?: string;\n  /** Shows values without editing, and hides add and remove. */\n  readOnly?: boolean;\n  disabled?: boolean;\n};\n\nexport function EnvEditor({\n  value: valueProp,\n  defaultValue = [],\n  onValueChange,\n  title = \"Environment variables\",\n  readOnly = false,\n  disabled = false,\n  className,\n  ...rest\n}: EnvEditorProps) {\n  const reduce = useReducedMotion();\n  const uid = useId();\n  const [rows, setRows] = useControllableState<EnvVar[]>({ value: valueProp, defaultValue, onChange: onValueChange });\n  const [revealed, setRevealed] = useState<Set<string>>(() => new Set());\n  const [touched, setTouched] = useState<Set<string>>(() => new Set());\n  // Rows that just arrived from a paste: they cascade in and hold a wash for a moment.\n  const [fresh, setFresh] = useState<{ ids: Set<string>; order: string[] }>({ ids: new Set(), order: [] });\n  const [note, setNote] = useState(\"\");\n  const focusNext = useRef<string | null>(null);\n  const root = useRef<HTMLDivElement>(null);\n  const [tip] = useState(() => Tooltip.createHandle<string>());\n  const { state: copyState, copy } = useCopy({ timeout: 1600 });\n\n  // Focus a row's key after it mounts (add, or Enter at the end).\n  useEffect(() => {\n    const id = focusNext.current;\n    if (!id) return;\n    focusNext.current = null;\n    root.current?.querySelector<HTMLInputElement>(`[data-key-input=\"${id}\"]`)?.focus();\n  }, [rows]);\n\n  useEffect(() => {\n    if (!fresh.ids.size && !note) return;\n    const t = window.setTimeout(() => {\n      setFresh({ ids: new Set(), order: [] });\n      setNote(\"\");\n    }, 2600);\n    return () => window.clearTimeout(t);\n  }, [fresh, note]);\n\n  const counts = useMemo(() => {\n    const m = new Map<string, number>();\n    for (const r of rows) {\n      const k = r.key.trim();\n      if (k) m.set(k, (m.get(k) ?? 0) + 1);\n    }\n    return m;\n  }, [rows]);\n  // For duplicates, the row whose value is actually used: the last one.\n  const winner = useMemo(() => {\n    const m = new Map<string, string>();\n    for (const r of rows) if (r.key.trim()) m.set(r.key.trim(), r.id);\n    return m;\n  }, [rows]);\n\n  const locked = readOnly || disabled;\n  const allRevealed = rows.length > 0 && rows.every((r) => revealed.has(r.id));\n\n  const update = (id: string, patch: Partial<EnvVar>) => setRows(rows.map((r) => (r.id === id ? { ...r, ...patch } : r)));\n\n  const add = (after?: string) => {\n    const row = createEnvVar();\n    focusNext.current = row.id;\n    const i = after ? rows.findIndex((r) => r.id === after) : -1;\n    setRows(i === -1 ? [...rows, row] : [...rows.slice(0, i + 1), row, ...rows.slice(i + 1)]);\n  };\n\n  const remove = (id: string, focusPrev = false) => {\n    const i = rows.findIndex((r) => r.id === id);\n    const next = rows.filter((r) => r.id !== id);\n    setRows(next);\n    const target = next[Math.max(0, i - (focusPrev ? 1 : 0))] ?? null;\n    // Hand focus to a neighbour so the keyboard never falls back to the page.\n    requestAnimationFrame(() => {\n      const sel = target ? `[data-${focusPrev ? \"value\" : \"key\"}-input=\"${target.id}\"]` : \"[data-add]\";\n      root.current?.querySelector<HTMLElement>(sel)?.focus();\n    });\n  };\n\n  // A pasted .env lands as rows: existing keys are updated in place, new keys appended.\n  const paste = (text: string, into: EnvVar) => {\n    const parsed = parseEnv(text);\n    if (!parsed.length) return false;\n    let next = [...rows];\n    const touchedIds: string[] = [];\n    let added = 0;\n    let updated = 0;\n    for (const [n, p] of parsed.entries()) {\n      const existing = next.find((r) => r.key.trim() === p.key && r.id !== into.id);\n      if (n === 0 && !into.key.trim() && !into.value) {\n        next = next.map((r) => (r.id === into.id ? { ...r, key: p.key, value: p.value } : r));\n        touchedIds.push(into.id);\n        added++;\n      } else if (existing) {\n        next = next.map((r) => (r.id === existing.id ? { ...r, value: p.value } : r));\n        touchedIds.push(existing.id);\n        updated++;\n      } else {\n        const row = createEnvVar(p.key, p.value);\n        next.push(row);\n        touchedIds.push(row.id);\n        added++;\n      }\n    }\n    setRows(next);\n    setFresh({ ids: new Set(touchedIds), order: touchedIds });\n    const say = (n: number, verb: string) => `${verb} ${n} ${n === 1 ? \"variable\" : \"variables\"}`;\n    setNote([added && say(added, \"Added\"), updated && say(updated, \"updated\")].filter(Boolean).join(\", \").replace(/^u/, \"U\"));\n    return true;\n  };\n\n  const toggleReveal = (id: string) => {\n    const next = new Set(revealed);\n    if (next.has(id)) next.delete(id);\n    else next.add(id);\n    setRevealed(next);\n  };\n\n  const field = cn(\n    \"h-8 w-full min-w-0 rounded-lg border bg-frame px-2.5 font-mono text-base text-fg outline-none sm:text-[12.5px]\",\n    \"transition-[border-color,box-shadow,background-color] duration-150 placeholder:text-fg-4\",\n    \"hover:border-fg-4 focus:border-fg-4 focus:ring-3 focus:ring-fg/10\",\n    \"disabled:pointer-events-none disabled:opacity-50 read-only:bg-transparent read-only:hover:border-line-2\",\n  );\n\n  return (\n    <Tooltip.Provider delay={500}>\n      <div\n        ref={root}\n        data-slot=\"env-editor\"\n        data-disabled={disabled || undefined}\n        className={cn(\"flex min-w-0 flex-col rounded-xl border border-line bg-raised text-fg shadow-[var(--shadow)]\", className)}\n        {...rest}\n      >\n        <div className=\"flex h-11 items-center gap-2 border-b border-line pl-4 pr-2\">\n          <h3 className=\"min-w-0 truncate text-[13px] font-medium tracking-[-0.01em]\">{title}</h3>\n          <span className=\"tabular font-mono text-[11px] text-fg-4\">{rows.filter((r) => r.key.trim()).length}</span>\n          <div className=\"ml-auto flex items-center gap-0.5\">\n            <ToolButton\n              label=\"Reveal all values\"\n              hint={allRevealed ? \"Hide all values\" : \"Reveal all values\"}\n              tip={tip}\n              disabled={!rows.length || disabled}\n              pressed={allRevealed}\n              onPress={() => setRevealed(allRevealed ? new Set() : new Set(rows.map((r) => r.id)))}\n            >\n              <SwapIcon k={allRevealed ? \"off\" : \"on\"} reduce={!!reduce}>\n                {allRevealed ? <EyeOff size={15} /> : <Eye size={15} />}\n              </SwapIcon>\n            </ToolButton>\n            <ToolButton\n              label=\"Copy as .env\"\n              hint={copyState === \"copied\" ? \"Copied\" : copyState === \"failed\" ? \"Couldn’t copy\" : \"Copy as .env\"}\n              tip={tip}\n              disabled={!rows.some((r) => r.key.trim())}\n              onPress={() => copy(() => toEnv(rows))}\n            >\n              <SwapIcon k={copyState} reduce={!!reduce}>\n                {copyState === \"copied\" ? <Check size={15} /> : <Copy size={15} />}\n              </SwapIcon>\n            </ToolButton>\n          </div>\n        </div>\n\n        {rows.length === 0 ? (\n          <div className=\"flex flex-col items-center gap-3 px-6 py-9 text-center\">\n            <div>\n              <p className=\"text-[13px] text-fg\">No variables yet</p>\n              <p className=\"mt-1 text-[12px] text-fg-3\">Add one, or paste a .env file into the key field.</p>\n            </div>\n            {!locked && (\n              <button type=\"button\" data-add onClick={() => add()} className={primaryButton}>\n                <Plus size={14} />\n                Add variable\n              </button>\n            )}\n          </div>\n        ) : (\n          <ul aria-label={title} className=\"flex flex-col px-2 pt-2\">\n            <AnimatePresence initial={false}>\n              {rows.map((r, i) => {\n                const k = r.key.trim();\n                const dupe = !!k && (counts.get(k) ?? 0) > 1;\n                const shadowed = dupe && winner.get(k) !== r.id;\n                const invalid = !!k && !KEY.test(k) && touched.has(r.id);\n                const shown = revealed.has(r.id);\n                const msgId = `${uid}-${r.id}-msg`;\n                const order = fresh.order.indexOf(r.id);\n                return (\n                  <motion.li\n                    key={r.id}\n                    initial={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n                    animate={{ opacity: 1, height: \"auto\" }}\n                    exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0, transition: { duration: 0.16, ease: ease.out } }}\n                    transition={{\n                      height: { duration: 0.24, ease: ease.out, delay: order > 0 ? Math.min(order, 8) * 0.03 : 0 },\n                      opacity: { duration: 0.2, ease: ease.out, delay: order > 0 ? Math.min(order, 8) * 0.03 : 0 },\n                    }}\n                    className=\"overflow-hidden\"\n                  >\n                    <div\n                      data-fresh={fresh.ids.has(r.id) || undefined}\n                      className=\"rounded-lg p-1.5 transition-colors duration-700 ease-out data-fresh:bg-fg/[0.05] data-fresh:duration-150\"\n                    >\n                      <div className=\"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5 sm:grid-cols-[minmax(0,5fr)_minmax(0,7fr)_auto]\">\n                        <input\n                          data-key-input={r.id}\n                          value={r.key}\n                          readOnly={readOnly}\n                          disabled={disabled}\n                          placeholder=\"KEY\"\n                          aria-label={`Key ${i + 1}`}\n                          aria-invalid={invalid || dupe || undefined}\n                          aria-describedby={invalid || dupe ? msgId : undefined}\n                          autoCapitalize=\"characters\"\n                          autoComplete=\"off\"\n                          autoCorrect=\"off\"\n                          spellCheck={false}\n                          onChange={(e) => update(r.id, { key: e.target.value.replace(/\\s/g, \"_\") })}\n                          onBlur={() => r.key && !touched.has(r.id) && setTouched(new Set(touched).add(r.id))}\n                          onPaste={(e) => {\n                            const text = e.clipboardData.getData(\"text\");\n                            if (/[\\n=]/.test(text) && paste(text, r)) e.preventDefault();\n                          }}\n                          onKeyDown={(e) => {\n                            if (e.key === \"Backspace\" && !r.key && !r.value && rows.length > 1) {\n                              e.preventDefault();\n                              remove(r.id, true);\n                            }\n                          }}\n                          className={cn(\n                            field,\n                            \"col-start-1 row-start-1 tracking-[0.01em]\",\n                            invalid ? \"border-danger/60 focus:border-danger focus:ring-danger/15\" : dupe ? \"border-warning/60 focus:border-warning focus:ring-warning/15\" : \"border-line-2\",\n                            shadowed && \"text-fg-3 line-through decoration-fg-4\",\n                          )}\n                        />\n                        <div className=\"relative col-span-2 row-start-2 sm:col-span-1 sm:col-start-2 sm:row-start-1\">\n                          <input\n                            data-value-input={r.id}\n                            type={shown ? \"text\" : \"password\"}\n                            value={r.value}\n                            readOnly={readOnly}\n                            disabled={disabled}\n                            placeholder=\"Value\"\n                            aria-label={`Value of ${k || `key ${i + 1}`}`}\n                            autoComplete=\"off\"\n                            autoCorrect=\"off\"\n                            spellCheck={false}\n                            data-1p-ignore\n                            data-lpignore=\"true\"\n                            data-bwignore\n                            onChange={(e) => update(r.id, { value: e.target.value })}\n                            onKeyDown={(e) => {\n                              if (e.key === \"Enter\" && !locked) {\n                                e.preventDefault();\n                                add(r.id);\n                              }\n                            }}\n                            className={cn(field, \"peer border-line-2 pr-9\", !shown && r.value && \"text-transparent focus:text-fg\")}\n                          />\n                          {/* A fixed-length mask at rest, so a hidden value doesn't give away its length. */}\n                          {!shown && r.value && (\n                            <span aria-hidden className=\"pointer-events-none absolute inset-y-0 left-2.5 flex items-center font-mono text-[12.5px] tracking-[0.1em] text-fg-3 peer-focus:hidden\">\n                              ••••••••••••\n                            </span>\n                          )}\n                          <button\n                            type=\"button\"\n                            aria-label={shown ? `Hide value of ${k || \"this key\"}` : `Reveal value of ${k || \"this key\"}`}\n                            aria-pressed={shown}\n                            disabled={disabled || !r.value}\n                            onClick={() => toggleReveal(r.id)}\n                            className={cn(\n                              \"absolute right-1 top-1 grid size-6 place-items-center rounded-md text-fg-3 outline-none\",\n                              \"transition-[background-color,color,scale] duration-150 ease-out hover:bg-fg/[0.06] hover:text-fg active:scale-90 active:duration-75\",\n                              \"focus-visible:outline-1 focus-visible:outline-offset-0 focus-visible:outline-fg-3 focus-visible:outline-solid\",\n                              \"disabled:pointer-events-none disabled:opacity-40\",\n                              \"before:absolute before:-inset-2 before:content-[''] pointer-fine:before:hidden\",\n                            )}\n                          >\n                            <SwapIcon k={shown ? \"off\" : \"on\"} reduce={!!reduce}>\n                              {shown ? <EyeOff size={14} /> : <Eye size={14} />}\n                            </SwapIcon>\n                          </button>\n                        </div>\n                        {!locked && (\n                          <Tooltip.Trigger\n                            handle={tip}\n                            payload=\"Remove\"\n                            render={\n                              <button\n                                type=\"button\"\n                                aria-label={`Remove ${k || `row ${i + 1}`}`}\n                                onClick={() => remove(r.id)}\n                                className={cn(\n                                  \"relative col-start-2 row-start-1 grid size-8 place-items-center rounded-lg text-fg-3 outline-none sm:col-start-3\",\n                                  \"transition-[background-color,color,scale] duration-150 ease-out hover:bg-danger-soft hover:text-danger active:scale-90 active:duration-75\",\n                                  \"focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3 focus-visible:outline-solid\",\n                                  \"before:absolute before:-inset-1.5 before:content-[''] pointer-fine:before:hidden\",\n                                )}\n                              />\n                            }\n                          >\n                            <Trash size={15} />\n                          </Tooltip.Trigger>\n                        )}\n                      </div>\n                      {(invalid || dupe) && (\n                        <p id={msgId} className={cn(\"flex items-center gap-1.5 px-1 pt-1.5 text-[11.5px]\", invalid ? \"text-danger\" : \"text-warning\")}>\n                          <Warning size={13} className=\"shrink-0\" />\n                          {invalid\n                            ? \"Use letters, digits and underscores, not starting with a digit\"\n                            : shadowed\n                              ? `Duplicate key. A later ${k} overrides this one.`\n                              : `Duplicate key. This value overrides the earlier ${k}.`}\n                        </p>\n                      )}\n                    </div>\n                  </motion.li>\n                );\n              })}\n            </AnimatePresence>\n          </ul>\n        )}\n\n        {rows.length > 0 && (\n          <div className=\"flex min-h-12 items-center gap-3 px-3.5 pb-2.5 pt-1\">\n            <span role=\"status\" className=\"sr-only\">\n              {copyState === \"copied\" ? \"Copied as .env\" : copyState === \"failed\" ? \"Couldn’t copy\" : \"\"}\n            </span>\n            {!locked && (\n              <button type=\"button\" data-add onClick={() => add()} className={secondaryButton}>\n                <Plus size={14} />\n                Add variable\n              </button>\n            )}\n            <p className=\"relative min-w-0 flex-1 text-right text-[12px] text-fg-4\" aria-live=\"polite\">\n              <AnimatePresence mode=\"popLayout\" initial={false}>\n                <motion.span\n                  key={note || \"hint\"}\n                  className=\"block truncate\"\n                  initial={reduce ? { opacity: 0 } : { opacity: 0, y: 4 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  exit={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n                  transition={{ duration: 0.2, ease: ease.out }}\n                >\n                  {note ? <span className=\"text-fg-2\">{note}</span> : locked ? \"\" : <span className=\"hidden sm:pointer-fine:inline\">Paste a .env into any key to add many</span>}\n                </motion.span>\n              </AnimatePresence>\n            </p>\n          </div>\n        )}\n      </div>\n\n      <Tooltip.Root handle={tip}>\n        {({ payload }) => (\n          <Tooltip.Portal>\n            <Tooltip.Positioner side=\"top\" sideOffset={6} collisionPadding={8} className=\"z-(--z-tooltip)\">\n              <Tooltip.Popup\n                className={cn(\n                  \"flex min-h-6 items-center rounded-md border border-line-2 bg-raised px-2 py-1 text-[12px] leading-4 text-fg shadow-pop\",\n                  \"origin-(--transform-origin) transition-[opacity,scale,translate] duration-150 ease-out-expo\",\n                  \"data-starting-style:translate-y-0.5 data-starting-style:scale-96 data-starting-style:opacity-0 data-ending-style:scale-98 data-ending-style:opacity-0 data-ending-style:duration-100\",\n                  \"data-instant:duration-0\",\n                )}\n              >\n                {payload}\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        )}\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n\nconst primaryButton = cn(\n  \"inline-flex h-8 items-center gap-1.5 rounded-lg bg-fg px-3 text-[12.5px] font-medium text-frame outline-none\",\n  \"transition-[background-color,scale] duration-150 ease-out hover:bg-fg/90 active:scale-[0.97] active:duration-75\",\n  \"focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3 focus-visible:outline-solid\",\n);\nconst secondaryButton = cn(\n  \"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg border border-line-2 bg-raised px-2.5 text-[12.5px] font-medium text-fg shadow-[var(--shadow)] outline-none\",\n  \"transition-[background-color,border-color,scale] duration-150 ease-out hover:border-fg-4 hover:bg-hover active:scale-[0.97] active:duration-75\",\n  \"focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3 focus-visible:outline-solid\",\n);\n\nfunction SwapIcon({ k, reduce, children }: { k: string; reduce: boolean; children: React.ReactNode }) {\n  return (\n    <span className=\"relative grid size-4 place-items-center\">\n      <AnimatePresence initial={false}>\n        <motion.span\n          key={k}\n          className=\"absolute inset-0 grid place-items-center\"\n          initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n          animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }}\n          exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n          transition={reduce ? { duration: 0.12 } : spring.pop}\n        >\n          {children}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  );\n}\n\nfunction ToolButton({\n  label,\n  hint,\n  tip,\n  onPress,\n  disabled,\n  pressed,\n  children,\n}: {\n  label: string;\n  hint: string;\n  tip: ReturnType<typeof Tooltip.createHandle<string>>;\n  onPress: () => void;\n  disabled?: boolean;\n  pressed?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Tooltip.Trigger\n      handle={tip}\n      payload={hint}\n      closeOnClick={false}\n      render={\n        <button\n          type=\"button\"\n          aria-label={label}\n          aria-pressed={pressed}\n          disabled={disabled}\n          onClick={onPress}\n          className={cn(\n            \"relative grid size-8 place-items-center rounded-lg text-fg-3 outline-none\",\n            \"transition-[background-color,color,scale] duration-150 ease-out hover:bg-fg/[0.06] hover:text-fg active:scale-[0.92] active:duration-75\",\n            \"focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3 focus-visible:outline-solid\",\n            \"disabled:pointer-events-none disabled:opacity-40\",\n            \"before:absolute before:-inset-1.5 before:content-[''] pointer-fine:before:hidden\",\n          )}\n        />\n      }\n    >\n      {children}\n    </Tooltip.Trigger>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/env-editor"
}
