Skip to content

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:

FieldTypeNotes
severity'warning' | 'error'
codeToolkitDiagnosticCodea namespaced string from DIAGNOSTICS
messagestring
componentstring | undefinedthe reporting component's name, when there is one
errorunknownrequired 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

ts
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:

js
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

BehaviourCases
Reported exactly oncerecovered load, mount, invalid-strategy and Base lifecycle failures
Report and continueisolated signal, context subscription and teardown, attribute watcher, service, scheduler tick and task, DOM-update runner and extendable-event callbacks
Thrown or rejecteddecorator 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:

ts
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:

js
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:

js
import { captureDiagnostics } from '@studiometa/js-toolkit/test';

const diagnostics = captureDiagnostics();
// … exercise the component
expect(diagnostics.codes).toContain('ref.mismatch');
diagnostics.stop();

MIT Licensed