Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
import { failureMessage } from "$lib/api";
import { isAlertEnabled, type AlertKind } from "$lib/alert-prefs";
import { toast, type ToastAction } from "$lib/components/ui/toast-state";
/** Brief confirmation — readable but not sticky. */
const SUCCESS_DURATION_MS = 4000;
/** Errors need more time to read. */
const ERROR_DURATION_MS = 7000;
/** Link actions need dwell time to click. */
const ACTION_DURATION_MS = 10000;
type NotifyOptions = {
description?: string;
action?: ToastAction;
actions?: ToastAction[];
duration?: number;
/**
* Operator alert category (P0-9). When set, toast is gated by local alert prefs.
* Omit for always-on UI feedback (forms, undo, validation).
*/
alert?: AlertKind;
};
export type NotifyApiErrorOptions = NotifyOptions & {
/** When set, used as toast title; failure message becomes the description. */
title?: string;
};
function hasLinkActions(opts: NotifyOptions): boolean {
return Boolean(opts.action || opts.actions?.length);
}
function resolveDuration(opts: NotifyOptions, fallbackMs: number): number {
if (opts.duration !== undefined) return opts.duration;
if (hasLinkActions(opts)) return ACTION_DURATION_MS;
return fallbackMs;
}
function shouldShowToast(opts: NotifyOptions): boolean {
if (!opts.alert) return true;
// Never mute toasts that carry onClick actions (e.g. Undo cancel).
const actions = opts.actions?.length ? opts.actions : opts.action ? [opts.action] : [];
if (actions.some((a) => typeof a.onClick === "function")) return true;
return isAlertEnabled(opts.alert);
}
/** Show a toast (and keep Alert strings in sync when callers also set local state). */
export function notifySuccess(title: string, descriptionOrOpts?: string | NotifyOptions) {
const opts =
typeof descriptionOrOpts === "string"
? { description: descriptionOrOpts }
: (descriptionOrOpts ?? {});
if (!shouldShowToast(opts)) return;
toast({
title,
description: opts.description,
variant: "default",
action: opts.action,
actions: opts.actions,
duration: resolveDuration(opts, SUCCESS_DURATION_MS)
});
}
export function notifyError(title: string, descriptionOrOpts?: string | NotifyOptions) {
const opts =
typeof descriptionOrOpts === "string"
? { description: descriptionOrOpts }
: (descriptionOrOpts ?? {});
if (!shouldShowToast(opts)) return;
toast({
title,
description: opts.description,
variant: "destructive",
action: opts.action,
actions: opts.actions,
duration: resolveDuration(opts, ERROR_DURATION_MS)
});
}
/**
* Standard path for `api()` failures → toast (and optional page banner).
* Returns the message so callers can assign `error = notifyApiError(...)`.
* Message is always returned even when the toast is muted by alert prefs.
*/
export function notifyApiError(
err: unknown,
fallback: string,
opts?: NotifyApiErrorOptions
): string {
const message = failureMessage(err, fallback);
const { title, description, action, actions, duration, alert } = opts ?? {};
const toastOpts: NotifyOptions = { description, action, actions, duration, alert };
if (title !== undefined) {
notifyError(title, { ...toastOpts, description: description ?? message });
} else {
notifyError(message, toastOpts);
}
return message;
}