Skip to content

Beyond the panel

Extending

A library - or an app - can add to BevTools: panels, state providers, diagnostic rules, palette commands, value formatters and timeline events of its own. This site registers such an extension, DemoBevToolsExtension, and every part of it is visible in the panel right now.

Timeline events of your own

Code that does work BevTools cannot see - a sync, a cache refresh, a background job - can put it on the timeline through IBevToolsTimeline, a scoped service. Each button below records an Inventory sync with BeginActivity, which takes its duration when the scope ends.

Live Renderer: Static · prerendering CtrlShiftD opens BevTools
0 sync(s) run
@inject IBevToolsTimeline Timeline

using (Timeline.BeginActivity("sync", "Inventory sync", $"{ms} ms of simulated work"))
{
    await Task.Delay(ms);
}

The Timeline shows each sync as a custom entry with its duration, caused by the click that started it (the custom chip filters them). Record and Complete do the same in two steps, and IsEnabled says whether to build an event at all.

What the demo's extension adds

  • A panel: the Store tab, after the built-in ones. It lists who listens to FilterState - open the Products page, show and hide the price alert, and one more lambda in PriceAlert.OnInitialized stays subscribed each time. It inherits BevToolsTab, so it re-reads whenever the panel refreshes, and its renders and clicks never count as the app's.
  • A state provider: State → FilterState, marked extension. FilterState knows nothing about BevTools; an adapter reads it and names each change (SetQuery, SetCategory, SetMaxPrice, Notify), so typing on the Products page builds a history with diffs.
  • A diagnostic rule: demo.slow-sync turns a sync over a second into a finding - press Run a slow sync and open Diagnostics. It appears in the Rules table with the built-in ones.
  • Two commands: CtrlK, then >demo or >cart → Show the demo store and Copy the cart as Markdown, under Demo store. Without the > the palette searches components, events and services. A command can show a panel, select a component or an event, notify, and hand text to the browser (CopyTextAsync, DownloadTextAsync).
  • A value formatter for Product: select a ProductRow, or open State → CartState, and a product reads Rustic Keyboard 2 · $83.99 instead of a record to expand.

About → Session → Extensions names every extension that is loaded: Demo store, and Blazor Server circuits - the Circuit panel, its rules and command are themselves an extension.

Writing one

A library references Bit.BevTools, implements IBevToolsExtension and is added to the options. This is the demo's, shortened; the whole file is Demo/Client/Extension/DemoBevToolsExtension.cs.

public sealed class DemoBevToolsExtension : IBevToolsExtension
{
    public string Name => "Demo store";

    public void Configure(IBevToolsExtensionBuilder builder) => builder
        .AddPanel(new BevToolsPanelDescriptor("demo-store", "Store", typeof(DemoStorePanel)))
        .AddStateProvider(services => new FilterStateAdapter(services.GetRequiredService<FilterState>()))
        .AddDiagnosticRule(new SlowSyncRule())
        .AddCommand(new ShowStoreCommand())
        .AddCommand(new CopyCartCommand())
        .AddValueFormatter(typeof(Product), v => v is Product p ? $"{p.Name} · {p.Price:C}" : null);
}

// Program.cs of every host that runs BevTools - here both the server and the .Client project
builder.Services.AddBitBevToolsServices(o => o.Extensions.Add(new DemoBevToolsExtension()));
  • HideComponentsInNamespace("MyLibrary.Internal") keeps a library's internal components out of the tree; the internals toggle in the Components tab still shows them.
  • On members: [BevToolsSensitive] redacts a value, [BevToolsIgnore] leaves a member - or, on a class, a whole component - out of BevTools. The dashboard's UserSession uses both.
  • From code: session.PauseCapture() / ResumeCapture(), session.UseAmbient() to attribute background work to a session, and BevSessionExporter / BevTraceExporter for the files About exports.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.

An unhandled error has occurred. Reload 🗙