Learn / Errors and failures

Diagnostics

A diagnostic is { _tag, source, level, message } plus its facts: source names the package, level is error when something broke on this side and warning when the application should change something. Every package reports through Diagnostics.report, which hands it to the Diagnostics.Reporter in the Effect context and never fails. By default that is Effect's log: the browser's console, a server's log, wrangler tail on a Worker. With Effect's default logger an error is written with console.error (stderr on a server) and a warning with console.log, so DevTools draws an error red and an end-to-end check for console errors fails on it, while an offline warning does not. A server that replaced Effect's logger receives both at their level.

Replace it like any service, in a runtime's resources or a server's layer:

ts
import { Effect, Layer } from 'effect'
import { Diagnostics, Remote } from 'foldkit-remote'

const resources = Layer.mergeAll(
  Remote.clientLayer(Remote.http('/remote')),
  // Logged as by default, and sent on to the server.
  Diagnostics.layer(diagnostic =>
    Effect.andThen(
      Diagnostics.log(diagnostic),
      Effect.sync(() => navigator.sendBeacon('/diagnostics', diagnostic.message)),
    ),
  ),
)

In a browser, give the reporter with Diagnostics.layer(reporter) rather than Layer.succeed: Foldkit runs Mounts outside the runtime's resources, and the layer also claims what a Mount hands the document (Diagnostics.dispatch), such as MountFailed. A page that should not show warnings in production picks Diagnostics.quiet (errors only) where it knows its build mode; in development, foldkit-mixins' DevOverlay lists every diagnostic on the page.

Diagnostics is foldkit-surface's, re-exported by foldkit-remote. Each package's variants:

Remote's client also records its diagnostics in the Model (Data.inspect(model).diagnostics, the newest 256), since its reducer is pure: Data.wiring and Remote.fold report what each update recorded. An update that calls Data.reduce by hand keeps them in the Model and reports nothing.

Development and production report the same unless the application chooses Diagnostics.quiet; no package switches a diagnostic off in production. What development adds is checks you opt into: Remote.make({ verifyPredictions: true }), which costs a comparison per answer and a fetch per placed list. GuardServer.validate throws at startup for a guard a server does not apply, and reports UnpinnedWrites for a write a change can come between.

A definition the application wrote wrong throws instead, when it is made, naming the call and the option. Theme.make, Theme.set and Knobs.withValue throw foldkit-mixins' Diagnostics.DiagnosticError with theme:knob-value (Theme.make: surface.lightness refuses its value: … Expected a value between 6 and 36 at ["dark"]) or theme:unknown-knob.