On this pageOverview
DevTools
Foldkit includes a built-in DevTools overlay that displays every Message flowing through your app and lets you inspect the Model, Message, Commands, and Mounts at any point in time. It renders inside a shadow DOM so it won’t interfere with your styles or layout.
You can see it in action right now. Look for the tab on the bottom right edge of this page.
Open the panel and you’ll see a scrolling list of every Message dispatched so far, newest at the bottom. Click any row to inspect it. Four tabs swap the inspector content: Model shows the full state tree (with changed paths highlighted), Message shows the payload, Commands lists the Commands returned by the update, and Mounts shows which Mounts started or torn down during that render. A Live badge tells you whether you’re looking at the latest state or a past snapshot; clicking a row in time-travel mode pauses the app and Resume returns to live. A Clear button drops history without restarting the app.
AI agent integration
Foldkit also exposes DevTools to AI agents over the Model Context Protocol. See the DevTools MCP page for setup.
DevTools are enabled by default in development. Pass a devTools object to makeApplication to configure behavior:
import { Runtime } from 'foldkit'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
container: document.getElementById('root'),
devTools: {
position: 'BottomLeft',
},
})
Runtime.run(application)The devTools field accepts an object with the following optional properties, or false to disable DevTools entirely.
'Development' (the default) enables DevTools only in development. 'Always' enables them in all environments, including production.
Controls where the badge and panel appear on screen. One of 'BottomRight' (default), 'BottomLeft', 'TopRight', or 'TopLeft'.
'TimeTravel' (the default) enables full time-travel debugging. Clicking a Message row pauses the app and re-renders it exactly as it looked at that point in time. User interaction is blocked while paused, but Subscriptions continue running in the background and new rows keep appearing in the panel. Click Resume to jump back to the live state.
'Inspect' lets you browse state snapshots without pausing the app, which is useful when showing DevTools to visitors in production or staging environments.
You can also pass { development, production } to select a different mode per environment. This is the recommended pattern when show: 'Always' keeps DevTools available in production: keep 'TimeTravel' for local debugging and ship 'Inspect' to your users so clicking a row never pauses their app.
import { Runtime } from 'foldkit'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
container: document.getElementById('root'),
devTools: {
show: 'Always',
mode: { development: 'TimeTravel', production: 'Inspect' },
banner: 'Welcome to our app! Browse the state tree to see how it works.',
},
})
Runtime.run(application)An optional string displayed as a banner at the top of the panel. Useful for welcoming visitors or leaving a note for your team.
The application’s Message Schema. Required only for AI agent integration: when set and the running app is connected to the DevTools MCP server, agents can dispatch Messages into the live runtime. The Schema decodes inbound dispatch payloads at the bridge boundary and rejects mismatches with a clean error. Omit this field to disable agent dispatch entirely.
A list of Message _tag values whose dispatches should not be recorded in DevTools history. The Messages still drive update and the runtime as usual; they just don’t appear in the history panel and don’t pay the per-Message diff cost. Reach for this when an animation-frame Subscription, pointer-move handler, scroll listener, or other high-frequency dispatcher would otherwise flood history with entries that all look the same.
When excludeFromHistory is set, DevTools also switches to a per-entry snapshot strategy so time-travel jumps to recorded entries reflect the real live state at the moment they were recorded. Without this, replay would walk only the kept Messages and miss any cumulative state the excluded ones would have produced. The "Live" model view stays in sync as well: excluded Messages still update the latest-model snapshot, they just don’t append a history entry or compute a diff.
import { Runtime } from 'foldkit'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
subscriptions,
container: document.getElementById('root'),
devTools: {
excludeFromHistory: ['TickedFrame', 'MovedPointer'],
},
})
Runtime.run(application)Maximum number of recorded Messages retained in history before the oldest is evicted. Defaults to 100. Clamped to the range 20 to 500: smaller values keep the panel snappy under high message rates, larger values give you more scroll-back. Each retained entry is one append + diff in the regular case, or one append + full Model snapshot when excludeFromHistory is active, so memory cost scales with both maxEntries and your Model size.
import { Runtime } from 'foldkit'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
container: document.getElementById('root'),
devTools: {
maxEntries: 250,
},
})
Runtime.run(application)