{# Shared component macros. Keep this lean: only the primitives that recur across pages live here. Page-specific blocks stay in pages. #} {% macro stat_card(label, value, icon_name=None, footer=None) %}
{% if icon_name %}{{ icon(icon_name) }}{% endif %} {{ label }}
{{ value }}
{% endmacro %} {% macro pill(text, tone='neutral', icon_name=None, dot=False) %} {% set tone_class = { 'neutral': '', 'success': ' is-success', 'warning': ' is-warning', 'danger': ' is-danger', 'info': ' is-info', 'accent': ' is-accent', }[tone] %} {% if icon_name %}{{ icon(icon_name) }}{% endif %} {{ text }} {% endmacro %} {% macro panel(title=None, subtitle=None, icon_name=None) %}
{% if title %}

{% if icon_name %}{{ icon(icon_name) }}{% endif %} {{ title }}

{% if subtitle %}

{{ subtitle }}

{% endif %}
{% if caller and caller.__name__ == 'panel_actions' %}{{ caller() }}{% endif %}
{% endif %} {{ caller() }}
{% endmacro %} {% macro empty_state(title, body=None, icon_name='sparkles') %}
{{ icon(icon_name) }}
{{ title }}
{% if body %}
{{ body }}
{% endif %}
{% endmacro %} {# Per-page "What's this?" expander. ``key`` must be unique per page — the dismissal preference is keyed on it (localStorage). ``title`` is the button label; the caller block is the panel body and may contain any HTML. First-time visitors see the panel open; once collapsed, the dismissal sticks until the operator opens it again. Usage: {% call info_toggle(key='tasks', title="What is a task?") %}

Tasks are coordination units...

{% endcall %} #} {% macro info_toggle(key, title="What's this?") %}
{{ caller() }}
{% endmacro %} {% macro notice(text, tone='info', icon_name=None) %} {% set tone_class = { 'success': 'is-success', 'warning': 'is-warning', 'danger': 'is-danger', 'info': 'is-info', }[tone] %} {% set default_icon = { 'success': 'check-circle', 'warning': 'alert-triangle', 'danger': 'alert-triangle', 'info': 'info', }[tone] %}
{{ icon(icon_name or default_icon) }}
{{ text }}
{% endmacro %} {# Status → pill tone map. Covers the vocabularies seen across sessions, tasks, decisions, handoffs, patterns, tools, routes, workspaces, and recurring tasks. Unknown statuses fall through to the neutral tone — never break the page on a new status. #} {% set _STATUS_TONE = { 'active': 'success', 'open': 'info', 'available': 'success', 'ready': 'success', 'approved': 'success', 'resolved': 'success', 'success': 'success', 'ok': 'success', 'idle': 'neutral', 'cleared': 'neutral', 'closed': 'neutral', 'archived': 'neutral', 'done': 'neutral', 'completed': 'success', 'picked_up': 'warning', 'picked': 'warning', 'awaiting': 'warning', 'awaiting_human': 'warning', 'pending': 'warning', 'in_progress': 'info', 'running': 'info', 'stale': 'warning', 'zombie': 'danger', 'reaped': 'danger', 'expired': 'danger', 'failed': 'danger', 'error': 'danger', 'missing': 'danger', 'denied': 'danger', 'unverified': 'warning', 'verified': 'success', 'dirty': 'warning', 'clean': 'success', } %} {% macro status_pill(status, dot=True) %} {% set key = (status|string|lower|replace(' ', '_')|replace('-', '_')) %} {% set tone = _STATUS_TONE.get(key, 'neutral') %} {{ pill(status, tone=tone, dot=dot) }} {% endmacro %} {# Humanised timestamp with the exact ISO one hover away. Falsy inputs render as a dim em-dash so empty columns don't dent the row rhythm. #} {% macro relative_time(ts, fallback='—') %} {% set rel = ts|relative_time %} {% if rel %} {{ rel }} {% else %} {{ fallback }} {% endif %} {% endmacro %}