This tool works with both Expo and React Native CLI projects. Just install and go.
Testing a checkout flow means building the same state over and over: clear the cart, add ten items, navigate to checkout, test, repeat. Every iteration pays the setup cost again. Reproduced a bug that only happens with specific state? One wrong tap and you're rebuilding it from scratch.
Time Machine is macOS-style restore points for your app's client state: capture everything — device storage (AsyncStorage, MMKV, SecureStore), Redux, Zustand, Jotai, and the React Query cache — as a named snapshot, then restore it in one tap. Set up your test state once; jump back to it as many times as you want.
A checkout state gets captured, nine things drift, you open the values, see what restore can't apply and why, then restore in reload mode and watch the restore points come back with the app.
Not a video — this is the shipped tool on mock data. Watch a restore point get captured: storage, Redux, Zustand, the query cache and the screen you were on, under one name.
npm install @buoy-gg/time-machineThat's it — auto-discovery finds the installed package and the TIME MACHINE tool appears in your floating menu:
import { FloatingDevTools } from "@buoy-gg/core";
export default function App() {
return (
<>
<YourApp />
<FloatingDevTools />
</>
);
}
import { FloatingDevTools } from "@buoy-gg/core";
export default function App() {
return (
<>
<YourApp />
<FloatingDevTools />
</>
);
}
Each state source is captured through the Buoy tool that already watches it — install the ones you use (@buoy-gg/storage, @buoy-gg/redux, @buoy-gg/zustand, @buoy-gg/jotai, @buoy-gg/react-query) and they register as snapshot sources automatically. The tool's Sources strip shows you exactly which sources can capture and restore, and why when one can't — including Route, which lights up on any Expo Router app and needs no extra package.
/checkout · 14:32), and you can rename it from the row later. The complete client state persists on-device and survives app restarts and JS reloads./checkout/payment, query string and all), and "Return to this screen" sits with the other changes in the restore preview. It's on by default and it's a property of the restore point, so you answer once rather than on every restore. The app navigates as soon as the state lands — or right after the reload, when you restored in reload mode. Needs Expo Router; without it snapshots carry no route and the checkbox doesn't appear.time_machine_action): capture → drive the flow → restore → repeat is one tool call per step.| Source | Restore mechanism |
|---|---|
| AsyncStorage | Diff: keys missing from the snapshot are removed, snapshot entries bulk-written. Buoy's own keys are never touched. |
| MMKV | Per registered instance, per-key diff. Read-only instances and ArrayBuffer values are skipped (and reported). |
| SecureStore | Registered keys written back with their original options. Biometric-protected keys are never read or written. |
| Redux | A single full-state jump through Buoy's reducer wrapper, verified after dispatch. Works with the zero-config enhancer, or wrap your root reducer in withBuoyDevTools() when using the middleware. |
| Zustand | Full setState(state, true) replace — with your store's action functions re-grafted from the live store first, so useStore(s => s.increment) keeps working. |
| Jotai | Every watched, writable atom is set individually. Derived atoms are captured for inspection and left to recompute. |
| React Query | Per-query diff against the live cache: existing queries get their state set (observers stay attached, with their real queryFns), missing ones are rebuilt, extras removed. Query data is captured in full — snapshots live on-device, so nothing is trimmed for size — while error objects keep only their name and message. Mutations aren't replayable, so the mutation cache is cleared. |
| Route | Opt-in per restore: router.navigate() to the captured URL — after the state is applied, or on the next boot when the restore reloads. |
Honest limits: this is client state — your backend doesn't time-travel, so a restored cart is only as valid as the server allows. Component-local useState and in-flight requests aren't captured. The route restores as a URL (pathname + search params) — the back stack it sat on, and params you passed imperatively as objects rather than in the URL, do not. Sources that can't fully restore say so up front, in the tool.
Install @buoy-gg/time-machine and capture a restore point — it snapshots device storage (AsyncStorage, MMKV, SecureStore), Redux, Zustand, Jotai, and the React Query cache together, and restores the whole set in one tap. Set your test state up once and jump back to it every iteration.
Only if you want it to. Live restore swaps state in place — stores are replaced, the query cache is diffed query-by-query so mounted components keep their subscriptions, and the app stays exactly where it is. Restore + reload writes storage back and reloads the JS bundle so in-memory stores rebuild themselves.
Yes — they persist on-device through restarts and JS reloads, and each one also records the route it was captured on so you can return to the screen as well as the state.