diff --git a/packages/client/src/feedback-controller.ts b/packages/client/src/feedback-controller.ts index 889787d..b27e0d8 100644 --- a/packages/client/src/feedback-controller.ts +++ b/packages/client/src/feedback-controller.ts @@ -489,33 +489,63 @@ export function createHeadlessFeedbackController( ) if (state.disposed) return state.submitState - const adapterFailures = adapterResults.flatMap((entry) => { - if (entry.status === 'rejected') return ['adapter'] - return entry.value.result.ok ? [] : [entry.value.name] - }) + const adapterSuccesses: string[] = [] + const adapterFailures: string[] = [] + let hasDeliveryUrl = false + for (const entry of adapterResults) { + if (entry.status === 'rejected') { + adapterFailures.push('adapter') + } else if (entry.value.result.ok) { + const { deliveryId: id, deliveryUrl: url } = entry.value.result + if (url) { + adapterSuccesses.push(`${entry.value.name} #${id ?? ''}`) + hasDeliveryUrl = true + } else { + adapterSuccesses.push(id ? `${entry.value.name} #${id}` : entry.value.name) + } + } else { + adapterFailures.push(entry.value.name) + } + } + + const hasAdapterSuccess = adapterSuccesses.length > 0 + const hasAdapterFailure = adapterFailures.length > 0 - if (!flushOk || adapterFailures.length > 0) { + if (hasAdapterSuccess && !hasAdapterFailure) { + state.submitState = { + kind: 'complete', + tone: 'success', + message: `Feedback delivered via ${adapterSuccesses.join(', ')}.`, + html: hasDeliveryUrl, + } + } else if (hasAdapterSuccess && hasAdapterFailure) { state.submitState = { kind: 'complete', tone: 'warning', - message: [ - flushOk - ? 'Feedback saved and sent from this page.' - : 'Feedback saved locally. Server delivery will retry automatically.', - adapterFailures.length > 0 - ? `Adapter delivery failed: ${adapterFailures.join(', ')}.` - : '', - ] - .filter(Boolean) - .join(' '), + message: `Delivered via ${adapterSuccesses.join(', ')}. Failed: ${adapterFailures.join(', ')}.`, + html: hasDeliveryUrl, } - } else { + } else if (!hasAdapterSuccess && config.adapters.length > 0) { + state.submitState = { + kind: 'complete', + tone: 'warning', + message: flushOk + ? 'Feedback saved to server. Adapter delivery failed.' + : 'Feedback saved locally. Delivery will retry automatically.', + } + } else if (flushOk) { state.submitState = { kind: 'complete', tone: 'success', message: state.includeScreenshot - ? 'Feedback sent with the current screenshot attached.' - : 'Feedback sent without a screenshot.', + ? 'Feedback sent with screenshot attached.' + : 'Feedback sent.', + } + } else { + state.submitState = { + kind: 'complete', + tone: 'warning', + message: 'Feedback saved locally. Server delivery will retry automatically.', } } diff --git a/packages/client/src/feedback.ts b/packages/client/src/feedback.ts index 54a9341..cc0e71f 100644 --- a/packages/client/src/feedback.ts +++ b/packages/client/src/feedback.ts @@ -344,8 +344,12 @@ export function showFeedbackDialog( return parts.length > 0 ? `Details · ${parts.join(' · ')}` : 'Details' } - const updateStatus = (message: string, tone?: FeedbackStatusTone) => { - status.textContent = message + const updateStatus = (message: string, tone?: FeedbackStatusTone, html = false) => { + if (html) { + status.innerHTML = message + } else { + status.textContent = message + } status.style.padding = message ? '8px 10px' : '0' status.style.borderRadius = theme.panelRadius if (tone) { @@ -426,7 +430,7 @@ export function showFeedbackDialog( sendButton.textContent = 'Close' setButtonEnabled(sendButton, true) cancelButton.style.display = 'none' - updateStatus(completion.message, completion.tone) + updateStatus(completion.message, completion.tone, completion.html) schedulePosition() return } diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 625e502..d370596 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -35,7 +35,7 @@ export type FeedbackScreenshotState = 'pending' | 'ready' | 'unavailable' export type FeedbackSubmitState = | { kind: 'idle' } | { kind: 'submitting' } - | { kind: 'complete'; tone: FeedbackStatusTone; message: string } + | { kind: 'complete'; tone: FeedbackStatusTone; message: string; html?: boolean } export interface FeedbackTrigger { element: Element @@ -103,6 +103,8 @@ export interface AdapterResult { error?: string /** Adapter-specific delivery ID (e.g. issue number, message ID). */ deliveryId?: string + /** URL to view the delivered feedback (e.g. GitHub issue link). */ + deliveryUrl?: string } /** @@ -111,6 +113,8 @@ export interface AdapterResult { */ export interface FeedbackAdapter { name: string + /** Human-friendly label shown in the UI (e.g. "GitHub"). Defaults to name. */ + displayName?: string send(event: TelemetryEvent): Promise }