Scenario · State
Checkout
A cart, two forms and a few application settings. CartState and AppState are ordinary
scoped services that implement IBevStateProvider, so every action on them is a named state change
in BevTools, with a before/after diff and the click that made it. The forms show what validates them and what
does not, and the coupon box has an accessibility problem to find.
The checkout
Add, remove, apply the coupon (the ✓ button) or clear the cart, then fill the shipping form in and place the
order - nothing is sent anywhere. The newsletter form below it subscribes even with an empty address. The two
toggles change AppState values only; the site's own light/dark theme is the button in the header.
Cart (0 items)
Shipping details
State: history with diffs
Open State and select CartState: the change history names each action
(Add, Remove, ApplyCoupon, Clear), with the values before and
after and a link to the UI event that triggered it. AppState records SetTheme,
ToggleNotifications, Click and a Visit per navigation - and shows its
ApiKey redacted.
Forms: what validates each form
Press Place order with the form empty, then Subscribe with the newsletter field empty, and open
Forms. It lists every EditForm on the page with its model:
- The shipping form is validated by DataAnnotationsValidator and its messages are shown by ValidationSummary; the table has a row per field - modified or not, how many changes, and its validation messages.
- The newsletter form is validated by nothing, next to its model's rules (Email: Required, EmailAddress): it has no
<DataAnnotationsValidator />, soOnValidSubmitalways runs. Diagnostics reports NewsletterModel's validation attributes are never checked. - The Timeline records every validation with its outcome: Form validated: NewsletterModel is valid.
Browser → Accessibility: a coupon box nobody can name
Add something to the cart, then open Browser and press Check the page. Two findings
point at the coupon box, each naming the component that rendered it (Checkout) and the WCAG
criterion, with Show to outline the element: the field has no label - a placeholder disappears as soon as
something is typed, so it is not one - and the ✓ button has no accessible name, because its only content is
hidden from screen readers. Diagnostics repeats them. What markup cannot decide - contrast, focus
order, whether a name makes sense - the panel leaves to a person.
Exposing your own state
A state container becomes a provider by implementing IBevStateProvider - a name, a snapshot and a
Changed event carrying the action - and being registered with
AddBevToolsStateProvider. This is the cart above, shortened:
public sealed class CartState : IBevStateProvider { public List<CartLine> Lines { get; } = []; public string Name => "CartState"; public string? Description => "scoped service, one per circuit / app"; public event Action<BevStateChangeInfo>? Changed; public object? GetSnapshot() => this; public void Add(Product product) { // … update Lines … Changed?.Invoke(new BevStateChangeInfo("Add", product.Name)); } } // Program.cs - registers it as a scoped service and as a state provider builder.Services.AddBevToolsStateProvider<CartState>();
A store that cannot implement the interface - a library's, or one you would rather keep free of BevTools - gets
an adapter instead: AddBevToolsStateProvider<TService>(name, snapshot, subscribe), or an
extension's AddStateProvider, which is how the FilterState of the Products page
appears in the State panel (see Extending).