# ADR 0005 — DX moat: devtools, testing, error DX (v0.9)

- **Status:** Accepted
- **Date:** 2026-07-02
- **Context:** the v0.9 developer-experience milestone. Builds on the reactive core and
  the store from [ADR 0002](./0002-async-lifecycle-hardening.md)/[ADR 0003](./0003-data-story.md).

## Decisions

### 1. A single tree-shakeable instrumentation seam

Atom writes (and, via `inspectStore()`, the query store) emit through one module-level
subscriber set (`devtools-core`). While nothing is connected, the core skips building events
(one `devConnected()` check per write), so production pays nothing; the heavy consumers — the
Redux DevTools bridge and the `<Inspector>` — live in separate modules that tree-shake away
unless imported. Two consumers on one seam avoids a bespoke hook per tool.

### 2. Redux DevTools bridge first, own panel alongside

`connectReduxDevtools()` speaks the Redux DevTools **browser extension** protocol
(`connect/init/send`) — the de-facto state inspector the ecosystem (Zustand, Jotai, …) bridges to
— with **zero dependency on Redux the library**, and no-ops when the extension is absent. effract's
own `<Inspector>` (a live atom + query-store panel, no extension needed) ships alongside for the
custom-panel path (#54, #55). Atoms take an optional `name` for readable labels.

### 3. `@tmonier/effract-test` — renderRec + atom helpers

A new package wraps the `act`/Suspense/timing ceremony effract's own tests hand-rolled into
`renderRec(node)`, plus `readAtom`/`setAtom`/`collectAtom` for driving state without a component,
and documents Effect `TestClock` for deterministic virtual-time tests. effract dogfoods it
(`dogfood.test.tsx`) (#56).

### 4. `.catch` fallbacks get a `retry`

Every `.catch` handler now receives `(error, { retry })`. `retry` re-attempts the body — the
error boundary re-mounts the subtree (fresh queries), the inline fast-path re-renders — so a
fallback can offer a retry button beyond the automatic atom-watch reset. Backward compatible
(handlers that ignore the second argument are unchanged). "Defects are never swallowed" is kept:
effract does not add a default boundary that renders defects; an opt-in defect handler and richer
dev formatting remain a future step (#57, partial).

## Consequences

- Dev tooling is opt-in and free in production.
- The library's own tests can shrink onto `renderRec`; consumers get the same helper.
- `.catch` is now `(error, api)`; the type and both drivers (client boundary + inline, server)
  pass the API.

## Alternatives considered

- **A bespoke devtools protocol.** Rejected for step 1: bridging the ubiquitous Redux DevTools
  extension is higher-leverage than a custom wire format.
- **A default defect-rendering boundary.** Rejected: it would blur "defects are never swallowed".
  The retry affordance and an explicit inspector are the safe wins.
