XTend DEV API
The XTend DEV API is the explicit, read-only diagnostics boundary between an XTend application and development tools. Expose it as window.__XTEND_DEV_API__ when the application should be recognized by XTend Dev Surface or another local diagnostics consumer.
The API does not control the application. It publishes current snapshots while the RMT kernel, Fabric, the host runtime and the application remain the owners of execution and state.
DEV API and window.XTend
window.XTend is the product-facing browser API initialized by api.js. It contains runtime services such as theme and feedback APIs. window.__XTEND_DEV_API__ is a separate development contract for telemetry and diagnostics.
The leading and trailing double underscores are intentional. Do not add DEV-only methods to window.XTend, and do not use the DEV API as an application service locator.
Contract at a glance
| Member | Requirement | Result |
|---|---|---|
version | Recommended | DEV API version string; an omitted value is reported as null |
getPerformanceSnapshot() | Required | Current measurements, phases, budgets and statuses |
getFabricTelemetrySnapshot() | Required | Current lanes, fibers, totals and backpressure |
getKernelSnapshot() | Required | Current kernel health, panic and recovery state |
getHydrationSnapshot() | Optional | Hydration or resume strategy, timings, surfaces and XScaler state |
subscribe(listener) | Optional | Observation signal that returns an unsubscribe function |
The three required methods determine whether the runtime bridge is healthy. A missing optional method affects only the corresponding capability. In particular, omitting getHydrationSnapshot() must not degrade Performance, Kernel or Fabric.
Runtime rules
Every snapshot method must:
- Return synchronously. A
Promiseor another thenable is rejected. - Return a JSON-serializable value without cycles, DOM nodes, functions or class instances that lose essential state during serialization.
- Read current state on every call. Do not capture a boot-time snapshot and return it forever.
- Return data owned by the application without transferring ownership to the caller.
- Avoid secrets, credentials, raw user content and unredacted resume tokens.
Install the object before the complete runtime boot when possible. Early calls may return valid degraded snapshots. Replace their data through the host-owned stores when the application becomes ready; do not replace browser globals or patch framework internals to collect it.
XTend Classic opt-in
An HTML-first host that uses the canonical loader can opt into the read-only Classic adapter without adding another script tag:
<script type="module"
src="/xtend-loader.js"
data-manifest="/components/manifest.json"
data-dev-api="true"></script>
The equivalent programmatic option is window.XTendLoader.initiateXTend({ devApi: true }). The loader imports its internal ESM service alongside the manifest. It publishes real loader and browser performance measurements. Fabric, RMT Kernel and SSR hydration report supported: false when those runtimes are not active; enabling diagnostics never boots them implicitly.
The default remains off. An existing host-owned window.__XTEND_DEV_API__, such as the Docs or Animation TestBench adapter, is preserved. The Classic service is intentionally not a direct package export: xtend-loader.js remains its lifecycle owner.
Minimal implementation
The following app-local module establishes a complete v1 boundary. It is an integration example, not an exported XTend factory.
const listeners = new Set();
const telemetry = {
status: 'degraded',
performance: {
schema: 'xtend.devsurface.performance-snapshot.v1',
supported: true,
status: 'degraded',
measurements: []
},
fabric: {
schema: 'xtend.fabric.telemetry-snapshot.v1',
status: 'degraded',
lanes: {},
totals: {
fiberCount: 0,
completedCount: 0,
failedCount: 0,
budgetMissCount: 0
},
backpressure: { level: 'none', action: 'observe' }
},
kernel: {
schema: 'xtend.rmt.kernel-panic-state.v1',
state: 'none',
severity: 'info',
recoveryAction: 'none',
mitigationStrategy: 'observe',
affectedScopes: [],
affectedJobs: []
},
hydration: {
schema: 'xtend.devsurface.hydration-snapshot.v1',
supported: true,
strategy: 'none',
status: 'degraded',
timing: {},
surfaces: [],
xscaler: {},
diagnostics: []
}
};
function cloneSnapshot(value) {
return JSON.parse(JSON.stringify(value));
}
function publishDevTelemetry(patch) {
Object.assign(telemetry, patch);
if (listeners.size === 0) return;
const event = cloneSnapshot({
schema: 'xtend.devsurface.subscription-event.v1',
status: telemetry.status
});
listeners.forEach((listener) => {
try { listener(event); } catch (_) {}
});
}
window.__XTEND_DEV_API__ = Object.freeze({
version: '1.0.0',
getPerformanceSnapshot() {
return cloneSnapshot(telemetry.performance);
},
getFabricTelemetrySnapshot() {
return cloneSnapshot(telemetry.fabric);
},
getKernelSnapshot() {
return cloneSnapshot(telemetry.kernel);
},
getHydrationSnapshot() {
return cloneSnapshot(telemetry.hydration);
},
subscribe(listener) {
if (typeof listener !== 'function') return () => {};
listeners.add(listener);
return () => listeners.delete(listener);
}
});
export { publishDevTelemetry };
When the runtime is ready, update the app-owned adapter with real measurements and snapshots:
const recovery = appRuntime.getPanicRecoverySnapshot();
publishDevTelemetry({
status: 'ready',
performance: {
schema: 'xtend.devsurface.performance-snapshot.v1',
supported: true,
status: 'ready',
measurements: appMeasurements.slice()
},
fabric: fabric.createTelemetrySnapshot({ source: 'my-app' }),
kernel: recovery.kernel || recovery.records?.at(-1) || telemetry.kernel,
hydration: hydrationStore.snapshot()
});
appMeasurements and hydrationStore represent application-owned adapters in this example. They are not new XTend globals. The Docs shell derives the same boundary from its AppRuntime, while the Animation TestBench derives it from its boot, resume and XScaler state.
Performance snapshot
The bridge accepts measurements, performanceMeasurements or entries; measurements is the canonical field for new integrations. Each measurement should use xtend.performance.measurement.v1.
{
"schema": "xtend.devsurface.performance-snapshot.v1",
"supported": true,
"status": "ready",
"measurements": [
{
"schema": "xtend.performance.measurement.v1",
"id": "app.route.transition",
"name": "Route transition",
"phase": "route",
"profile": "app-shell",
"lane": "transition",
"durationMs": 42,
"budgetMs": 140,
"status": "pass",
"sampleKind": "runtime"
}
]
}
Use pass, warn or fail when the application already evaluated the budget. If status is absent, Dev Surface derives it from durationMs and budgetMs. Durations must be elapsed work, not absolute timestamps such as performance.now().
Fabric telemetry snapshot
Fabric accepts lanes as an object keyed by lane ID or as an array with id or lane. Totals make the overview deterministic; fiber records add drill-down detail.
{
"schema": "xtend.fabric.telemetry-snapshot.v1",
"lanes": {
"user-blocking": {
"fiberCount": 3,
"activeFiberCount": 1,
"completedCount": 2,
"failedCount": 0,
"budgetMissCount": 0,
"deadlineMs": 80,
"averageDurationMs": 14,
"backpressureLevel": "none",
"fibers": []
}
},
"totals": {
"fiberCount": 3,
"completedCount": 2,
"failedCount": 0,
"budgetMissCount": 0
},
"backpressure": {
"level": "none",
"action": "observe",
"laneIds": []
}
}
Report the lane used by the runtime instead of mapping every job to a generic lane. That preserves useful evidence for user-blocking work, transitions, visible hydration and idle work.
Kernel snapshot
The kernel snapshot represents current health rather than an error log. A healthy runtime still returns a record with state: "none".
{
"schema": "xtend.rmt.kernel-panic-state.v1",
"state": "none",
"severity": "info",
"recoveryAction": "none",
"mitigationStrategy": "observe",
"affectedScopes": [],
"affectedJobs": [],
"blockedCommitCount": 0,
"criticalViolationCount": 0
}
Supported health states are none, suspected, active, recovering, recovered and failed. Keep recovery and mitigation values explicit so operators can distinguish observation from an active block or retry.
Optional hydration snapshot
Hydration may describe initial hydration, resume or a deliberate no-hydration path. Use elapsed durations relative to the operation that they describe.
{
"schema": "xtend.devsurface.hydration-snapshot.v1",
"supported": true,
"strategy": "server_prerender_resume",
"status": "resumed",
"resumeToken": "redacted",
"resumeTokenRedacted": true,
"rootId": "app-root",
"adapterKind": "node-ssr",
"responseKind": "rmt_template_chunk",
"timing": {
"ssrRenderMs": 18,
"resumeReadMs": 2,
"hydrateMs": 14,
"firstInteractiveMs": 34,
"clsValue": 0
},
"surfaces": [],
"xscaler": {
"mode": "protocol-lazy",
"preflightCount": 0,
"acceptedCount": 0,
"rejectedCount": 0,
"networkDuringRender": false,
"lazyLoadedCount": 0,
"atcSessions": []
},
"diagnostics": []
}
Never reconstruct a resume token for diagnostics. Publish the already-redacted value and mark it with resumeTokenRedacted: true, or omit it entirely.
Subscription behavior
subscribe(listener) is optional. It advertises that the host can notify observers after relevant state changes, but snapshot methods remain the source of truth. Return an idempotent unsubscribe function and isolate listener failures so diagnostics tooling cannot break the application.
The extension may still request a fresh snapshot after receiving a signal. Do not assume that a subscription event transfers the complete runtime state or ownership of it.
Verify from the browser console
Run this in the inspected application tab, not in the DevTools extension page:
const api = window.__XTEND_DEV_API__;
if (!api) throw new Error('XTend DEV API is not installed.');
const required = [
'getPerformanceSnapshot',
'getFabricTelemetrySnapshot',
'getKernelSnapshot'
];
for (const method of required) {
if (typeof api[method] !== 'function') {
throw new Error(`Missing required method: ${method}`);
}
const snapshot = api[method]();
if (snapshot && typeof snapshot.then === 'function') {
throw new Error(`${method} returned a Promise.`);
}
JSON.stringify(snapshot);
}
const unsubscribe = api.subscribe?.((event) => console.debug(event));
unsubscribe?.();
After this check, open XTend Dev Surface and select Refresh. The telemetry tabs should show app values instead of No XTend app detected or placeholder data.
Diagnostics and troubleshooting
| Diagnostic | Meaning | Fix |
|---|---|---|
xtend.devsurface.dev_api.missing | The inspected page exposes no DEV API | Install it in the application page and reload the inspected tab |
xtend.devsurface.dev_api.method_missing | A required method is absent | Add the named method; optional hydration and subscription methods do not trigger this error |
xtend.devsurface.runtime_bridge.async_snapshot_unsupported | A snapshot method returned a thenable | Read prepared state synchronously and perform collection before the DEV API call |
xtend.devsurface.runtime_bridge.serialization_failed | JSON cloning failed | Remove cycles, DOM nodes, functions and non-data class instances |
xtend.devsurface.runtime_bridge.read_failed | A method threw while being evaluated | Catch errors in the app adapter and return a valid degraded snapshot with diagnostics |
If values remain stale after a route transition, inspect whether the method creates a fresh clone from current stores. Replacing window.__XTEND_DEV_API__ repeatedly is not a refresh mechanism and can invalidate subscribers.
Security checklist
- Publish aggregate diagnostics and identifiers, not credentials or user payloads.
- Redact resume tokens before they enter the snapshot.
- Keep methods read-only and free of app mutations.
- Do not patch
fetch,history,performance,customElementsor framework APIs. - Do not load a remote diagnostics runtime or CDN script.
- Treat subscribers as optional observers and remove them during teardown.
Related reading
- XTend Dev Surface
- Performance
- Hydration Policies
- RMT Kernel Runtime
- XTend Fabric Runtime
- API
- RMT AnimationEngine
Demand-driven Maraca observation
In 0.8.0, the existence of window.__XTEND_DEV_API__ does not create continuous demand for Maraca full snapshots. The canonical scaffold adapter connects DEV observers through runtime.subscribeEvents(), disconnects after the final subscription and can connect again later. Signals contain direct, immutable lifecycle events; a diagnostic tool then explicitly calls the snapshot method it needs.
runtime.subscribe() remains a full-snapshot subscription and deliberately enables that cost. Without such subscribers, errors, panic/recovery state, counters and lastEvent remain available for later reads. Existing redaction and synchronous serializability requirements are unchanged.
Check early and late attachment, current reads after changes, and disconnect/reconnect. An adapter that copies a snapshot only during boot violates this contract; the manual example above must be fed by ongoing host-owned updates. See Maraca diagnostics and DEV Surface.
Investigate panic and recovery
rmt.kernel.panic, rmt.kernel.recovery, rmt.kernel.escalation and rmt.kernel.scheduler_failure are diagnostic channels for blocked output, recovery, host escalation and scheduler failures. The absence of a live subscription must not remove their recorded diagnostic information.
Use panicId and correlationId to identify the affected scope and job. blockedCommitCount counts prevented commits; recoveryAction describes the concrete response. quarantined means the scope is isolated, and hostNotified records host notification. Incident Severity depends on severity, affected scope and recovery outcome; a single warning proves neither successful completion nor a fatal condition.
rollback-last-safe-snapshot restores a verified state, render-safe-fallback uses the safe fallback and notify-host requests a host decision. A panic_blocked commit must not be retried through a direct DOM path. A result made superseded by newer presentation is instead a controlled lifecycle cancellation, not a panic.
Record schema, time, correlation, redacted cause and recovery outcome. After recovery, request a fresh synchronous snapshot to verify that the state actually became recovered. User content, credentials and raw tokens do not belong in a shared report. See Trusted DOM for output migration.