From 4cf73428f87ae7a2eeab628b3642a7280730742d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:14:02 -0300 Subject: [PATCH] fix(dashboard): migrate ManualConfigModal copy to shared useCopyToClipboard hook (#4502) Rebuilt onto release/v3.8.33 (squash-base-stale). Integrated into release/v3.8.33. --- CHANGELOG.md | 1 + src/shared/components/ManualConfigModal.tsx | 34 ++++------- .../manual-config-modal-clipboard.test.ts | 59 +++++++++++++++++++ 3 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 tests/unit/manual-config-modal-clipboard.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d591a41a5a..fc9153b6ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ _In development — bullets added per PR; finalized at release._ ### 🐛 Fixed - **fix(sse): combo routing now skips a provider whose credentials are all disabled instead of failing the whole request** — when a combo like `antigravity/opus → github/opus` hit a leg whose only configured connections were disabled (or where no connections existed at all), `handleNoCredentials` returned `400 BAD_REQUEST`, which the combo target loop treats as a hard stop (combo's 400-break guard from PR #4316 / issue #4279 prevents infinite fallback loops on body-specific 4xx errors). The combo therefore died on the first leg even when later targets were perfectly healthy. The no-active-credentials branch now returns `404 NOT_FOUND` with `"No active credentials for provider:
"` instead — `404` flows through `checkFallbackError` as `shouldFallback: true` (generic-error catch-all path in `open-sse/services/accountFallback.ts`), so the next combo target is tried. The log level for this branch also drops from `error` to `warn` because zero active credentials is an expected operator-driven state, not a server fault. Inspired-by upstream decolua/9router PR #336. (thanks @East-rayyy) +- **fix(dashboard): Manual Config modal "Copy" button now works on HTTP / non-secure deployments** — the copy handler in `ManualConfigModal` re-implemented the Clipboard-API-with-`execCommand`-fallback inline and gated the modern path on `window.isSecureContext`, so some non-secure-context browsers (and any future drift) silently lost the fallback. Migrated to the shared `useCopyToClipboard` hook (which delegates to `src/shared/utils/clipboard.ts`), giving consistent HTTP/HTTPS behavior with the rest of the dashboard and removing the duplicated code path. (thanks @anuragg-saxenaa) - **fix(embeddings):** forward output dimensions to Gemini for consistent embedding dims. (thanks @nguyenha935) - **fix(translator):** sanitize Read tool args from non-Anthropic models to prevent retry loops. (thanks @GodrezJr2) - **fix(usage):** reuse Gemini CLI project ID for quota checks (avoid re-discovery). (thanks @Delcado19) diff --git a/src/shared/components/ManualConfigModal.tsx b/src/shared/components/ManualConfigModal.tsx index a6ebea8508..5b577eb786 100644 --- a/src/shared/components/ManualConfigModal.tsx +++ b/src/shared/components/ManualConfigModal.tsx @@ -4,34 +4,22 @@ import { useState } from "react"; import { useTranslations } from "next-intl"; import Modal from "./Modal"; import Button from "./Button"; +import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; export default function ManualConfigModal({ isOpen, onClose, title, configs = [] }) { const t = useTranslations("common"); const resolvedTitle = title ?? t("manualConfig"); const [copiedIndex, setCopiedIndex] = useState(null); + const { copy } = useCopyToClipboard(); - const copyToClipboard = async (text, index) => { - try { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(text); - } else { - const textarea = document.createElement("textarea"); - textarea.value = text; - textarea.style.position = "fixed"; - textarea.style.left = "-9999px"; - textarea.style.top = "-9999px"; - textarea.style.opacity = "0"; - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); - document.execCommand("copy"); - document.body.removeChild(textarea); - } - setCopiedIndex(index); - setTimeout(() => setCopiedIndex(null), 2000); - } catch (err) { - console.log("Failed to copy:", err); - } + // Delegates to the shared useCopyToClipboard hook, which transparently + // falls back to a hidden textarea + legacy copy command when the + // Clipboard API is unavailable (HTTP / non-secure contexts, iframes). + const copyConfig = async (text, index) => { + const ok = await copy(text, `manualconfig-${index}`); + if (!ok) return; + setCopiedIndex(index); + setTimeout(() => setCopiedIndex(null), 2000); }; return ( @@ -44,7 +32,7 @@ export default function ManualConfigModal({ isOpen, onClose, title, configs = []