+ {found && (
+ <>
{found.matches.length > 0 && (
From 57edae4e3f487d188983ba8cab224843dbc9e997 Mon Sep 17 00:00:00 2001
From: P SRIKAR NAIDU
Date: Fri, 30 Oct 2026 20:47:22 +0530
Subject: [PATCH 3/4] fix(color-converter): announce palette clipboard failures
(#431)
* fix(color-converter): announce palette clipboard failures
* test(color-converter): verify clipboard success and format values
---
.../ColorConverterClient.test.tsx | 36 ++++++++++++++++++-
.../color-converter/ColorConverterClient.tsx | 24 +++++++++----
2 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/app/(tools)/color-converter/ColorConverterClient.test.tsx b/app/(tools)/color-converter/ColorConverterClient.test.tsx
index 1f6e864..ac45828 100644
--- a/app/(tools)/color-converter/ColorConverterClient.test.tsx
+++ b/app/(tools)/color-converter/ColorConverterClient.test.tsx
@@ -2,11 +2,45 @@ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { ColorConverterClient } from "./ColorConverterClient";
import { contrastRatio, parseColor } from "@/lib/color-utils";
+import { useRegisterCommands } from "@/components/shared/CommandPalette";
+
+const { push } = vi.hoisted(() => ({ push: vi.fn() }));
vi.mock("@/components/shared/CommandPalette", () => ({ useRegisterCommands: vi.fn() }));
-vi.mock("@/components/shared/Toast", () => ({ useToast: () => ({ push: vi.fn() }) }));
+vi.mock("@/components/shared/Toast", () => ({ useToast: () => ({ push }) }));
afterEach(cleanup);
+describe("palette clipboard feedback", () => {
+ it.each([
+ ["hex", "#ff0000"],
+ ["rgb", "rgb(255, 0, 0)"],
+ ["hsl", "hsl(0, 100%, 50%)"],
+ ])("reports %s copy results with the correct value", async (format, expected) => {
+ push.mockClear();
+ const writeText = vi.fn().mockRejectedValue(new Error("Clipboard denied"));
+ const clipboard = Object.getOwnPropertyDescriptor(navigator, "clipboard");
+ Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText } });
+ try {
+ render();
+ fireEvent.change(screen.getByLabelText("Color input"), { target: { value: "#ff0000" } });
+ const commands = vi.mocked(useRegisterCommands).mock.calls.at(-1)![1];
+ const command = commands.find(({ id }) => id === `color:copy-${format}`)!;
+ await expect(command.run()).resolves.toBeUndefined();
+ expect(writeText).toHaveBeenCalledOnce();
+ expect(writeText).toHaveBeenCalledWith(expected);
+ expect(push).toHaveBeenCalledExactlyOnceWith("Could not copy — clipboard blocked", "error");
+ push.mockClear();
+ writeText.mockReset().mockResolvedValue(undefined);
+ await command.run();
+ expect(writeText).toHaveBeenCalledExactlyOnceWith(expected);
+ expect(push).toHaveBeenCalledExactlyOnceWith(`Copied ${format.toUpperCase()} to clipboard`, "success");
+ } finally {
+ if (clipboard) Object.defineProperty(navigator, "clipboard", clipboard);
+ else Reflect.deleteProperty(navigator, "clipboard");
+ }
+ });
+});
+
function failingPair() {
render();
const foreground = screen.getByLabelText("Text / Foreground");
diff --git a/app/(tools)/color-converter/ColorConverterClient.tsx b/app/(tools)/color-converter/ColorConverterClient.tsx
index 086db50..d2cca7d 100644
--- a/app/(tools)/color-converter/ColorConverterClient.tsx
+++ b/app/(tools)/color-converter/ColorConverterClient.tsx
@@ -162,8 +162,12 @@ export function ColorConverterClient() {
title: "Copy current color as HEX",
run: async () => {
if (hexValue) {
- await navigator.clipboard.writeText(hexValue);
- push("Copied HEX to clipboard", "success");
+ try {
+ await navigator.clipboard.writeText(hexValue);
+ push("Copied HEX to clipboard", "success");
+ } catch {
+ push("Could not copy — clipboard blocked", "error");
+ }
}
},
},
@@ -173,8 +177,12 @@ export function ColorConverterClient() {
title: "Copy current color as RGB",
run: async () => {
if (rgbValue) {
- await navigator.clipboard.writeText(rgbValue);
- push("Copied RGB to clipboard", "success");
+ try {
+ await navigator.clipboard.writeText(rgbValue);
+ push("Copied RGB to clipboard", "success");
+ } catch {
+ push("Could not copy — clipboard blocked", "error");
+ }
}
},
},
@@ -184,8 +192,12 @@ export function ColorConverterClient() {
title: "Copy current color as HSL",
run: async () => {
if (hslValue) {
- await navigator.clipboard.writeText(hslValue);
- push("Copied HSL to clipboard", "success");
+ try {
+ await navigator.clipboard.writeText(hslValue);
+ push("Copied HSL to clipboard", "success");
+ } catch {
+ push("Could not copy — clipboard blocked", "error");
+ }
}
},
},
From 0a62db0037b1b980fb9ceb1f6043275c27693c78 Mon Sep 17 00:00:00 2001
From: Anurag Verma <78868769+anurag629@users.noreply.github.com>
Date: Fri, 30 Oct 2026 20:56:44 +0530
Subject: [PATCH 4/4] chore: version 0.10.0 (#433)
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ce4b0e8..7c6309b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "toolbelt",
- "version": "0.9.0",
+ "version": "0.10.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "toolbelt",
- "version": "0.9.0",
+ "version": "0.10.0",
"license": "MIT",
"dependencies": {
"@cfworker/json-schema": "^4.1.1",
diff --git a/package.json b/package.json
index 6197a83..9ee8baf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "toolbelt",
- "version": "0.9.0",
+ "version": "0.10.0",
"private": true,
"description": "A developer's toolbelt: fast, privacy-first browser tools — JSON formatter, JWT decoder, Base64, URL encoder, invoice generator. By CODERCOPS.",
"license": "MIT",