Andrey Antukh 34b24a9d9d Retry transient saves with backoff and reconnect notice
Classify save failures as transient or terminal (`transient-error?`
over the repo retryable types plus `:invalid-save-response`).
Transient failures keep the head commit queued under a new `:retrying`
status and resend it with backoff (2s/8s/20s, then terminal):
stamp rotation reuses the same `:commit-id`, the in-flight guard
prevents double-sends, and episode tokens silence stale timers.
One tagged reconnect notice per episode (hidden on save and on
terminal failure, silent recovery) plus a `:retrying` save-indicator
state; the browser `online` event and new edits resume the episode.
Terminal failures keep the exact `:error` path. Covers tasks 4, 6
and 7 with 31 persistence tests; updates the persistence memory.

Relates to #11724

AI-assisted-by: muse-spark-1.3-contributor
2026-09-23 12:05:24 +02:00

1.1 KiB

Clojure Idioms (verified)

Behaviors confirmed against the language/stdlib — do not re-derive from assumption; a wrong assumption here already cost a review round.

  • int? is NOT 32-bit-only: true for Long, Integer, Short, Byte (fixed-precision integers). Clojure integer literals are Long, so (int? 5000) is true.
  • integer? is the general integer predicate; prefer it when any integer kind must match, int? only when fixed precision is meant.
  • await is a cljs.core macro asserting (:async &env): it fails at compile time outside an ^:async context, never silently.
  • The analyzer reads :async only from the fn name meta and the fn operator meta; list-level meta is ignored (would make a MetaFn). (fn ^:async [] …) puts the meta on argv (pre/post only, NOT async).
  • Valid: (defn ^:async f), (defn- ^:async f), (^:async fn [] …) (the latter is what stock t/async generates itself).
  • ^:async fns never throw synchronously (rejected promises instead); try/catch/finally supported; continuations are microtasks, timers and RxJS schedulers are macrotasks (FIFO).