{# 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. #245: the roster splits into two visual groups — verified ("Known speakers") and unverified ("Unnamed voices", collapsed by default). Heard-name annotation shows the machine-proposed name for unverified speakers. #} {% 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 or overview.possible_duplicates %}
TO DO {% if overview.unverified_active %} {{ overview.unverified_active_total }} voice{{ "s" if overview.unverified_active_total != 1 }} waiting for a name Review them {% endif %} {% if overview.name_suggestions %} {{ overview.name_suggestion_total }} name suggestion{{ "s" if overview.name_suggestion_total != 1 }} to review Review {% endif %} {% if overview.possible_duplicates %} {{ overview.possible_duplicates_total }} pair{{ "s" if overview.possible_duplicates_total != 1 }} of voices sound alike Compare {% for d in overview.possible_duplicates %} — {{ d.speaker_a_name }} & {{ d.speaker_b_name }}{% if not loop.last %}, {% endif %} {% endfor %} {% if overview.possible_duplicates_total > overview.possible_duplicates|length %} and {{ overview.possible_duplicates_total - overview.possible_duplicates|length }} more {% endif %} {% endif %}
{% endif %} {% if overview.rows %} {# Partition into verified (known) and unverified (unnamed) groups. #} {% set verified_rows = overview.rows|selectattr("aggregate.verified")|list %} {% set unverified_rows = overview.rows|rejectattr("aggregate.verified")|list %}
{% 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 %} {# --- Table row macro (reused by both groups) --- #} {% macro table_row(row) %}
{{ spk_badge(row) }} {{ row.entry.speaker.display_name }} {% if row.heard_name and not row.aggregate.verified %} "{{ row.heard_name }}?" heard name, unconfirmed {% endif %} {{ 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 %} {% if row.heard_name and not row.aggregate.verified %} Rule → {% else %} Open → {% endif %}
{% endmacro %} {# --- Card macro (reused by both groups) --- #} {% macro card_item(row) %}
  • {{ spk_badge(row) }}
    {{ row.entry.speaker.display_name }} {{ status_chip(row) }} {% if row.heard_name and not row.aggregate.verified %} "{{ row.heard_name }}?" heard name {% endif %}

    {{ row.aggregate.files }} file{{ "s" if row.aggregate.files != 1 }} · {{ format_duration(row.aggregate.seconds) }} · {% if row.aggregate.last_seen %}last heard {{ format_age(row.aggregate.last_seen, now=now) }}{% else %}never heard{% endif %}

    {{ match_pct(row) }}

  • {% endmacro %} {# --- Table header (shared between groups) --- #} {% macro table_header() %}
    NAME STATUS FILES MINUTES VOICE MATCH LAST HEARD
    {% endmacro %} {# === Known speakers (verified) === #} {% if verified_rows %}
    Known speakers ({{ verified_rows|length }})
    {% if view == "table" %}
    {{ table_header() }} {% for row in verified_rows %} {{ table_row(row) }} {% endfor %}
    {% else %} {% endif %}
    {% else %}

    No verified speakers yet. Name a voice during review to add someone here.

    {% endif %} {# === Unnamed voices (unverified, collapsed by default) === #} {% if unverified_rows %}
    Unnamed voices ({{ unverified_rows|length }}) {% if view == "table" %}
    {{ table_header() }} {% for row in unverified_rows %} {{ table_row(row) }} {% endfor %}
    {% else %} {% endif %}
    {% 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 %}