Devtools
Gyral's loop is data from end to end: an event becomes a message, a reducer turns it into state
and commands, a driver answers with another message. So a timeline of exactly what happened is
cheap to record. @gyral/devtools shows it in a panel inside your page.
npm install -D @gyral/devtoolsOpening the panel
Load it in development only, behind a URL parameter, so production builds never include it:
// src/devtools.ts
// Add ?devtools to the URL to open the panel. `vite build` replaces import.meta.env.DEV with
// false, so the import below disappears from production bundles.
if (import.meta.env.DEV && new URLSearchParams(location.search).has('devtools')) {
void import('@gyral/devtools').then(({ mountDevtools }) => {
mountDevtools({ open: true });
});
}
export {};Import that module from your client entry. Every Gyral example works this way: add ?devtools
to an example's URL to open it.
mountDevtools(options) returns { panel, unmount }. The options are open (start open),
parent (where to put the panel; default document.body) and shortcutCode (the key used with
Alt+Shift to toggle it; default KeyD).
What it shows
- Timeline. Every event, newest first: components connecting, disconnecting and
hydrating, and hydration mismatches; each message with the state before and after; each
command as it's issued, dropped (
exhaust), interrupted (switch, disconnect), settled with the driver's output, or failed with its error; and store messages. Filter by text or by kind. The last 500 events are kept. - Components. The live instances, with a preview of each one's current state. Components that connected before the panel loaded are listed too.
- Command lanes. Each lane's owner, policy, last phase and how many commands are in flight,
which makes a stuck request or an unexpected
switcheasy to spot.
Toggle the panel with its button or Alt+Shift+D. It is itself a Gyral component: a labelled region with a real heading and keyboard-operable controls, in light and dark.
Free in production
The instrumentation lives in @gyral/core, behind a package-internal import with a
development condition. Vite resolves that condition in the dev server and in Vitest, and the
production one in vite build, where every call site is guarded by a constant false. The
bundler drops the call sites and the module: a production build contains neither the hook nor
its event code.
With other bundlers, set the development resolve condition in development builds to get events.
Without it you get the safe default: no events.
Building your own tools
The panel listens through a small hook. A development build of @gyral/core emits each event to
globalThis.__GYRAL_DEVTOOLS__.emit(event) when that object exists (DEVTOOLS_GLOBAL names
the property, and the DevEvent type describes the events). Both come from
@gyral/core/internal, which has no stability promise: the event shapes may change in any
release. Without a listener, Gyral returns
before building an event. Event payloads hold live references to elements and state: read them,
never change them.