The client runtime
The server stays the source of truth, but something runs in the browser: a home-made engine, with no npm and no third-party reactive framework. This page says what it guarantees, then enumerates its vocabulary.You do not write this
In a Bretzel app, the `bz-*` directives are emitted by the components: passing `visible=binding` produces a `bz-show`, `value=binding` produces a `bz-model`. This page is the emergency exit — what one reads to understand what was emitted, or to write by hand the case the components do not cover.The three guarantees
A client runtime is judged on what it promises when the server rewrites the page under its feet.| Guarantee | The mechanism |
|---|---|
| A client state survives a server refresh | Every scope is keyed by bz-id. After a morph, the subtree is re-scanned: the bindings are thrown away then rewired, and the effects restore what the morph overwrote (text, display, value, classes). |
| No round trip just to display something | A client binding compiles into a JS expression evaluated in the browser. Showing, hiding, computing a class: nothing goes over the network. |
| The transport boundary is the runtime's | A component never POSTs. It declares a handler; the bridge adds the HMAC signature, the CSRF token, the protocol headers and the client-state snapshot. |
What the runtime weighs
The bundle is concatenated from `_src/*.js`, with no bundler and no minifier — the file served is the one you debug. The numeric prefix IS the load order.25 modulescore: 8 files, 2940 linesbundle : 336 KB
core8
| File | Role | Lines |
|---|---|---|
| 00_index.js | bootstrap + window.$bz wiring. | 447 |
| 01_signals.js | reactive primitives : signal / effect / computed. | 117 |
| 02_directives.js | the 13 bz-* directives + the binding engine. | 761 |
| 03_scope.js | bz-data component-scoped state, keyed by bz-id. | 268 |
| 04_persistence.js | storage adapters per ClientState instance. | 99 |
| 05_bridge.js | HTMX glue : signal snapshot out, <bz-patch> in. | 567 |
| 06_helpers.js | shared helpers for overlay-class components. | 531 |
| 06_locale.js | month and weekday names, derived from the language. | 150 |
component engine17
| File | Role | Lines |
|---|---|---|
| 07_calendar.js | <bz-calendar> custom element (stateful day grid). | 942 |
| 08_file_upload.js | $bz.fileUpload.makeScope factory for ui.file_upload. | 488 |
| 09_notification.js | toast stack manager (bz-data scope factory). | 137 |
| 10_charts.js | shared chart tooltip + legend scope factories. | 334 |
| 11_number_input.js | shared scope for the NumberInput component. | 112 |
| 12_slider.js | shared scope for the Slider component. | 122 |
| 13_select.js | shared scopes for the Select component. | 72 |
| 14_combobox.js | shared scopes for the Combobox component. | 204 |
| 15_pagination.js | the Pagination component's shared scope. | 135 |
| 16_accordion.js | the shared scopes of Accordion, Tree, Tabs, Stepper | 294 |
| 17_carousel.js | the Carousel component's shared scope. | 231 |
| 18_time_picker.js | the TimePicker component's shared scope. | 158 |
| 19_dnd.js | the node-DnD gesture shared by `dropzone` / `draggable`. | 721 |
| 20_resizable.js | the shared scope of the Resizable component (split panes). | 389 |
| 21_signature_pad.js | the SignaturePad component's shared scope. | 262 |
| 22_verbs.js | the CLIENT half of `bretzel.runtime.verbs`. | 126 |
| 23_diagram.js | the highlighting of neighbours, in `ui.diagram`. | 131 |
The directives
Grouped by what they guarantee. The vocabulary has two halves — a Python constant declares it, a JS module wires it — and the runtime cannot import Python. A gate already checks that at commit time (test_python_js_mirror.py); this table shows it, one notch stricter: comments stripped, and per source module.DOM structure4
| Written into the HTML | What it guarantees | Declared by |
|---|---|---|
| bz-data="{ open: false }" | Opens a local scope, keyed by bz-id — it SURVIVES the morph of a server refresh. Inside a scope method, write this.field. | BZ_DATA_PREFIX — component-local scope declaration |
| bz-for="v in liste :key=v.id :flip" | Keyed iteration over a single-root <template>. :key reuses the nodes; :flip animates the reflow of the rows that survive. | BZ_FOR_PREFIX — keyed client-side iteration |
| bz-if="expr" | Really mounts / unmounts the subtree. Every mount starts from a fresh tree. | BZ_IF_PREFIX — mount/unmount conditional |
| bz-teleport="body" | Projects the content elsewhere in the DOM; the scope stays resolved at the original place. Re-projects if the source changed. | BZ_TELEPORT_PREFIX — render at another DOM location |
reactive display4
| Written into the HTML | What it guarantees | Declared by |
|---|---|---|
| bz-attr:aria-expanded="open" | A one-way reactive attribute. false/null/undefined remove the attribute; on a native form field, the property is updated too (otherwise a morph would empty the input). | BZ_ATTR_PREFIX — generic one-way attribute bind |
| bz-class="{ actif: open }" | Adds / removes classes by truthiness. The static class= rendered by the server is a baseline that is never removable. | BZ_CLASS_PREFIX — class object/array bind |
| bz-show="expr" | Toggles display. The element stays mounted (hence stays in the DOM for focus, measurement, tests). | BZ_SHOW_PREFIX — display:none toggle (stays mounted) |
| bz-text="expr" | textContent follows the value. Display only. | BZ_TEXT_PREFIX — textContent (display-only) |
two-way input1
| Written into the HTML | What it guarantees | Declared by |
|---|---|---|
| bz-model="$bz.state.Form.default.email" | Two-way binding, form controls only. | BZ_MODEL_PREFIX — two-way binding on form inputs |
user gesture1
| Written into the HTML | What it guarantees | Declared by |
|---|---|---|
| bz-on:click="open = !open" | Listens for an event. An element marked aria-disabled STARTS no interaction: activation events are ignored, the others pass (closing stays possible). | BZ_ON_PREFIX — event handler (client-only) |
the node's lifecycle3
| Written into the HTML | What it guarantees | Declared by |
|---|---|---|
| bz-effect="void geom" | A continuous reactive effect: replayed on every mutation of a signal it reads. | BZ_EFFECT_PREFIX — continuous reactive effect |
| bz-init="setup()" | Once only, at the node's first mount — and not replayed by a re-bind after a morph. | BZ_INIT_PREFIX — one-shot on-mount hook |
| bz-ref="track" | Names the node in $refs. Registered across the whole tree BEFORE any binding, so a parent bz-init sees a child ref. | BZ_REF_PREFIX — named DOM reference |
Wiring order on a single elementref → model → attr → class → text → show → on → effect → initRead from the engine. It is not cosmetic: bz-ref is wired first (a neighbour can read it), bz-init last (the node is fully wired when it runs).
Inside an expression
A directive expression is compiled once then cached. The current scope is on the scope chain — a bare name reads the scope — and a few variables are injected.scope1
| Variable | Contents |
|---|---|
| $scope | The current bz-data scope. It is on the scope chain too, so a bare name reads it. |
the node2
| Variable | Contents |
|---|---|
| $el | The element carrying the directive. |
| $refs | The nodes named by bz-ref in this scope. |
the event2
| Variable | Contents |
|---|---|
| $event | The DOM event — inside a bz-on: only. |
| $value | The value written — inside a bz-model only. |
act2
| Variable | Contents |
|---|---|
| $dispatch | $dispatch(name, detail) — a CustomEvent bubbling up from $el. |
| $nextTick | $nextTick(fn) — after the signal flush, hence after the DOM has been rewritten. |
{ open: false, pick(v) { this.open = false; } }
The trap: the rebinding of bare names holds for inline expressions, not for a scope's methods. Inside a method it is `this.field` — without the `this`, you create a global.The $bz surface
The global object, read module by module. The scope factories are what a component spreads into its `bz-data`; the rest is the engine. The “exposes” column is read from the JS literal, never copied by hand.reactivity3
| Name | What it is | Exposes | Defined in |
|---|---|---|---|
| $bz.computed | computed(fn) → {get, peek, dispose}. A memoised derivation, recomputed when a dependency moves. | — | 01_signals.js |
| $bz.effect | effect(fn) → {dispose}. Runs straight away, runs again when a signal it read changes. The writes of a single tick are merged into one re-execution. | — | 01_signals.js |
| $bz.signal | signal(init) → {get, set, peek, subscribe}. get() subscribes the current effect, peek() reads without subscribing, set() triggers nothing if the value is identical. | — | 01_signals.js |
state & transport4
| Name | What it is | Exposes | Defined in |
|---|---|---|---|
| $bz.notify | Pushes a toast onto the notification stack. | — | 00_index.js |
| $bz.pending | pending(element|action_id, delay_ms) → the “an action is in flight” signal. An element walks up to the carrier of its hx-post; a string addresses the action from elsewhere in the page. The delay is what avoids the flash on short round trips. On the Python side: ``ui.pending()``. | — | 05_bridge.js |
| $bz.state | $bz.state.<Class>.<key>.<field> — the signal of a client-state field, materialised at boot from the envelope. | — | 00_index.js |
| $bz.version | The bundle's protocol version. | — | 00_index.js |
shared helpers5
| Name | What it is | Exposes | Defined in |
|---|---|---|---|
| $bz.helpers | The bricks shared by the overlays and by the pointer-drag family. | focusTrap, escapeKey, onWindow, pushUrl, urlParam, scrollLock, clickOutside, floating, prefersReducedMotion, emitChange, capturePointer, releasePointer | 06_helpers.js |
| $bz.locale | The month and day names, derived from ``<html lang>`` by ``Intl``. Python cannot produce them: its ``locale`` module is process-global state. | tag, monthNames, monthName, weekdayNames, weekdayLongNames | 06_locale.js |
| $bz.multiSelect | The membership algebra of a multiple selection, shared by Select and Combobox. | _picked, _isPicked, _hasPicked, _togglePick, _removeOne, _pick, _setValue, _clearAll | 06_helpers.js |
| $bz.num | Pure numeric primitives: precision implied by a step. | precisionFromStep, cachedPrecision | 06_helpers.js |
| $bz.verbs | The client half of the VERBS (``bretzel.copy`` …): a browser action triggered from an ``on_*=``. Only ``copy`` lives there — ``print`` and ``fullscreen`` fit in an expression Python writes out in full. It carries the fallback outside a secure context, without which an internal tool served over ``http://`` on a local IP would copy nothing, in silence. | share, vibrate, copy | 22_verbs.js |
gesture engine1
| Name | What it is | Exposes | Defined in |
|---|---|---|---|
| $bz.dnd | The Pointer Events drag-and-drop engine, shared by draggable and dropzone. | MOUSE_THRESHOLD_PX, TOUCH_HOLD_MS, TOUCH_TOLERANCE_PX, _state, _axisOf, _accepts, _canEnter, _indexOf | 19_dnd.js |
component scope18
| Name | What it is | Exposes | Defined in |
|---|---|---|---|
| $bz.accordion | A scope factory, spread into the component's bz-data. | single, multi | 16_accordion.js |
| $bz.carousel | A scope factory, spread into the component's bz-data. | scope | 17_carousel.js |
| $bz.charts | A scope factory, spread into the component's bz-data. | tooltipScope, lineScope, scatterScope, hide | 10_charts.js |
| $bz.combobox | A scope factory, spread into the component's bz-data. | optionOf, paintOptions, common, single, multi | 14_combobox.js |
| $bz.diagram | A scope factory, spread into the component's bz-data. | scope | 23_diagram.js |
| $bz.fileUpload | A scope factory, spread into the component's bz-data. | makeScope | 08_file_upload.js |
| $bz.notification | A scope factory, spread into the component's bz-data. | — | 09_notification.js |
| $bz.numberInput | A scope factory, spread into the component's bz-data. | scope | 11_number_input.js |
| $bz.pagination | A scope factory, spread into the component's bz-data. | scope | 15_pagination.js |
| $bz.resizable | A scope factory, spread into the component's bz-data. | scope | 20_resizable.js |
| $bz.select | A scope factory, spread into the component's bz-data. | single, multi | 13_select.js |
| $bz.signaturePad | A scope factory, spread into the component's bz-data. | scope | 21_signature_pad.js |
| $bz.slider | A scope factory, spread into the component's bz-data. | scope | 12_slider.js |
| $bz.stepper | A scope factory, spread into the component's bz-data. | scope | 16_accordion.js |
| $bz.tabs | A scope factory, spread into the component's bz-data. | scope | 16_accordion.js |
| $bz.time | A scope factory, spread into the component's bz-data. | scope, fill | 18_time_picker.js |
| $bz.tooltip | A scope factory, spread into the component's bz-data. | scope | 16_accordion.js |
| $bz.tree | A scope factory, spread into the component's bz-data. | scope | 16_accordion.js |