Read-only introspection of a uiSession + lightweight profiler + readable elm-array dump.
Module devtools | Source packages/front/fw/src/dom/rendering/devtools.js | Deps clock | Worker-safe no
Exposes data only (snapshots, strings, durations) — no UI. The sde/sdc layer can build a debug panel or console on top. Standalone usage: pipe the outputs to console.log / console.table.
Resolve
const dev = runtime.resolve('devtools');
// Returns: { inspect, summarize, dumpTemplate, profile, profileAsync, timer }
API
| Method | Signature | Returns |
|---|---|---|
inspect |
(session: UISession) => Snapshot |
Plain-JSON snapshot of the session |
summarize |
(session: UISession) => string |
Single-line summary |
dumpTemplate |
(input: ParseResult | ElmNode[], opts?) => string |
Readable indented tree |
profile |
(fn: () => T) => { result: T, durationMs: number } |
Sync profile |
profileAsync |
(fn: () => Promise<T>) => Promise<{ result: T, durationMs: number }> |
Async profile |
timer |
() => { mark(label), end() } |
Multi-segment profiler |
dev.inspect(session) → Snapshot
Photographs the logical state of the session without capturing live DOM nodes. All missing internal fields are tolerated (returned as null or []) — useful on a partially disposed session.
Returned shape:
{
container: string | null,
blocks: Array<{ id: string, logicalIds: string[], isLoop: boolean }>,
attaches: Array<{ blockId: string, slots: string[] }>,
children: Array<{ parent: string, children: string[] }>,
listeners: Array<{ blockId: string, logicalId: string, names: string[] }>,
mountHooks: string[],
unmountHooks: string[],
lists: Array<{ parent: string, slot: string, size: number, disposed: boolean }>,
portals: Array<{ name: string, container: any }>,
}
Throws Error if session is null / not an object.
dev.summarize(session) → string
Returns a space-separated string, one token per mounted block: id(logicalIds…). Loops: id(loop×N). Convenient for quick console.log.
dev.dumpTemplate(input, opts?) → string
Indented render of the tree of an elm-array or a full ParseResult. Each line: <tag#id> [text=…] [slot=…] [attrs=…] [map=…]. The iterates of a ParseResult are displayed in separate sections.
| Option | Type | Default | Description |
|---|---|---|---|
indent |
string |
' ' (2 spaces) |
Indentation string per level |
dev.profile(fn) → { result, durationMs }
Executes fn() synchronously and returns its result + the duration via clock.monotonic (~1 ms resolution). Throws Error if fn is not a function.
dev.profileAsync(fn) → Promise<{ result, durationMs }>
Async variant. Throws Error if fn is not a function.
dev.timer() → { mark(name), end() }
Multi-segment profiler. mark(name) opens a segment and closes the previous one. end() closes the last open segment and returns:
{
total: number, // ms since the timer was created
segments: Array<{ name: string, durationMs: number }>
}
Examples
Inspecting a session in the console
const dev = runtime.resolve('devtools');
const ui = runtime.resolve('uiSession')('app');
// … mount some blocks …
console.table(dev.inspect(ui).blocks);
console.log(dev.summarize(ui));
// e.g.: "header(h1,nav) content(body) sidebar(menu)"
Dumping a parsed template
const parser = runtime.resolve('parser');
const result = parser.fromHTML('<ul><li>#{text}</li></ul>');
console.log(dev.dumpTemplate(result));
// <ul#root>
// <li#item> map=[$text=#{text}=]
Synchronous profile
const { result, durationMs } = dev.profile(() => render.full(items));
console.log(`render: ${durationMs.toFixed(2)} ms`, result);
Async profile
const { result, durationMs } = await dev.profileAsync(async () => {
const resp = await fetch('/api/data');
return resp.json();
});
console.log(`fetch+parse: ${durationMs.toFixed(2)} ms`);
Multi-segment timer
const t = dev.timer();
t.mark('parse'); const parsed = parser.fromHTML(html);
t.mark('render'); const elms = render.full(parsed, data);
t.mark('insert'); template.elms('app', elms);
const { total, segments } = t.end();
console.table(segments);
// parse: 1.2 ms, render: 0.8 ms, insert: 2.1 ms, total: 4.1 ms
Notes
inspect()reads the internal fields_map,_attach,_children,_listeners,_mount,_unmount,_lists,_portals,_containerofuiSession. These are implementation details — the snapshot may change between major versions.worker-safe: false—devtoolsis not designed for Worker use; theclockmodule itself does not impose this restriction.profile/profileAsyncuseclock.monotonic(sanity-safe, not directperformance.now) — resolution is ~1 ms on most platforms.devtools.factory(clock)takes the clock INSTANCE, not theclockmodule descriptor.runtime.resolve('devtools')injects it correctly —def.factory.apply({}, deps)insrc/core/runtime.jscalls dependency factories for you before injection. Hand-wiring outsideresolve()must do the same call itself:devtools.factory(clock.factory()), neverdevtools.factory(clock).dumpTemplatedoes not capture live DOM nodes — only the static elm-array structure. For live DOM state, usedev.inspect().- Internals:
uiSessionis composed of three internal mixins (uiSession-core.js,uiSession-direct.js,uiSession-list.js) that are not documented separately;inspect()reflects their combined structure.