Overview

mates-devtools is a separate package. Install it with npm install mates-devtools (mates stays a peer). renderMatesDevTools() mounts a gear FAB and a resizable panel: Components, Logs, User Logs, and Performance. This site already calls it, so the gear on the landing page and in these docs inspects the components on the page you are reading.

Call renderMatesDevTools() before renderApp() so hooks are installed when the first components mount.

Core keeps tiny no-op stubs in lib/devtools. The panel package fills them via installDevToolsHooks — production bundles stay clean when you omit the package.

What the panel shows

Surface
What it tracks
Use it when
Components Mounted tree, props, atoms, scopes, hooks, useState A view re-rendered and you want the values it read. The list flashes a render count. Highlight jumps to the DOM node.
Logs Mount, render, unmount, atom writes, events, Fetch You want the sequence: which component rendered, which atom changed, and which request left the app.
User Logs log(), logError(), logWarning() Your own messages. They go through matesLogEvent and show here even when the console stays quiet off localhost.
Performance Render duration per component Total, average, and slowest render, plus a bar of recent timings. Open a row to jump back to that component.
Timers Live setTimeout / setInterval A button in the panel header. Use it when a timer keeps firing after the screen that started it is gone.

Counter + DevTools panel

A Counter plus log(). Open the gear FAB, then press + / − and log(). Components and Logs follow the atom. User Logs shows the log() call.

import { html, atom, x, log, renderApp } from 'mates';
import { renderMatesDevTools } from 'mates-devtools';

// Install hooks + FAB / panel before any component mounts
renderMatesDevTools();

const Counter = () => {
const count = atom(0);

return () => html`
<div class="m-col m-gap m-items-center">
<p class="overline">count</p>
<h1>${count()}</h1>
<div class="m-flex m-gap-sm">
<button class="btn" @click=${() => count.set(n => n - 1)}>−</button>
<button class="btn-ghost" @click=${() => count.set(0)}>Reset</button>
<button class="btn-primary" @click=${() => count.set(n => n + 1)}>+</button>
<button class="btn" @click=${() => log('counter', { n: count() })}>log()</button>
</div>
<p class="hint">Open the gear FAB. Components and Logs follow the atom. User Logs shows log().</p>
</div>
`;
};

const App = () => () => html`
<div class="m-col m-gap">
<h1>DevTools counter</h1>
<p class="muted">Click + / −, then inspect atoms and renders in the panel.</p>
${x(Counter)}
</div>
`;

renderApp(App, document.getElementById('app'));

/* Extra bottom pad so the DevTools FAB doesn't cover actions */
#app { padding-bottom: 4.5rem; }

installDevToolsHooks (bridge)

Low-level demo: wire a partial hook set and flip isDevToolsInstalled. Prefer the panel demo above for day-to-day debugging.

import { html, atom, renderApp, installDevToolsHooks, isDevToolsInstalled } from 'mates';

const App = () => {
const installed = atom(isDevToolsInstalled());
const log = atom([]);

const wire = () => {
installDevToolsHooks({
logAtomSet: (name, value) => {
log.update(rows => {
rows.push('atom set ' + name + ' = ' + String(value));
});
},
registerComponent: () => {},
});
installed.set(isDevToolsInstalled());
};

const count = atom(0);

return () => html`
<div class="m-col m-gap">
<p class="muted">installed=${installed() ? 'yes' : 'no'}</p>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${wire}>installDevToolsHooks</button>
<button @click=${() => count.set(c => c + 1)}>
count=${count()}
</button>
</div>
<div class="card card-body">
<span class="overline">Hook log</span>
${log().map(line => html`<div class="mono">${line}</div>`)}
</div>
<p class="muted m-t-5">Prefer renderMatesDevTools() for the full panel — this only shows the bridge.</p>
</div>
`;