Skip to content

feat(ui): API key management and client info for processing services#1201

Open
mihow wants to merge 2 commits intomainfrom
feat/processing-service-auth-ui
Open

feat(ui): API key management and client info for processing services#1201
mihow wants to merge 2 commits intomainfrom
feat/processing-service-auth-ui

Conversation

@mihow
Copy link
Copy Markdown
Collaborator

@mihow mihow commented Apr 1, 2026

Summary

  • Adds "Generate API Key" button with copy/reveal UX in processing service details
  • Shows "Authentication" section with API key prefix and service mode (push/pull)
  • Shows "Last Known Worker" section with hostname, software, platform, and IP from client_info
  • Passes projectId to processing service details hook for project-scoped queries
  • Makes endpoint_url optional for pull-mode services that register themselves
  • Fixes async service status to show ONLINE when heartbeat is recent

Depends on #1194 (backend auth).

Permission and ownership notes for future UI work

The following considerations were surfaced during E2E testing and should inform the UI design:

API key scoping: The API key is tied to the ProcessingService (PS), not to a specific project. Since a PS has a M2M with projects, one API key grants access to all linked projects. Removing a PS from a project immediately denies access (checked live on each request).

Who can generate/revoke keys: Currently any project member who can list PSes can generate keys. This should probably be restricted to project admins/managers — key revocation affects all projects the PS serves.

User-created vs global PSes: Consider adding a created_by field to ProcessingService. Global PSes (admin-created) can be managed by admins; user-created PSes only by their creator + project admins. This prevents unauthorized key revocation.

Multi-project PS management: A user who creates a PS and belongs to multiple projects should be able to add the same PS to other projects they're a member of. The UI could show "My Processing Services" for self-service linking.

Org-level ownership: When the organization-level object is added above projects, PSes could also be org-owned, simplifying multi-project sharing within an organization.

Test plan

  • cd ui && yarn build
  • Verify processing service details dialog shows Authentication section
  • Generate API key and verify copy/reveal/regenerate flow
  • Verify Last Known Worker section appears after a worker heartbeat

Co-Authored-By: Claude noreply@anthropic.com

…ng services

- Add GenerateAPIKey component with copy/reveal/regenerate UX
- Add useGenerateAPIKey hook for POST /generate_key/ endpoint
- Show "Authentication" section in PS details (key prefix, mode)
- Show "Last Known Worker" section with client info (hostname,
  software, platform, IP)
