SAO ✦ BILLING ✦ INTERACTIVE
Billing Dashboard
An interactive billing simulation built on SAO state machines. Advance the clock, trigger invoices, fail payments, and watch subscriptions degrade — all running in the browser.
Advance Clock
Advance the billing clock to trigger subscription renewals and invoice creation.
Payment Behavior
Set what happens when the engine attempts to charge each payment method.
Schema, Machine, Instance, Outcome. Every resource type is a compiled state machine. Transitions are validated, outcomes are immutable.
Advance the clock, find due subscriptions, create invoices, attempt payment. The full billing cycle in pure functions.
Configure per-card behavior: succeed, decline, or auth failure. See how the billing engine handles each scenario.
The dashboard above is composed from five Gutenberg block types. Each block is a top-level entry in the page spec, self-positioning via position or view fields. The shell boots the JS runtime; the other blocks are pure configuration the runtime interprets.
The layout container. Defines the sidebar or topbar chrome, the list of views (with labels and routing keys), and a JS bundle that boots the runtime.
Renders the current simulated (or real) time. Declares its position within the shell. The value comes from the runtime, not the spec.
Buttons that advance the billing clock by configurable increments. Declares which view it belongs to. Each step has a label and a day count.
Per-payment-method controls that set what happens on charge attempts: succeed, decline, or auth failure. Declares its view and the available options with visual tones.
Not a standalone block — a property of views in the shell. Defines what sections appear when a user clicks into a resource (e.g. customer detail showing subs, invoices, PMs).
At compile time, Gutenberg serializes each block's config into the HTML (via data attributes or inline JSON). At runtime, the shell's JS bundle boots, discovers the other blocks in the DOM, reads their config, and mounts them into the declared positions. The blocks carry no JS of their own — they are pure configuration that the runtime interprets.
| Block | Field | Example | Meaning |
|---|---|---|---|
| clock_display | position | nav_footer | Bottom of the sidebar nav |
| clock_advance | view | controls | Inside the Controls view |
| payment_behavior | view | controls | Inside the Controls view |
The shell's JS bundle creates a Database and BillingEngine in memory. Clock advance calls engine.advanceClock(). Payment behavior calls engine.payments.setBehavior(). All state is local. Full re-render on every action.
The shell connects to a Durable Object via WebSocket or SSE. Clock advance sends POST /api/clock/advance. Outcomes stream back and update the UI incrementally. The specs don't change — only the bundle changes.
The critical design invariant: specs describe what the UI shows and what controls are available. They never describe how state is computed or where it lives. The runtime is the only moving part between local simulation and production streaming.
Each interactive block is a top-level entry in the page spec. Self-positioning via position or view fields. Avoids deep YAML nesting. Each block is independently authorable and lintable.
The navigation sidebar is part of the shell spec's views array, not a separate block. Nav is intrinsic to the shell's layout — separating it creates a reference coupling without adding flexibility.
Click-to-detail is a detail field on DashboardViewSpec, not a standalone block. It describes what happens when a resource is selected — which sub-resources to show, whether to show alerts.
No block spec contains data values (clock time, resource counts, payment method IDs). The spec says show a clock here in date format. The runtime fills in the actual value. This is what makes the spec portable between local simulation and streaming production.
Five blocks. One runtime. The spec is the contract between the page author and the dashboard engine. Change the YAML to reshape the dashboard. Change the bundle to reshape the behavior.