Diagnostics
Everything the framework recovers from is reported on one cancelable event, and nothing in core calls console.warn() or console.error() directly.
The protocol
EVENTS.diagnostic is 'js-toolkit:diagnostic'. It carries a ToolkitDiagnosticDetail:
| Field | Type | Notes |
|---|---|---|
severity | 'warning' | 'error' | |
code | ToolkitDiagnosticCode | a namespaced string from DIAGNOSTICS |
message | string | |
component | string | undefined | the reporting component's name, when there is one |
error | unknown | required for error, absent for warning |
Every diagnostic starts on its relevant connected element, or on document when there is none, and is dispatched with { bubbles: true, composed: true, cancelable: true }.
Listening
document.addEventListener(EVENTS.diagnostic, (rawEvent) => {
const event = rawEvent as CustomEvent<ToolkitDiagnosticDetail>;
monitor(event.detail.code, event.detail);
if (event.detail.code === DIAGNOSTICS.responsive.unknownBreakpoint) {
event.preventDefault();
}
});Dispatch always happens before the default output.
- An uncancelled warning calls
console.warn()once with exactly[js-toolkit:<code>] <message>. - An uncancelled error calls
reportError(detail.error)with the original value. preventDefault()suppresses that output only. It changes no framework decision.
The codes
DIAGNOSTICS is a deeply frozen object, and ToolkitDiagnosticCode is the exact union of its strings:
import { DIAGNOSTICS } from '@studiometa/js-toolkit';
DIAGNOSTICS.component.loadFailed; // 'component.load-failed'
DIAGNOSTICS.callback.serviceFailed; // 'callback.service-failed'
DIAGNOSTICS.protocol.lateRegistration; // 'protocol.late-registration'See the full list.
Report once, or report and continue
| Behaviour | Cases |
|---|---|
| Reported exactly once | recovered load, mount, invalid-strategy and Base lifecycle failures |
| Report and continue | isolated signal, context subscription and teardown, attribute watcher, service, scheduler tick and task, DOM-update runner and extendable-event callbacks |
| Thrown or rejected | decorator and manifest-adapter misuse, shared-runtime incompatibility, service startup rollback, caller-owned teardown, viewTransition() and swap() |
A scheduled task also rejects its own promise with the same value. Direct, caller-owned failures stay throws: they are the caller's to handle, not a channel's.
Warning deduplication is stored in a revisioned shared-runtime slot, keyed by weak owner plus a misuse key, so it works across independently evaluated copies of the package without retaining instances, elements, declarations, runners or manifest inputs.
Reporting your own
warn() and reportDiagnostic() are public, and a consumer code is any '<namespace>.<name>' string:
import { reportDiagnostic, warn } from '@studiometa/js-toolkit';
warn('carousel.no-slides', 'A Carousel with no slide does nothing.', { component: 'Carousel' });
try {
JSON.parse('{');
} catch (error) {
reportDiagnostic('carousel.bad-config', 'The config attribute is not valid JSON.', error);
}From inside a component, $warn() and $error() fill the component name in for you:
this.$warn('carousel.no-slides', 'A Carousel with no slide does nothing.');What was removed
EVENTS.error, ToolkitErrorDetail and ToolkitErrorStage are removed with no alias. DIAGNOSTICS, ToolkitDiagnosticSeverity, ToolkitDiagnosticCode and ToolkitDiagnosticDetail replace them.
In tests
Asserting on console.warn cannot see the code, the severity or the reporting component, and it passes for the wrong diagnostic. captureDiagnostics() reads the channel instead — and cancelling each event as it arrives is the same act that silences the console, so collecting and silencing are one step:
import { captureDiagnostics } from '@studiometa/js-toolkit/test';
const diagnostics = captureDiagnostics();
// … exercise the component
expect(diagnostics.codes).toContain('ref.mismatch');
diagnostics.stop();