{% extends "base.html" %} {% block title %}{{ run.workflow }} — run {{ run.id[:8] }}{% endblock %} {% block content %}

← Back to workflows

{{ run.workflow }} run {{ run.id[:12] }} {# Copy-report button — copies the full report text (the
 contents) to the clipboard. Lives in the H1 so it
       sits near the run identity, where users naturally look when
       they want to share/save a report. Disabled gracefully when
       navigator.clipboard is unavailable. #}
    
  

{{ run.status }} {% if run.exit_code is not none %} exit code {{ run.exit_code }} {% endif %} {% if run.started_at %} started {{ run.started_at }} {% endif %} {% if run.path %} scope {{ run.path }} {% endif %}

{# data-exclude-run-id filters this strip down to OTHER runs of the same workflow. Without it, the current run would appear as one of its own "Recent" siblings (P1-5 in the 2026-05-14 QA punch list). #} {# Phase 5 — structured recommendation cards. Workflows emit ATTUNE_REC stdout lines; the runner parses, validates, and broadcasts them on the recommendation SSE channel. run_view.js renders each payload as an action card here. #}
{# Phase 3.3 — live progress for discovery-sweep. Hidden unless the workflow name matches; run_view.js populates the per-source state from ATTUNE_DS lines on the SSE stream. The static markup orders sources alphabetically so the layout stays stable; the JS only flips status without moving DOM nodes. #} {% if run.workflow == "discovery-sweep" %} {% endif %} {# When stream_url is empty, this run was loaded from disk (older than the runner's in-memory ring buffer). Server pre-fills the log here so the page renders without SSE — run_view.js detects the empty stream_url and skips EventSource creation. #}
{% for line in server_rendered_lines %}{{ line }}
{% endfor %}

{% if stream_url %} This page reconnects to the run's stream automatically — refresh anytime to see the latest output. Buffered output replays for new subscribers, so you won't lose history. {% else %} This run was loaded from disk; the log shown above is its final captured output. Live streaming is not attached because the run is no longer in the runner's in-memory ring buffer. {% endif %}

{% endblock %} {% block scripts %} {# Server-injected configuration for run_view.js. Using a tagged JSON block avoids global-namespace pollution and is unambiguously data (not a script). #} {# runner.js provides the shared setupRecentRuns helper that run_view.js calls. Loaded BEFORE run_view.js so the helper is available when run_view.js initializes. #} {% endblock %}