Files
profilarr/docs/frontend/alerts.md

3.5 KiB

Alerts

Source: src/lib/client/alerts/ ($alerts/*) Mount: src/routes/+layout.svelte:62

Table of Contents

Alerts are the app's toast notification system: transient messages that appear at a configured screen edge, auto-dismiss after a timeout, and can be clicked to dismiss early. They are the channel for ephemeral feedback ("Settings saved", "Failed to unlink database"). For confirmations, use $ui/modal/Modal.svelte; for field-level errors, render next to the field.

Store

store.ts exposes a Svelte store with four methods: add, remove, clear, subscribe. Severity is one of 'success' | 'error' | 'warning' | 'info'.

alertStore.add('success', 'Settings saved successfully!');
alertStore.add('error', 'Failed to save');
alertStore.add('error', 'Tag already added.', 2000); // override duration
alertStore.add('warning', 'Parser service unavailable.', 0); // persistent

add returns the new alert id. If duration is omitted, the default comes from alertSettingsStore.durationMs. duration === 0 disables auto-dismiss: the alert stays until the user clicks it. Use it sparingly, for warnings the user must acknowledge.

The store does not dedupe or cap queue length. Callers that fire repeatedly must throttle themselves (see TagInput.svelte for the reference pattern).

Settings

settings.ts stores position (one of six edges / corners) and durationMs in localStorage under the key alertSettings. Defaults are top-center and 8000. Settings are client-only: SSR always starts from the defaults and the client picks up the stored preference on hydration.

The settings UI in src/routes/settings/general/+page.svelte presents duration in seconds; the store holds milliseconds. Conversion happens at the boundary (* 1000 on save, / 1000 on read).

Consuming alerts

Most alerts come from two places: direct calls in event handlers, and the use:enhance callback on form submissions.

The form-action pattern is the convention worth knowing. Server actions return failures via fail(status, { error: '...' }), and use:enhance translates the result into an alert:

<!-- src/routes/settings/general/+page.svelte:239-258 -->
<form
    method="POST"
    action="?/save"
    use:enhance={() => {
        saving = true;
        return async ({ result, update: formUpdate }) => {
            if (result.type === 'failure' && result.data) {
                alertStore.add('error', (result.data as { error?: string }).error || 'Failed to save');
            } else if (result.type === 'success') {
                alertStore.add('success', 'Settings saved successfully!');
            }
            saving = false;
            await formUpdate({ reset: false });
        };
    }}
>

The shape is consistent across the codebase: the error string comes from the server (result.data.error with a hardcoded fallback), the success string is hardcoded on the client. Pages with several actions usually extract a factory (see src/routes/databases/[id]/conflicts/+page.svelte:47 for an example) to avoid repeating the branch.

Not for load errors

Errors thrown in +page.server.ts or +layout.server.ts load functions are routed through SvelteKit's +error.svelte, which renders a full-page error view. Alerts are for recoverable failures the user can retry; thrown errors are for route-breaking failures where no useful page can render.