feat(idaa): improve error handling for Novi verification failures and network errors

Distinguish transient API failures (rate limits, server errors, network drops) from
real membership denials, so members see actionable messages instead of 'Access Denied.'

Layout: new verify_error_type state ('rate_limited' | 'api_error') surfaces a
yellow 'Identity Verification Unavailable' banner with three recovery options --
Try Again (no reload, clears latch), Clear Cache and Reload, and Full Reset.
Spinner now shows live status messages (e.g. 'High traffic - retrying in 10 seconds...').

Recovery meetings page: qry_error_detail distinguishes network drops (TypeError /
ERR_NETWORK_CHANGED) from server errors, showing specific guidance in the error UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-19 10:46:28 -04:00
parent c0386f27bc
commit 128944c7ab
2 changed files with 117 additions and 4 deletions

View File

@@ -47,6 +47,9 @@ let last_executed_key = '';
// with a visible retry button — NOT the same "No meetings found" message a real empty
// result would show. (Conflating the two was a key reason the bug went unsolved so long.)
let auto_retry_count = 0;
// Tracks the category of the last search error for a more informative user message.
// ERR_NETWORK_CHANGED and other fetch failures surface as TypeError in JS.
let qry_error_detail: 'network' | 'server' | null = $state(null);
// ── Escape-hatch: cache-reset button after suspicious zero-result delay ──────────────
//
@@ -188,6 +191,7 @@ async function handle_search_refresh(qry_key: string) {
);
$idaa_sess.recovery_meetings.qry__status = 'loading';
qry_error_detail = null;
// If 'Remote First' is toggled (Admin only), we clear results immediately to show fresh state.
if (remote_first) {
@@ -388,7 +392,10 @@ async function handle_search_refresh(qry_key: string) {
}
} catch (error) {
if (current_search_id === last_search_id) {
console.error('Revalidation failed:', error);
// TypeError = network-level failure (ERR_NETWORK_CHANGED, offline, DNS failure).
// These are transient by nature; show a different message than a server error.
const is_network_err = error instanceof TypeError;
console.error('Revalidation failed:', is_network_err ? `Network error: ${error}` : error);
if (auto_retry_count < 1) {
// First failure — schedule one silent retry before surfacing the error.
@@ -406,6 +413,7 @@ async function handle_search_refresh(qry_key: string) {
// is not the same as a genuinely empty result. Conflating the two was why
// the "no meetings found" bug went undiagnosed for ~1 year — staff and users
// saw what looked like an empty list and assumed no data, not a failure.
qry_error_detail = is_network_err ? 'network' : 'server';
$idaa_sess.recovery_meetings.qry__status = 'error';
event_id_li = [];
}
@@ -474,7 +482,13 @@ if (browser) {
{:else if $idaa_sess.recovery_meetings.qry__status === 'error'}
<div
class="ae_highlight ae_padding_md ae_row ae_flex_justify_center flex-col gap-2 text-center">
<p>Unable to load meetings. Please try again.</p>
<p>
{#if qry_error_detail === 'network'}
Network connection interrupted — please check your connection and try again.
{:else}
Unable to load meetings — server error. Please try again.
{/if}
</p>
<button
type="button"
class="btn btn-sm preset-tonal-primary m-auto"