From 624f6cad33022c4bd1f46e84975d3708954d3541 Mon Sep 17 00:00:00 2001 From: Matthew Valancy Date: Thu, 18 Jun 2026 07:29:30 -0700 Subject: [PATCH] UX-1: click-to-copy node IDs + reusable CopyableId chip (TDD) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New pure lib/clipboard.ts: shortId(id,len) + copyText() (Clipboard API with a hidden-textarea/execCommand fallback). Unit-tested first (5/5). - New reusable CopyableId chip: shows a short id, copies the full id on click, flashes a transient "Copied!" confirmation. Drop-in anywhere an id is shown. - Wired into the NodeInspector header (always visible across Card/Contents/ Diagram modes). Tests: clipboard unit 5/5; copy-id @copyid e2e (chip copies → clipboard holds the full id → "Copied!" flashes). THE GATE 5/5. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/web/src/components/CopyableId.tsx | 43 +++++++++++++++++++ packages/web/src/components/NodeInspector.tsx | 2 + .../web/src/lib/__tests__/clipboard.test.ts | 36 ++++++++++++++++ packages/web/src/lib/clipboard.ts | 39 +++++++++++++++++ tests/e2e/copy-id.spec.ts | 30 +++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 packages/web/src/components/CopyableId.tsx create mode 100644 packages/web/src/lib/__tests__/clipboard.test.ts create mode 100644 packages/web/src/lib/clipboard.ts create mode 100644 tests/e2e/copy-id.spec.ts diff --git a/packages/web/src/components/CopyableId.tsx b/packages/web/src/components/CopyableId.tsx new file mode 100644 index 00000000..a19b95de --- /dev/null +++ b/packages/web/src/components/CopyableId.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { Copy, Check } from 'lucide-react'; +import { shortId, copyText } from '../lib/clipboard'; + +interface CopyableIdProps { + id: string; + /** Characters of the id to show before truncating (default 8). */ + length?: number; + label?: string; + testId?: string; +} + +/** + * Click-to-copy id chip (#23): shows a short id + copy icon; clicking copies the + * FULL id and flashes a transient "Copied!" confirmation. Reusable anywhere an + * id is shown (inspector, modals, lists). + */ +export function CopyableId({ id, length = 8, label = 'ID', testId = 'copyable-id' }: CopyableIdProps) { + const [copied, setCopied] = useState(false); + if (!id) return null; + const onCopy = async () => { + const ok = await copyText(id); + if (ok) { + setCopied(true); + setTimeout(() => setCopied(false), 1400); + } + }; + return ( + + ); +} diff --git a/packages/web/src/components/NodeInspector.tsx b/packages/web/src/components/NodeInspector.tsx index 3b3e7fcd..62dab7e2 100644 --- a/packages/web/src/components/NodeInspector.tsx +++ b/packages/web/src/components/NodeInspector.tsx @@ -4,6 +4,7 @@ import { useGraph } from '../contexts/GraphContext'; import { getTypeConfig, getStatusConfig } from '../constants/workItemConstants'; import type { WorkItemType } from '../constants/workItemConstants'; import { NodeSubgraphPreview } from './NodeSubgraphPreview'; +import { CopyableId } from './CopyableId'; // Heavy (markdown + Prism) — lazy so it's out of the main bundle until a node's // contents are first opened. @@ -50,6 +51,7 @@ export function NodeInspector({ node, onClose, compact = false, rootTestId = 'no
{typeCfg.label}
{node.title}
+