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
}