{# mirrorwall/components.html — the component macros three applications share (spec §4). Every macro is keyword-driven with defaults that make sense for any application: nothing here has a required parameter shaped by one product's data model, which is the specific failure mode this extraction exists to avoid. Accessibility is not a later pass. Each macro emits the ARIA its pattern requires — a labelled table, a `role="status"` toast region, a `aria-modal` dialog with a heading it is labelled by — and the accessibility tests assert those attributes rather than trusting them. #} {% macro button(label, variant="default", type="button", name=None, value=None, disabled=False, aria_label=None, extra="") -%} {%- endmacro %} {% macro badge(label, tone="neutral", title=None) -%} {{ label }} {%- endmacro %} {% macro input(name, label, value="", type="text", hint=None, error=None, required=False, placeholder=None) -%} {% set field_id = "mw-field-" ~ name %}
{% if error %}

{{ error }}

{% elif hint %}

{{ hint }}

{% endif %}
{%- endmacro %} {% macro select(name, label, options, selected=None, hint=None) -%} {% set field_id = "mw-field-" ~ name %}
{% if hint %}

{{ hint }}

{% endif %}
{%- endmacro %} {% macro checkbox(name, label, checked=False, kind="checkbox") -%} {% set field_id = "mw-field-" ~ name %}
{%- endmacro %} {% macro switch(name, label, checked=False) -%} {% set field_id = "mw-field-" ~ name %}
{%- endmacro %} {% macro card(label, value, note=None, kind="default") -%}
  • {{ label }} {{ value }} {% if note %}{{ note }}{% endif %}
  • {%- endmacro %} {% macro table(columns, rows, caption=None, table_id=None, sortable=False, complete=True, row_count=None, density=None) -%} {# `columns` is a sequence of mappings with a required `label` and optional `numeric`/`mono` flags; `rows` is a sequence of already-rendered cell values. Both flags are tested with `is defined` rather than read directly, because the environment uses StrictUndefined and an omitted optional key is an ordinary column, not a mistake. `complete` is not decoration. UI standards §5: a client-side sort must apply to the whole dataset, so a table holding one page of a longer result set declares `complete=False` and the table module refuses to wire a sort control onto it. `density="dense"` (design brief §5) drops the row height to 32 px and the header to the small label style, scoped to this table alone (tables.css); every other table on the page, and every table that does not pass it, is unaffected. `mono` (design brief §2) marks a column — a model ID, a digest, a timestamp — as data rather than prose, reusing the `.mono` utility already in layout.css rather than inventing a second way to say the same thing. #}
    {% if caption %}{{ caption }}{% endif %} {% for column in columns %} {{ column.label }} {% endfor %} {% for row in rows %} {% for cell in row %} {% set column = columns[loop.index0] %} {% set cell_classes = [] %} {% if column.numeric is defined and column.numeric %}{% set _ = cell_classes.append('numeric') %}{% endif %} {% if column.mono is defined and column.mono %}{% set _ = cell_classes.append('mono') %}{% endif %} {{ cell }} {% endfor %} {% endfor %}
    {% if row_count is not none %}

    {{ row_count }} row{{ '' if row_count == 1 else 's' }}

    {% endif %} {%- endmacro %} {% macro empty_state(message, action_label=None, action_href=None) -%}

    {{ message }}

    {% if action_label and action_href %}

    {{ action_label }}

    {% endif %}
    {%- endmacro %} {% macro filter_bar(action="", method="get") -%} {%- endmacro %} {% macro kv_list(items) -%} {#- An item may carry an optional `href`, making its value an anchor. The value and label stay escaped text either way — never HTML — and the href goes through `safe_href`, which refuses javascript: and every other unsafe scheme; a refused href renders as the plain value. -#}
    {% for item in items %}
    {{ item.label }}
    {% set resolved_href = (item.href | safe_href) if (item.href is defined and item.href) else none %} {% if resolved_href is not none %}
    {{ item.value }}
    {% else %}
    {{ item.value }}
    {% endif %} {% endfor %}
    {%- endmacro %} {% macro json_viewer(payload, label="Details", open=False) -%} {{ label }}
    {{ payload | json_pretty }}
    {%- endmacro %} {% macro progress(value, maximum=100, label="Progress") -%} {{ value }} / {{ maximum }} {%- endmacro %} {% macro tabs(items, selected=None, label="Sections") -%}
    {% for item in items %} {% endfor %}
    {%- endmacro %} {% macro drawer(drawer_id, title, open=False) -%} {%- endmacro %} {% macro dialog(dialog_id, title) -%}

    {{ title }}

    {{ caller() }}
    {%- endmacro %} {% macro toast_region() -%}
    {%- endmacro %} {% macro tooltip(text, description) -%} {{ text }} {%- endmacro %} {% macro pagination(previous_href=None, next_href=None, summary=None) -%} {%- endmacro %} {% macro chart_container(chart_id, title, description) -%} {# The chart is decoration; the caller's table alternative carries the same figures, so a reader who cannot see the chart loses nothing (UI standards §5, §7). #}
    {{ title }}

    {{ description }}

    {{ caller() }}
    {%- endmacro %} {% macro telemetry_bar(stream_url="", meters=None) -%} {% with telemetry_stream_url = stream_url, telemetry_meters = meters %}{% include "mirrorwall/telemetry_bar.html" %}{% endwith %} {%- endmacro %} {#- The five macros below are new in 0.3, for the WeightRoomGym design brief (§5): a status vocabulary, a top-bar app tab, a telemetry meter, a bounded live log, and a sectioned left menu. Every one of them is additive — nothing above this line changed shape — and every required parameter is a generic presentation word, same as the rest of this file (`tests/test_no_application_vocabulary.py::test_no_component_macro_has_an_application_shaped_required_parameter`). -#} {% macro status_dot(status="unknown", label=None) -%} {#- `status` is one of ok/degraded/stopped/unknown (design brief §3) — a vocabulary distinct from the queued/running/failed one `badge` already carries, because a status dot describes whether a *component* answers, not whether a *run* finished. The dot alone carries the colour; the word is the page's ordinary text, so a dot with a word it cannot show is still fully described. -#} {{ label if label is not none else status }} {%- endmacro %} {% macro app_tab(label, href, status=None, selected=False) -%} {%- if status %}{{ status_dot(status, label="") }}{% endif %}{{ label }} {%- endmacro %} {% macro meter(label, value_text, percent=None) -%} {#- `percent` is optional because a meter can be fed a measurement this machine cannot take (UNSUPPORTED, ADR-0016): `role="meter"` requires a real `aria-valuenow`, so a meter with no percent renders its label and value only — never a bar claiming to show a number it does not have, and never a bare 0 someone could average (UI standards §5). -#} {{ label }} {% if percent is not none %} {% endif %} {{ value_text }} {%- endmacro %} {% macro log_pane(pane_id, stream_url=None, max_lines=500, label="Log") -%} {#- Swaps are htmx, behaviour is `log_pane.js` (ADR-0128 rule 6): the bounded buffer, the *dropped N lines* frame and the pause button all live in the module; this markup is what it operates on, plus the initial, JavaScript-free state (ADR-0020 rule 5). -#}
    {{ label }}
    {%- endmacro %} {% macro side_nav(sections, footer=None, label="Sections") -%} {#- A section's pages are keyed `links`, not `items`: a plain dict's `.items` resolves to the built-in method before Jinja falls back to key lookup, so a template that wrote `section.items` would silently iterate `dict.items` instead of the caller's list the first time a caller passed a real dict rather than a lucky namespace object. -#} {%- endmacro %}