Instrument bricks

Six shapes, one pipe, no domain knowledge. Live against meterd on 127.0.0.1:8812 — real samples, nothing mocked.
collector metrics shapes 6

The one idea

The element is the SHAPE. The metric is a KNOB. There is no <meter-ram>. There is <brick-fill>, which renders any bounded level — pointed at any endpoint speaking the pipe, under any metric name it has never heard of, with no code change. Everything below is the same seven elements with different attributes.

Every shape, every density

One element, three renderings, switched by the density attribute — not three elements. glance is the shape alone at a lazy 30 s poll; detail adds a live graph and a readout at 1 s; history adds an in-component range picker (click it, or tab to it and use the cursor keys — Enter opens and selects, Esc closes; no native dialog anywhere).

glance — header / deck card · poll 30 s
brick-fillbounded level
<brick-fill metric="ram" accent="--violet">
brick-columnsbounded rate, per unit
<brick-columns metric="cpu" accent="--cyan">
brick-donutbounded composition
<brick-donut metric="disk" accent="--emerald">
brick-curveunbounded rate
<brick-curve metric="net" accent="--gold">
brick-counteraccumulator · push()
host-driven · warn=7 limit=10 unit=usd
brick-statediscrete state · push()
states="0=detached:--faint, 1=attaching:--gold, …"
detail — expanded card / modal · poll 1 s · live graph + readout
brick-filldetail
brick-columnsdetail
brick-donutdetail
brick-curvedetail
brick-counterdetail · push()
brick-statedetail · push()
history — analysis view · poll 10 s · user-selectable range
brick-fillhistory
brick-columnshistory
brick-donuthistory
brick-curvehistory
brick-counterhistory · push()
brick-statehistory · push()

Proof: the element is the shape

The three cards below are the same element, <brick-fill>, with one attribute changed. The first two are pointed at two different real metrics. The third is pointed at a metric name this collector has never emitted and never will, and is fed entirely through push() by the page — no fetching at all. If a brick knew what RAM was, that third card could not exist.

same element · metric #1live
<brick-fill metric="ram">
same element · metric #2live
<brick-fill metric="disk"> — identical markup, one attribute differs
same element · unknown metricSYNTHETIC
<brick-fill metric="flux-capacitor-charge" unit="jigawatts">
driven by push() only — the one place on this page Math.random() is allowed
why that third card is the whole point

meterd has never heard of flux-capacitor-charge and never will. The brick renders it correctly anyway, in a made-up unit, because a shape only ever asked for a bounded level. Tomorrow that same element renders a context window, a storage quota or a battery — by changing an attribute, not code.

Proof: presets are convenience, never architecture

<brick-meter> is the only thing in the module that knows a metric name, and it is a plain data table you can extend at runtime with Brick.presets.register(). Given an unknown metric it picks a shape from the payload's own structure instead — total ⇒ fill, parts ⇒ columns, neither ⇒ curve.

preset hit→ fill
<brick-meter metric="ram">
preset hit→ columns
<brick-meter metric="cpu">
preset hit→ donut
<brick-meter metric="disk">
registered at runtime→ curve
Brick.presets.register('net-alias', …) — added by this page, not the module

Proof: absent data is a state, not a crash

Left: a metric the collector has genuinely never sampled — the server answers {"ok":false} and the brick draws its empty frame and a muted dash. Never a zero, which would be a lie; never a blank box, which reads as broken layout. Right: an endpoint that does not exist at all — a network failure, handled identically, with the label marked and a brick-error event fired rather than console noise.

no data yetok:false
unreachable endpointnetwork error
no parts in payloadaggregate only
ram carries no parts[] — the column is marked Σ, not faked per-unit
unbounded as columnsper-interface
no total ⇒ columns scale to the window's own max

Proof: a brick never owns the page

Bricks get told their bounds and adapt to them (paths/ui.md). Same markup at 80 px, 120 px, 200 px and a box you can drag by its corner — a ResizeObserver repaints, chrome drops out below ~110 px, and the bar fitter walks up its unit ladder rather than drawing bars too thin to read.

Proof: one poll can drive many bricks

Every brick above fetches for itself. These four do not: poll="0" turns self-fetching off and the page fans a single payload out with push(). This is the Nozotros-header case — one lazy poll, six instruments — and it needs no code change to reach, only an attribute. Driver interval is 5 s here so it is visibly live; a header would use 30 s.

pushedpoll=0
pushedpoll=0
pushedpoll=0
pushedpoll=0

Events

Every brick fires brick-update and brick-error, composed and bubbling, so a host can log, aggregate or alarm without reaching into a shadow root. Both are shown below — including the deliberate failure from the unreachable endpoint above.