Troubleshooting
Start by identifying the failing layer and the last successful step. When DevTools is available, inspect the prepaint, model, or tx category together with the transaction or model identifier.
Prepaint does not replay
| Symptom | Likely cause | Next action |
|---|---|---|
| Every visit is a cold start | No stored snapshot or a different route key | Revisit the same route and inspect the restore event strategy |
| Snapshot is discarded immediately | Missing fields, corruption, or policy mismatch | Inspect storage.error and cold-start restore, then create a new snapshot |
| Stale sensitive content is visible | Missing sensitive or volatile marker | Mark the element for scrubbing or volatility and refresh the stored snapshot |
| UI jumps after handoff | Large difference between snapshot and current React UI | Measure replay and handoff timing and reduce volatile regions |
The Prepaint overlay is non-interactive. Clicks being unavailable during replay is expected behavior.
Local-First data is not ready
useModelsynchronously returns the in-memory external-store snapshot, while IndexedDB loading is asynchronous. Handle the initialloadingstate.- When stored data fails the Zod schema, a
validation.errorevent is emitted and the stored value may be removed. useSuspenseSyncedModelthrows the Promise returned bygetSyncPromise()when both data and error are absent. Provide both an ErrorBoundary and a Suspense fallback.- When
broadcast.fallbackorbroadcast.skippedappears, do not assume live cross-tab updates. Provide refresh or manual sync.
StorageError.code uses the STORAGE_* namespace. Read the raw classification from storageCode, operation details from storageContext, and recovery status from isRecoverable().
Tx ends in rollback or timeout
- The failing step's
compensatefunction is not called. Only completed steps are compensated in reverse order. - If a step ignores
AbortSignal, the underlying work can continue after timeout. Pass the signal to cancellable APIs. RetryExhaustedErrormeans all configured attempts were used. Check server state and idempotency before repeating the request.CompensationFailedError.failurescontains compensation errors only. UsecompletedStepswith the DevTools timeline to decide the manual recovery scope.
Tx compensates completed client work in reverse order, but it does not make every external system atomic. Pair irreversible operations such as payment or inventory changes with server transactions or idempotency contracts.
DevTools fields are missing or different
Some runtime payload fields differ from the bridge types. For example, a Tx start event may use timeout while the bridge type uses timeoutMs, or hasCompensate while the type uses hasCompensation.
- Treat runtime package emitters as the observable contract.
- Prefer short diagnostic sessions because long sessions and large payload searches increase memory and serialization cost.
- Interpret
step.success.attemptwith the transaction timeline because it can reflect configuration rather than the actual attempt count.
Recovery by error type
| Error | Default classification | Response |
|---|---|---|
StorageError | Follow isRecoverable() | Show the user message and offer retry or refresh |
ValidationError | Recoverable | Check stored schema and version, then sync valid data |
TransactionTimeoutError | Recoverable | Check signal propagation and timeout budget |
RetryExhaustedError | Recoverable | Check server state and retry policy before explicit retry |
CompensationFailedError | Non-recoverable | Use failures, completedSteps, and timeline for manual recovery |
TransactionStateError | Non-recoverable | Remove duplicate calls against an already completed transaction |
See Reference for exact properties and exports, and DevTools for observation workflows.