Documentation menu

Dev Bar

The Via Dev Bar is a built-in, dev-gated debug overlay — like the Symfony Web Debug Toolbar or Laravel Debugbar, but tuned to php-via's signals, scopes, and SSE model. It streams live over the framework's own SSE connection and injects into any page with zero configuration.

It's running on this page right now. Look for the via pill in the bottom-right corner and click it open — every panel below describes what you're looking at.

Enabling it

The Dev Bar follows devMode by default: on in development, off (and 404-ing all its endpoints) in production. Override it explicitly with withTracing() — that's how this public site demos it.

$config = (new Config())
    ->withDevMode($isDev)
    ->withTracing(true)          // force on even in prod (read-only — see below)
    ->withTraceBufferSize(100);  // optional: ring-buffer depth (default 100)

When tracing is off, Tracer::current() is null and every capture call site is a no-op — there is no measurable overhead in production.

The six panels

PanelShows
TracesA waterfall of every request, action, and broadcast — nested spans with proportional bars, durations, and attributes.
SignalsThis context's named signals grouped by scope, with their live values. Editable when writes are enabled.
SSEThe wire: every datastar-fetch event, with the real patch payload (elements / signals) expandable inline.
RequestRoute, context id, and the request span's attributes (method, status, byte size).
ScopesActive scopes, contexts per scope, and connection counts — the php-via-specific view of who shares what.
LogsServer-side log records (level-filtered) plus client errors. See Server logs.

Adding your own spans

The framework auto-instruments the request → action → render → broadcast path. To time your own work — a DB query, an HTTP call, an expensive computation — wrap it in $c->span(). The name's prefix before the first dot becomes its colour category.

$issues = $c->span('db.list_issues', fn () => $repo->all(), ['limit' => 50]);

// Annotate the currently open span from anywhere in the action:
$c->traceAttribute('cache.hit', false);

For a static data layer with no Context in scope, reach the ambient tracer directly — it participates in whatever trace is open on the current coroutine:

use Mbolli\PhpVia\Tracing\Tracer;

private static function getCell(int $row, int $col): string {
    $tracer = Tracer::current();
    $run = fn () => /* … the SQLite query … */;

    return $tracer === null ? $run() : $tracer->span('db.get_cell', $run, ['row' => $row, 'col' => $col]);
}

The Spreadsheet and Chat Room examples both emit real db.* spans this way. Categories map to colours:

CategorySourceColour
requestpage / action root spansgray
renderrender.regions, render.componentgreen
cacheview-cache hitsyellow
dbyour db.* spansblue
ssebroadcast fan-outviolet
anything elseyour custom spansteal

Component renders are recorded as render.component spans tagged with the component namespace, so you can see exactly which sub-context re-rendered.

Server logs

Anything that goes through the framework logger — $app->log(), the per-context logger, broker errors, action failures — is teed into the Logs panel and streamed live. The level filter defaults to info; switch to debug to see the firehose, or error to focus on failures. Client-side window.onerror events land here too.

Only logs routed through the framework logger are captured. Raw error_log() or echo from application code bypass it — route those through $app->log() if you want them in the panel.

Editing signals

The Signals panel can write values back to the server, the way Livewire / React DevTools let you poke at state. This is gated separately and hard-disabled outside devMode.

$config
    ->withDevMode(true)
    ->withTracingWrites(true);   // or set VIA_DEVBAR_WRITES=1
Writes can never be enabled in production. isTracingWritesEnabled() begins with a devMode check, so even an explicit withTracingWrites(true) is ignored when devMode is off — withTracing(true) on a public site is always read-only. Any visitor who could reach an editable panel could mutate shared ROUTE/SESSION/GLOBAL state, so never enable writes on untrusted traffic.

Endpoints & the console

With tracing on, the server exposes these under /_via/*; with tracing off they all 404, so production never advertises the surface.

EndpointPurpose
GET /_viaStandalone full-screen Dev Console
GET /_via/streamSSE stream multiplexing trace + log events
GET /_via/scopesJSON snapshot for the Scopes panel
POST /_via/signalSignal write (refused unless writes are enabled)
GET /_via/devbar.js, .cssOverlay assets

The overlay is a Shadow-DOM web component, fully isolated from your page's CSS and Datastar runtime, and it survives full-page morphs. Open /_via for the console view.

The trace and log buffers live in the worker process, so under worker_num > 1 a console stream sees one worker's activity. That's fine for a development tool — production runs with tracing off.

See also