{# 02 / SCRAPING PLAN -- step-by-step actions, one card per FlowSegmentation.Action. Replaces the v0.1.0 endpoints_table.html. Each card lists the click target + the Bucket A calls triggered by that click, plus copy-pasteable curl + httpx snippets that thread ``recommended_headers`` from the validation pass. T13: when ``report.skipped_filter`` is True (the user picked "Continue with generic report" on the post-capture confirmation step), we replace the action-card layout with a flat v0.1.x-style endpoint table -- there's no Bucket A segmentation to render against, so listing every captured endpoint is the most useful surface. Inline styles (styles.css is byte-pinned to the mockup). All colour tokens come from the existing CSS variables so light + dark themes render identically. #}
02 / SCRAPING PLAN
{% if report.skipped_filter %} {# T13: generic fallback. Render every captured endpoint in a flat table -- no Bucket A / B segmentation, no per-action grouping. The header copy makes the trade-off explicit so users understand why this layout differs from the standard rich one. #}

Captured endpoints

{% set endpoints = report.analysis_result.endpoints %} {% if endpoints %}

{{ endpoints | length }} endpoint{{ '' if (endpoints | length) == 1 else 's' }} captured. Generic-report mode skipped intent filtering, so every API call recorded during the capture appears below.

{% for ep in endpoints %} {% endfor %}
Method URL template Response
{{ ep.method or 'GET' }} {{ ep.url_template }} {{ ep.response_summary or '' }}
{% else %}

No endpoints were captured during this scan.

{% endif %} {% else %} {% set actions = report.analysis_result.flow_segmentation.actions %} {% set bg_requests = report.analysis_result.flow_segmentation.background_requests %} {# Safety-net banner: when intent filtering produced zero Bucket A calls across every action, the per-step "No Bucket A data calls were tied to this action" line repeats on every card and silently hides the root cause. Hoist that signal to the top so the user sees it before scrolling. #} {% set has_any_bucket_a = namespace(found=false) %} {% for action in actions %} {% if bucket_a_calls_in_action(report, action.requests) %}{% set has_any_bucket_a.found = true %}{% endif %} {% endfor %} {% if not has_any_bucket_a.found %} {% endif %}

Step-by-step actions

{% if actions %}

{{ actions | length }} user action{{ '' if (actions | length) == 1 else 's' }} captured. Each card shows the click and the data calls it triggered, with ready-to-paste curl + Python snippets.

{% else %}

No click-driven actions were segmented from this capture. The site may render data on initial load only -- inspect the prerequisites + evidence sections below.

{% endif %} {% for action in actions %} {% set bucket_a_calls = bucket_a_calls_in_action(report, action.requests) %}
Step {{ loop.index }}: {{ action.label or 'User action' }}
{% if action.started_at %} t+{{ '%.2f' | format(action.started_at) }}s {% endif %}
{% if action.click_target %}
{%- if action.click_target.get('tag') -%} <{{ action.click_target.get('tag') }}> {%- endif %} {% if action.click_target.get('text') %} "{{ action.click_target.get('text') }}" {% endif %} {% if action.click_target.get('href') %} → href={{ action.click_target.get('href') }} {% endif %}
{% endif %} {% if bucket_a_calls %}
Data calls triggered ({{ bucket_a_calls | length }})
{% for call in bucket_a_calls %} {% set tab_id = 'snippet-' ~ loop.index0 ~ '-' ~ loop.index %} {% set parent_idx = loop.index0 %}
{{ call.method }} {{ call.url }} {# T34.9: replay outcome badge per Bucket A call. Renderer pre-computed ``call.replay_status`` against ``report.replay_per_endpoint``. #} {% if call.replay_status == 'matched' %} ✔ replay matched {% elif call.replay_status == 'mismatch' %} ⚠ replay mismatch {% else %} · not in replay sample {% endif %}
{% if call.response_summary %}
Response shape: {{ call.response_summary }}
{% endif %}
Show curl + Python snippets {# T34.8: tabbed code block. Both panes are present in the DOM so existing tests that grep for ``curl`` / ``httpx`` keep passing; the inactive pane is ``hidden``. A tiny inline ``onclick`` swaps the active tab (no JS framework). #}
{{ call.url | curl_snippet(call.method, call.headers, call.cookies, call.header_values, report.target_domain) }}
{% endfor %} {% else %}
No Bucket A data calls were tied to this action. {% if action.requests %} ({{ action.requests | length }} request{{ '' if (action.requests | length) == 1 else 's' }} fired but classified as bootstrap or noise.) {% endif %}
{% endif %}
{% endfor %} {% if bg_requests %}
Background requests ({{ bg_requests | length }})

XHR / fetch calls not tied to any user click -- typically analytics, lazy-loaded chunks, or polling endpoints.

    {% for url in bg_requests[:25] %}
  • {{ url }}
  • {% endfor %}
{% if (bg_requests | length) > 25 %}

and {{ (bg_requests | length) - 25 }} more not shown.

{% endif %}
{% endif %} {% endif %}