{# Console 2.0 speakers overview (#159, R4 #213) — full-page include and the htmx post-mutation refresh target (#roster). Numbers come from effective resolution (the canonical-run fold), never raw machine proposals. Every mutating form's action carries the current ?sort/?view so its re-render preserves both. Operator refusals (RosterError) arrive as roster_error. #} {% from "fragments/_chips.html" import chip %} {% set qs = "sort=" ~ sort ~ "&view=" ~ view %} {% if roster_error %}{% endif %} {# Mutation CSRF tokens: the overview's action forms live on the detail page, but the route handler re-renders this fragment after mutations, so the tokens must survive here for htmx round-trips. #} {# TO DO strip (#159 R4): action reminders the operator needs to address. #} {% if overview.unverified_active or overview.name_suggestions %}
TO DO {% if overview.unverified_active %} {{ overview.unverified_active_total }} voice{{ "s" if overview.unverified_active_total != 1 }} waiting for a name {% endif %} {% if overview.name_suggestions %} {{ overview.name_suggestion_total }} name suggestion{{ "s" if overview.name_suggestion_total != 1 }} to review {% endif %}
{% endif %} {% if overview.rows %}
{% macro spk_badge(row) %} {%- set name = row.entry.speaker.display_name -%} {%- set initials = name[:2]|upper if name else "?" -%} {{ initials }} {%- endmacro %} {% macro match_pct(row) %} {%- if row.tier.tier -%} {%- set evidence = row.tier.evidence -%} {%- set best = evidence|map(attribute="similarity")|select("number")|list -%} {%- if best -%} {%- set pct = (best|max * 100)|round|int -%} {{ pct }}% {%- else -%} {%- endif -%} {%- elif row.tier.has_voice_evidence -%} {%- else -%} {%- endif -%} {% endmacro %} {% macro status_chip(row) %} {%- if row.aggregate.verified -%} {{ chip("verified") }} {%- elif row.aggregate.files > 0 -%} {{ chip("needs you") }} {%- else -%} {{ chip("unknown") }} {%- endif -%} {% endmacro %} {% if view == "table" %}
NAME STATUS FILES MINUTES VOICE MATCH LAST HEARD
{% for row in overview.rows %}
{{ spk_badge(row) }} {{ row.entry.speaker.display_name }} {{ status_chip(row) }} {{ row.aggregate.files }} {{ format_duration(row.aggregate.seconds) }} {{ match_pct(row) }} {% if row.aggregate.last_seen %} {{ format_age(row.aggregate.last_seen, now=now) }} {% else %}never{% endif %} Open →
{% endfor %}
{% else %} {% endif %} {% else %}

No speakers yet. Voices join the roster when you name them while reviewing a recording.

{% endif %} {% if overview.inactive %}
Former speakers ({{ overview.inactive|length }}) {% for entry in overview.inactive %}

{{ entry.speaker.display_name }} — {% if entry.merged_into_name %} merged into {{ entry.merged_into_name }} {% else %} archived

{% endif %}

{% endfor %}
{% endif %}