{ "name": "env-diff", "type": "registry:ui", "title": "Env Diff", "description": "Two environments side by side, values masked by default. A variable missing from one side is drawn differently from one whose value merely differs — \"not set\" and \"set to something else\" have different causes.", "category": "Runtime", "dependencies": [ "lucide-react@^1.39.0" ], "registryDependencies": [ "badge", "button", "lib-motion", "lib-styles", "lib-utils" ], "files": [ { "path": "components/ui/env-diff.tsx", "type": "registry:ui", "content": "'use client'\n\nimport { useMemo, useState, type ComponentProps, type ReactNode } from 'react'\nimport { Eye, EyeOff, TriangleAlert } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { enterFade } from '@/lib/motion'\nimport { radius, surface } from '@/lib/styles'\nimport { cn } from '@/lib/utils'\n\n/**\n * Two environments' variables side by side.\n *\n * Values are masked by default and revealed per row. This is the screen someone\n * opens when staging behaves differently from production, and it is exactly the\n * screen that gets screenshotted into a shared channel with a database password\n * in it.\n *\n * A variable missing from one side is the finding, and it is drawn differently\n * from one whose value merely differs — \"not set\" and \"set to something else\"\n * have completely different causes. Both are ranked above equal rows.\n *\n * Whitespace and length differences are called out even when the masked values\n * look identical, because a trailing newline in a secret is a genuinely common\n * and genuinely invisible outage.\n *\n * One grid owns the columns and every row inherits them through `subgrid`.\n * Per-row grids only agree on fixed tracks: `1fr` and `auto` resolve against\n * whatever that row holds, so a row with a reveal button lines up differently\n * from one without.\n */\nexport type EnvEntry = { key: string; value: string | undefined; secret?: boolean }\n\ntype Row = {\n key: string\n left: string | undefined\n right: string | undefined\n secret: boolean\n state: 'missing-left' | 'missing-right' | 'differs' | 'same'\n whitespace: boolean\n}\n\nconst RANK = { 'missing-left': 0, 'missing-right': 0, differs: 1, same: 2 }\n\n/**\n * Default label formatters, hoisted out of the parameter list.\n *\n * An arrow function written inline as a default parameter is a value the\n * React Compiler cannot safely reorder, so it bails on the whole component\n * and none of it gets auto-memoised. At module scope it is a stable\n * reference and the component compiles.\n */\nconst DEFAULT_TOGGLE_SAME_LABEL: (count: number) => ReactNode = (count) => `Show ${count} identical`\n\nfunction EnvDiff({\n left,\n right,\n leftLabel = 'Left',\n rightLabel = 'Right',\n notSetLabel = 'not set',\n whitespaceNote = 'differs only in whitespace',\n revealLabel = 'Reveal value',\n hideLabel = 'Hide value',\n sameLabel = 'identical',\n showSame = false,\n toggleSameLabel = DEFAULT_TOGGLE_SAME_LABEL,\n hideSameLabel = 'Hide identical',\n className,\n ...props\n}: Omit, 'children'> & {\n left: EnvEntry[]\n right: EnvEntry[]\n leftLabel?: ReactNode\n rightLabel?: ReactNode\n notSetLabel?: ReactNode\n whitespaceNote?: ReactNode\n revealLabel?: string\n hideLabel?: string\n sameLabel?: ReactNode\n showSame?: boolean\n toggleSameLabel?: (count: number) => ReactNode\n hideSameLabel?: ReactNode\n}) {\n const [revealed, setRevealed] = useState([])\n const [same, setSame] = useState(showSame)\n\n const rows = useMemo(() => {\n const leftMap = new Map(left.map((e) => [e.key, e]))\n const rightMap = new Map(right.map((e) => [e.key, e]))\n const keys = [...new Set([...leftMap.keys(), ...rightMap.keys()])].sort()\n\n return keys\n .map((key) => {\n const a = leftMap.get(key)\n const b = rightMap.get(key)\n const state: Row['state'] =\n a?.value === undefined && b?.value !== undefined\n ? 'missing-left'\n : b?.value === undefined && a?.value !== undefined\n ? 'missing-right'\n : a?.value === b?.value\n ? 'same'\n : 'differs'\n return {\n key,\n left: a?.value,\n right: b?.value,\n secret: Boolean(a?.secret || b?.secret),\n state,\n // A trailing newline in a secret is invisible and takes a service down.\n whitespace:\n state === 'differs' &&\n (a?.value ?? '').trim() === (b?.value ?? '').trim(),\n }\n })\n .sort((x, y) => RANK[x.state] - RANK[y.state] || x.key.localeCompare(y.key))\n }, [left, right])\n\n const sameCount = rows.filter((row) => row.state === 'same').length\n const visible = same ? rows : rows.filter((row) => row.state !== 'same')\n\n const show = (row: Row, value: string | undefined) => {\n if (value === undefined) {\n return {notSetLabel}\n }\n if (row.secret && !revealed.includes(row.key)) {\n return {'•'.repeat(Math.min(value.length, 16))}\n }\n return value === '' ? \"\" : value\n }\n\n return (\n \n
\n
\n Variable\n {leftLabel}\n {rightLabel}\n {sameCount > 0 && (\n \n )}\n
\n\n
    \n {visible.map((row) => (\n \n \n {row.key}\n {row.state === 'same' && (\n \n {sameLabel}\n \n )}\n \n\n \n {show(row, row.left)}\n \n\n \n {show(row, row.right)}\n \n\n {row.secret ? (\n \n setRevealed((current) =>\n current.includes(row.key)\n ? current.filter((k) => k !== row.key)\n : [...current, row.key],\n )\n }\n >\n {revealed.includes(row.key) ? : }\n \n ) : (\n \n )}\n\n {row.whitespace && (\n

    \n \n {whitespaceNote}\n

    \n )}\n \n ))}\n
\n
\n \n )\n}\n\nexport { EnvDiff }\n" } ] }