Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

src/error-display.ts

58 lines · 1.7 KB · TypeScript
/**
 * Error display helpers for package apps.
 * Prefer a stable in-flow alert (or reserved slot) over injecting late chrome that shifts layout.
 * For ephemeral failures, prefer a toast overlay (zero CLS).
 */

export type ErrorDisplayInput = {
	title?: string
	message: string
	hint?: string
	/** Optional technical detail for tip / expandable */
	detail?: string
}

/** Normalize unknown thrown values into a short user-facing message. */
export function toErrorMessage(error: unknown): string {
	if (error instanceof Error && error.message) return error.message
	if (typeof error === 'string' && error.trim()) return error.trim()
	try {
		return JSON.stringify(error)
	} catch {
		return String(error)
	}
}

/**
 * HTML fragment for an inline error alert (role=alert).
 * Use when the error replaces expected content in a reserved region.
 */
export function errorBannerHtml(input: ErrorDisplayInput): string {
	const title = escapeHtml(input.title || 'Something went wrong')
	const message = escapeHtml(input.message || 'Unknown error')
	const hint = input.hint
		? `<p class="pak-muted pak-error-hint">${escapeHtml(input.hint)}</p>`
		: ''
	const tip = input.detail ? ` data-tip="${escapeAttr(input.detail)}"` : ''
	return `
<div class="pak-error" role="alert"${tip} tabindex="0">
  <strong class="pak-error-title">${title}</strong>
  <p class="pak-error-message">${message}</p>
  ${hint}
</div>
`.trim()
}

function escapeHtml(value: string) {
	return value
		.replaceAll('&', '&amp;')
		.replaceAll('<', '&lt;')
		.replaceAll('>', '&gt;')
		.replaceAll('"', '&quot;')
}

function escapeAttr(value: string) {
	return escapeHtml(value).replaceAll("'", '&#39;')
}

export default errorBannerHtml