{# P3-T03..T08 — per-step Validation Explorer. Replaces the P2 placeholder. Renders the per-endpoint matrix (library × proxy attempts), header reduce summary, cookie scenarios, and rate-limit probe data persisted in each ValidationRun row. Inputs (assembled by ``services.validation_view.build_validation_step_view``): * ``view.scan_id`` / ``view.scan_id_short`` / ``view.back_to_trace_url`` * ``view.step_index`` / ``view.step_index_padded`` / ``view.total_step_count`` * ``view.step_name`` / ``view.status_label`` / ``view.status_pill_class`` * ``view.duration`` / ``view.summary_sub_line`` * ``view.error_class`` / ``view.error_message`` * ``view.endpoints`` — list of ValidationEndpointView (per-endpoint table) * ``view.is_in_progress`` / ``view.is_cancelled_scan`` #} {% extends "admin_layout.html" %} {% from 'partials/help_tooltip.html' import tip, tip_styles %} {% from 'partials/notifications.html' import notify_styles %} {% block page_title %}admin :: step :: {{ view.scan_id_short }} / {{ view.step_index_padded }}{% endblock %} {% block breadcrumb %} admin/ scans/ {{ view.scan_id_short }}/ step {{ view.step_index_padded }} {% endblock %} {% block head_extra %} {{ tip_styles() }} {{ notify_styles() }} {% if view.is_in_progress %}{% endif %} {% endblock %} {% block content %} {# ============== Sticky breadcrumb strip ============== #}
← back to trace · step {{ view.step_index_padded }} of {{ view.total_step_count }} {{ tip("The Validation Explorer is a read-only inspector. Per-request replay (admin action) ships separately.", position="bottom") }}
{# ============== Eyebrow + headline ============== #}
02.1 — scan debug · step {{ view.step_index_padded }} · validation

Step {{ view.step_index_padded }} — {{ view.step_name }}

{{ view.summary_sub_line }}· {{ view.status_label }} {% if view.is_cancelled_scan %} · [cancelled scan] {% endif %}
{# ============== In-progress banner (P3-T23) ============== #} {% if view.is_in_progress %}
// in progress step {{ view.step_index_padded }} ({{ view.step_name }}) is still running — page auto-refreshes every 30s.
{% endif %} {# ============== Error banner (§4·g) ============== #} {% if view.error_class or view.error_message %} {% endif %} {# ============== ENDPOINTS card (P3-T04) ============== #}
endpoints {{ tip("One row per Bucket A/B endpoint validated on this scan. Click expand to inspect the per-request matrix.") }} {{ view.endpoints | length }} endpoint{{ '' if view.endpoints | length == 1 else 's' }}
{% if not view.endpoints %}
{{ view.empty_state_heading }}

{{ view.empty_state_body }}

{% else %}
{% for ep in view.endpoints %} {% endfor %}
# method · url {{ tip("HTTP method and URL template the endpoint was validated against.", position="bottom") }} bucket {{ tip("Validation bucket: A = primary data path, B = bootstrap / session, C = ignored.", position="bottom") }} best lib {{ tip("First library whose proxy ladder passed; '—' when every library was blocked.", position="bottom") }} headers req. {{ tip("Empirically required headers / total tested. Headers dropped without effect are 'optional'.", position="bottom") }} cookies {{ tip("Cheapest cookie scenario that worked. 'cold' = none needed; 'warmup' = homepage hit required; 'full' = full session cookies needed.", position="bottom") }} rate {{ tip("Estimated safe rate from the rate_limit_probe (1 / safe-delay). '—' when not measured.", position="bottom") }} status {{ tip("Endpoint validation status. complete = winning library found; blocked all = no library passed any tier.", position="bottom") }}
{{ ep.endpoint_id }} {{ ep.method }} {{ ep.url_template or ep.url }} {% if ep.bucket %} {{ ep.bucket }} {% else %} {% endif %} {{ ep.best_library or '—' }} {{ ep.headers_required_label }} {{ ep.cookies_label }} {{ ep.rate_limit_summary }} {{ ep.validation_status.replace('_', ' ') }}
{% endif %}
{% endblock %} {% block scripts %} {% endblock %}