- Add apiKeyPrefix and lastSeenClientInfo getters to PS model
- Pass projectId to useProcessingServiceDetails for scoped queries
- Make endpoint_url optional for pull-mode services
- Fix async service status: show ONLINE when lastSeenLive is true

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 1, 2026 19:06
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 1, 2026

Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit 179ba5a
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/69cd6e9023586f000748fbe3
😎 Deploy Preview https://deploy-preview-1201--antenna-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 63 (🔴 down 3 from production)
Accessibility: 89 (🟢 up 9 from production)
Best Practices: 100 (no change from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 1, 2026

Deploy Preview for antenna-ssec ready!

Name Link
🔨 Latest commit 179ba5a
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/69cd6e9078803d0008a296c1
😎 Deploy Preview https://deploy-preview-1201--antenna-ssec.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

This pull request introduces API key generation functionality for processing services. It adds a new React hook for API key mutations, extends the ProcessingService model with API key and worker information properties, updates the processing service details query to support project filtering, and adds UI components to display and manage generated API keys with show/hide and copy-to-clipboard features.

Changes

Cohort / File(s) Summary
API Key Generation Hook
ui/src/data-services/hooks/processing-services/useGenerateAPIKey.ts
New React hook using React Query mutation to generate API keys via authenticated POST request, with query invalidation on success and optional project ID query parameter support.
Processing Service Query Hook
ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts
Updated to accept optional projectId parameter and include it in both the query key and URL query string for project-scoped requests.
Data Model
ui/src/data-services/models/processing-service.ts
Added getters for apiKeyPrefix and lastSeenClientInfo to expose API key and client telemetry data; updated status getter logic to derive online status from lastSeenLive property.
Details Dialog & Actions
ui/src/pages/processing-service-details/processing-service-details-dialog.tsx, ui/src/pages/project/processing-services/processing-services-actions.tsx
Extended dialog to display authentication section with API key prefix and new GenerateAPIKey component; added optional "Last Known Worker" section showing client connection details. New GenerateAPIKey component with show/hide toggle, copy-to-clipboard, and generate/regenerate button with error handling.
Form Configuration
ui/src/pages/project/entities/details-form/processing-service-details-form.tsx
Removed required validation from endpoint URL field and updated description for pull-mode services that self-register.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A key is born, with prefix in hand,
To authenticate workers across the land!
We show, we hide, we copy with care,
Generating access with UI so fair—
The rabbit rejoices at features so grand! 🔑✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main features added: API key management and client information display for processing services.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description includes a clear summary, list of key changes, related dependency reference, testing instructions, and important design notes. However, it lacks several template sections like 'Detailed Description', 'Deployment Notes', and the complete checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/processing-service-auth-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds UI support for processing-service API key management and worker/client metadata display, aligning the frontend with the new processing-service authentication + heartbeat model introduced in the backend work.

Changes:

  • Adds “Generate/Regenerate API Key” UX (reveal + copy) and wires it to a new mutation hook.
  • Extends processing-service details to show an Authentication section (prefix + mode) and a Last Known Worker section from client_info.
  • Updates processing-service details fetching to support project-scoped queries and adjusts async status display based on heartbeat recency.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui/src/pages/project/processing-services/processing-services-actions.tsx Adds GenerateAPIKey component (copy/reveal UX) and hooks it into processing service actions.
ui/src/pages/project/entities/details-form/processing-service-details-form.tsx Makes endpoint_url optional in the form config with updated description.
ui/src/pages/processing-service-details/processing-service-details-dialog.tsx Adds new details sections (Authentication, Last Known Worker) and passes projectId into the details hook.
ui/src/data-services/models/processing-service.ts Adds getters for apiKeyPrefix and lastSeenClientInfo; updates async status derivation from heartbeat.
ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts Adds optional projectId parameter and includes it in query key and request URL.
ui/src/data-services/hooks/processing-services/useGenerateAPIKey.ts New mutation hook to call generate_key endpoint and invalidate processing service queries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +59
const handleCopy = async () => {
if (apiKey) {
await navigator.clipboard.writeText(apiKey)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleCopy awaits navigator.clipboard.writeText(apiKey) without any error handling. In non-secure contexts or when clipboard permissions are denied, this will throw and can result in an unhandled promise rejection (and the UI never reflects the failure). Wrap the clipboard call in try/catch (and/or guard on navigator.clipboard) and provide a fallback/error message; also consider clearing the 2s timeout on unmount to avoid setting state after the dialog closes.

Copilot uses AI. Check for mistakes.
queryKey: [API_ROUTES.PROCESSING_SERVICES, processingServiceId],
url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}`,
queryKey: [API_ROUTES.PROCESSING_SERVICES, processingServiceId, projectId],
url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${params}`,
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook hand-builds the details URL and always introduces a trailing slash when projectId is undefined (.../${processingServiceId}/). Elsewhere the UI uses getFetchDetailsUrl() which omits the trailing slash when there are no query params and adds /?... only when needed. Please switch to getFetchDetailsUrl({ collection, itemId, projectId }) (or mirror its behavior) to keep URL formatting consistent and avoid possible redirect/404 differences between /id vs /id/.

Suggested change
url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${params}`,
url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}${params}`,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
ui/src/pages/project/processing-services/processing-services-actions.tsx (1)

62-90: Timer not cleaned up on unmount.

The setTimeout in handleCopy could fire after the component unmounts if the user navigates away quickly, causing a state update on an unmounted component.

Consider using useEffect cleanup or a ref
+import { useEffect, useRef, useState } from 'react'
 
 export const GenerateAPIKey = ({
   processingService,
 }: {
   processingService: ProcessingService
 }) => {
   const { projectId } = useParams()
   const { generateAPIKey, isLoading, error, apiKey } = useGenerateAPIKey(projectId)
   const [copied, setCopied] = useState(false)
   const [visible, setVisible] = useState(false)
+  const timerRef = useRef<ReturnType<typeof setTimeout>>()
+
+  useEffect(() => {
+    return () => {
+      if (timerRef.current) clearTimeout(timerRef.current)
+    }
+  }, [])

   const handleCopy = async () => {
     if (apiKey) {
       await navigator.clipboard.writeText(apiKey)
       setCopied(true)
-      setTimeout(() => setCopied(false), 2000)
+      timerRef.current = setTimeout(() => setCopied(false), 2000)
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/project/processing-services/processing-services-actions.tsx`
around lines 62 - 90, The timeout created in handleCopy can update state after
unmount; to fix, store the timeout id in a ref (e.g., copyTimeoutRef) when
calling setTimeout inside handleCopy and clearTimeout(copyTimeoutRef.current) in
a useEffect cleanup (returning a function that clears it) or clear it in a
componentWillUnmount equivalent; update the ProcessingServicesActions component
to use that ref and cleanup to prevent setCopied state updates after unmount.
ui/src/data-services/models/processing-service.ts (1)

72-74: Redundant nullish coalescing with undefined.

The ?? undefined is a no-op since the nullish coalescing operator already returns undefined when the left side is null or undefined.

Suggested simplification
   get apiKeyPrefix(): string | undefined {
-    return this._processingService.api_key_prefix ?? undefined
+    return this._processingService.api_key_prefix
   }

Same applies to lastSeenClientInfo on line 86.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/data-services/models/processing-service.ts` around lines 72 - 74,
Remove the redundant "?? undefined" nullish coalescing in the getter so it
simply returns the underlying value; specifically update the apiKeyPrefix getter
to return this._processingService.api_key_prefix directly (and apply the same
simplification to the lastSeenClientInfo accessor that currently uses "??
undefined"), ensuring the property types remain string | undefined.
ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts (1)

21-25: URL has extraneous slash before query parameters.

When projectId is provided, the URL becomes .../processing-services/${id}/?project_id=... with a trailing slash before the query string. While this typically works, it's inconsistent with standard URL conventions.

Suggested fix
-  const params = projectId ? `?project_id=${projectId}` : ''
+  const params = projectId ? `?project_id=${projectId}` : ''
   const { data, isLoading, isFetching, error } =
     useAuthorizedQuery<ProcessingService>({
       queryKey: [API_ROUTES.PROCESSING_SERVICES, processingServiceId, projectId],
-      url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${params}`,
+      url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${params ? params : ''}`,

Or more cleanly:

-  const params = projectId ? `?project_id=${projectId}` : ''
-  const { data, isLoading, isFetching, error } =
-    useAuthorizedQuery<ProcessingService>({
-      queryKey: [API_ROUTES.PROCESSING_SERVICES, processingServiceId, projectId],
-      url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${params}`,
+  const { data, isLoading, isFetching, error } =
+    useAuthorizedQuery<ProcessingService>({
+      queryKey: [API_ROUTES.PROCESSING_SERVICES, processingServiceId, projectId],
+      url: `${API_URL}/${API_ROUTES.PROCESSING_SERVICES}/${processingServiceId}/${projectId ? `?project_id=${projectId}` : ''}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts`
around lines 21 - 25, The URL construction in useProcessingServiceDetails is
adding an extra slash before query params (params currently set to include '?'
then appended after a trailing slash), so change how params are built and
appended: keep params as just the query fragment (e.g., project_id=...), or keep
the '?' in params but remove the hard-coded trailing slash before ${params};
update the URL assembly used in useAuthorizedQuery (the line with
API_URL/API_ROUTES/processingServiceId) so it conditionally appends the query
string without introducing '/?' — reference variables/functions: params,
processingServiceId, API_URL, API_ROUTES, and useProcessingServiceDetails.
ui/src/pages/processing-service-details/processing-service-details-dialog.tsx (2)

97-109: Hardcoded strings bypass i18n system.

The new sections use hardcoded English strings ("Authentication", "API Key Prefix", "Mode", "Pull (async)", "Push (sync)", etc.) while other fields use translate(STRING.*). This creates an i18n inconsistency.

Consider adding these strings to the translation system for consistency, or leave a TODO comment if i18n support for these fields is planned for later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/src/pages/processing-service-details/processing-service-details-dialog.tsx`
around lines 97 - 109, Replace the hardcoded English strings in the
Authentication block with i18n lookups: wrap the FormSection title and each
InputValue label and the mode display text in translate(STRING.*) calls (e.g.,
replace "Authentication", "API Key Prefix", "Mode", "Pull (async)", "Push
(sync)" with translate(STRING.YOUR_KEY_*)), add corresponding keys to the STRING
enum/translation resource and localized JSON files, and update the mode
rendering to choose the translated string based on processingService.isAsync; if
translation entries will be added later, add a clear TODO comment near
FormSection/InputValue/GenerateAPIKey referencing the need to replace these
hardcoded strings with translate(STRING.*).

119-127: Complex ternary for software display.

The nested conditional is hard to parse at a glance.

Consider extracting to a helper function for readability
const getSoftwareDisplay = (clientInfo: typeof processingService.lastSeenClientInfo) => {
  if (!clientInfo) return undefined
  const { software, version } = clientInfo
  if (software && version) return `${software} ${version}`
  if (software) return software
  if (version) return `v${version}`
  return undefined
}

Then use:

<InputValue
  label="Software"
  value={getSoftwareDisplay(processingService.lastSeenClientInfo)}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ui/src/pages/processing-service-details/processing-service-details-dialog.tsx`
around lines 119 - 127, Extract the complex nested ternary that builds the
software string into a small helper (e.g., getSoftwareDisplay) and call it from
the InputValue; specifically, implement getSoftwareDisplay(clientInfo: typeof
processingService.lastSeenClientInfo) that returns `${software} ${version}` when
both exist, software when only software exists, `v${version}` when only version
exists, and undefined otherwise, then replace the inline ternary in the value
prop of InputValue with getSoftwareDisplay(processingService.lastSeenClientInfo)
to improve readability and maintain behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/src/pages/project/processing-services/processing-services-actions.tsx`:
- Around line 54-60: The handleCopy function currently calls
navigator.clipboard.writeText(apiKey) without handling rejections; wrap that
call in try/catch (or attach .catch) inside handleCopy to handle failures, log
or surface the error via an existing logger or UI (e.g., set an error state or
show a toast), and only setCopied(true) when the write succeeds; also keep the
existing timeout to clear the copied flag. Target the handleCopy function and
update its clipboard write invocation accordingly.

---

Nitpick comments:
In
`@ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts`:
- Around line 21-25: The URL construction in useProcessingServiceDetails is
adding an extra slash before query params (params currently set to include '?'
then appended after a trailing slash), so change how params are built and
appended: keep params as just the query fragment (e.g., project_id=...), or keep
the '?' in params but remove the hard-coded trailing slash before ${params};
update the URL assembly used in useAuthorizedQuery (the line with
API_URL/API_ROUTES/processingServiceId) so it conditionally appends the query
string without introducing '/?' — reference variables/functions: params,
processingServiceId, API_URL, API_ROUTES, and useProcessingServiceDetails.

In `@ui/src/data-services/models/processing-service.ts`:
- Around line 72-74: Remove the redundant "?? undefined" nullish coalescing in
the getter so it simply returns the underlying value; specifically update the
apiKeyPrefix getter to return this._processingService.api_key_prefix directly
(and apply the same simplification to the lastSeenClientInfo accessor that
currently uses "?? undefined"), ensuring the property types remain string |
undefined.

In
`@ui/src/pages/processing-service-details/processing-service-details-dialog.tsx`:
- Around line 97-109: Replace the hardcoded English strings in the
Authentication block with i18n lookups: wrap the FormSection title and each
InputValue label and the mode display text in translate(STRING.*) calls (e.g.,
replace "Authentication", "API Key Prefix", "Mode", "Pull (async)", "Push
(sync)" with translate(STRING.YOUR_KEY_*)), add corresponding keys to the STRING
enum/translation resource and localized JSON files, and update the mode
rendering to choose the translated string based on processingService.isAsync; if
translation entries will be added later, add a clear TODO comment near
FormSection/InputValue/GenerateAPIKey referencing the need to replace these
hardcoded strings with translate(STRING.*).
- Around line 119-127: Extract the complex nested ternary that builds the
software string into a small helper (e.g., getSoftwareDisplay) and call it from
the InputValue; specifically, implement getSoftwareDisplay(clientInfo: typeof
processingService.lastSeenClientInfo) that returns `${software} ${version}` when
both exist, software when only software exists, `v${version}` when only version
exists, and undefined otherwise, then replace the inline ternary in the value
prop of InputValue with getSoftwareDisplay(processingService.lastSeenClientInfo)
to improve readability and maintain behavior.

In `@ui/src/pages/project/processing-services/processing-services-actions.tsx`:
- Around line 62-90: The timeout created in handleCopy can update state after
unmount; to fix, store the timeout id in a ref (e.g., copyTimeoutRef) when
calling setTimeout inside handleCopy and clearTimeout(copyTimeoutRef.current) in
a useEffect cleanup (returning a function that clears it) or clear it in a
componentWillUnmount equivalent; update the ProcessingServicesActions component
to use that ref and cleanup to prevent setCopied state updates after unmount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfa163b8-1f69-4dbc-bc19-b99ac68ddd46

📥 Commits

Reviewing files that changed from the base of the PR and between ed8f857 and 22326f4.

📒 Files selected for processing (6)
  • ui/src/data-services/hooks/processing-services/useGenerateAPIKey.ts
  • ui/src/data-services/hooks/processing-services/useProcessingServiceDetails.ts
  • ui/src/data-services/models/processing-service.ts
  • ui/src/pages/processing-service-details/processing-service-details-dialog.tsx
  • ui/src/pages/project/entities/details-form/processing-service-details-form.tsx
  • ui/src/pages/project/processing-services/processing-services-actions.tsx

Comment on lines +54 to +60
const handleCopy = async () => {
if (apiKey) {
await navigator.clipboard.writeText(apiKey)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing error handling for clipboard API.

navigator.clipboard.writeText() can fail (e.g., in insecure contexts, or if permission is denied). The rejected promise is unhandled.

Suggested fix with error handling
   const handleCopy = async () => {
     if (apiKey) {
-      await navigator.clipboard.writeText(apiKey)
-      setCopied(true)
-      setTimeout(() => setCopied(false), 2000)
+      try {
+        await navigator.clipboard.writeText(apiKey)
+        setCopied(true)
+        setTimeout(() => setCopied(false), 2000)
+      } catch {
+        // Optionally show an error state or fallback
+        console.error('Failed to copy to clipboard')
+      }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = async () => {
if (apiKey) {
await navigator.clipboard.writeText(apiKey)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
const handleCopy = async () => {
if (apiKey) {
try {
await navigator.clipboard.writeText(apiKey)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
// Optionally show an error state or fallback
console.error('Failed to copy to clipboard')
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/pages/project/processing-services/processing-services-actions.tsx`
around lines 54 - 60, The handleCopy function currently calls
navigator.clipboard.writeText(apiKey) without handling rejections; wrap that
call in try/catch (or attach .catch) inside handleCopy to handle failures, log
or surface the error via an existing logger or UI (e.g., set an error state or
show a toast), and only setCopied(true) when the write succeeds; also keep the
existing timeout to clear the copied flag. Target the handleCopy function and
update its clipboard write invocation accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants