@spunto/design-system 0.35.1 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spunto/design-system",
3
- "version": "0.35.1",
3
+ "version": "0.36.0",
4
4
  "description": "Spunto's shared design system — warm/flame tokens, color constants, and UI primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  // machine), then how it was set up.
15
15
 
16
16
  import { createContext, useContext, useState, type ReactNode } from "react"
17
- import { ChevronRightIcon, ExternalLinkIcon, GitBranchIcon, Loader2Icon, XCircleIcon } from "lucide-react"
17
+ import { CheckIcon, ChevronRightIcon, CopyIcon, ExternalLinkIcon, GitBranchIcon, Loader2Icon, XCircleIcon } from "lucide-react"
18
18
 
19
19
  import { cn, formatRelativeTime } from "../../utils"
20
20
  import { TaskCommandLog } from "./command-log"
@@ -55,6 +55,27 @@ function Fact({ label, children }: { label: string; children: ReactNode }) {
55
55
  )
56
56
  }
57
57
 
58
+ /** The branch is what you type next (`git fetch`, `git checkout`), so it is one click from the clipboard. */
59
+ function CopyBranchButton({ branch }: { branch: string }) {
60
+ const [copied, setCopied] = useState(false)
61
+ return (
62
+ <button
63
+ type="button"
64
+ title={copied ? "Copied" : "Copy branch name"}
65
+ aria-label="Copy branch name"
66
+ onClick={() => {
67
+ navigator.clipboard?.writeText(branch).then(() => {
68
+ setCopied(true)
69
+ setTimeout(() => setCopied(false), 1500)
70
+ })
71
+ }}
72
+ className="mt-0.5 shrink-0 text-muted-foreground transition-colors hover:text-foreground"
73
+ >
74
+ {copied ? <CheckIcon className="h-3 w-3 text-emerald-600 dark:text-emerald-400" /> : <CopyIcon className="h-3 w-3" />}
75
+ </button>
76
+ )
77
+ }
78
+
58
79
  function Block({
59
80
  variant,
60
81
  title,
@@ -140,7 +161,8 @@ export function TaskDetails({
140
161
  <Fact label="Branch">
141
162
  <span className="flex items-start gap-1 font-mono break-all">
142
163
  <GitBranchIcon className="mt-0.5 h-3 w-3 shrink-0 text-muted-foreground" />
143
- {task.branch}
164
+ <span className="min-w-0">{task.branch}</span>
165
+ <CopyBranchButton branch={task.branch} />
144
166
  </span>
145
167
  </Fact>
146
168
  </div>
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { ReactNode } from "react"
9
9
  import { describe, it, expect, vi } from "vitest"
10
- import { render, screen, fireEvent } from "@testing-library/react"
10
+ import { render, screen, fireEvent, waitFor } from "@testing-library/react"
11
11
 
12
12
  import { renderInlineMarkdown } from "./agent-markdown"
13
13
  import { emptyCommandTerminalState, planCommandTerminalWrites } from "./command-terminal"
@@ -15,6 +15,7 @@ import { TaskComposer } from "./composer"
15
15
  import { TaskBoot, bootSteps } from "./machine"
16
16
  import { formatCost, formatTokens } from "./session-usage"
17
17
  import { TaskCockpit } from "./task-cockpit"
18
+ import { TaskDetails } from "./task-details"
18
19
  import { TaskList } from "./task-list"
19
20
  import { taskSpan } from "./task-state"
20
21
  import { TaskTitle } from "./task-title"
@@ -382,3 +383,14 @@ describe("TaskCockpit panels", () => {
382
383
  expect(screen.getByText("session panel")).toBeTruthy()
383
384
  })
384
385
  })
386
+
387
+ describe("TaskDetails", () => {
388
+ it("copies the branch name from its icon", async () => {
389
+ const writeText = vi.fn().mockResolvedValue(undefined)
390
+ Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true })
391
+ render(<TaskDetails task={task()} />)
392
+ fireEvent.click(screen.getByRole("button", { name: "Copy branch name" }))
393
+ expect(writeText).toHaveBeenCalledWith("task/fix-login-a1b2c3")
394
+ await waitFor(() => expect(screen.getByTitle("Copied")).toBeTruthy())
395
+ })
396
+ })