* ♻️ Handle fetch-error gracefully with toast instead of full-page error
Network-level failures (lost connectivity, DNS failure, etc.) on RPC
calls were propagating as :internal/:fetch-error to the global error
handler, which replaced the entire UI with a full-page error screen.
Now the :internal handler distinguishes :fetch-error from other internal
errors and shows a non-intrusive toast notification instead, allowing
the user to continue working.
* ✨ Add automatic retry with backoff for idempotent RPC requests
Idempotent (GET) RPC requests are now automatically retried up to 3
times with exponential back-off (1s, 2s, 4s) when a transient error
occurs. Retryable errors include: network-level failures
(:fetch-error), 502 Bad Gateway, 503 Service Unavailable, and browser
offline (status 0).
Mutation (POST) requests are never retried to avoid unintended
side-effects. Non-transient errors (4xx client errors, auth errors,
validation errors) propagate immediately without retry.
* ♻️ Make retry helpers public with configurable parameters
Make retryable-error? and with-retry public functions, and replace
private constants with a default-retry-config map. with-retry now
accepts an optional config map (:max-retries, :base-delay-ms) enabling
callers and tests to customize retry behavior.
* ✨ Add tests for RPC retry mechanism
Comprehensive tests for the retry helpers in app.main.repo:
- retryable-error? predicate: covers all retryable types (fetch-error,
bad-gateway, service-unavailable, offline) and non-retryable types
(validation, authentication, authorization, plain errors)
- with-retry observable wrapper: verifies immediate success, recovery
after transient failures, max-retries exhaustion, no retry for
non-retryable errors, fetch-error retry, custom config, and mixed
error scenarios
* ♻️ Introduce :network error type for fetch-level failures
Replace the awkward {:type :internal :code :fetch-error} combination
with a proper {:type :network} type in app.util.http/fetch. This makes
the error taxonomy self-explanatory and removes the special-case branch
in the :internal handler.
Consequences:
- http.cljs: emit {:type :network} instead of {:type :internal :code :fetch-error}
- errors.cljs: add a dedicated ptk/handle-error :network method (toast);
restore :internal handler to its original unconditional full-page error form
- repo.cljs: simplify retryable-types and retryable-error? — :network
replaces the former :internal special-case, no code check needed
- repo_test.cljs: update tests to use {:type :network}
* 📚 Add comment explaining the use of bit-shift-left