{% extends "base.html" %} {% block title %}Dashboard — Job Cannon{% endblock %} {% block content %}

Dashboard

At-a-glance overview
{# F1 — Inbox wiring banner: shows only when status has been RED for ≥7 days #} {% if inbox_banner %}
Inbox wiring: {{ inbox_banner.summary }} {{ inbox_banner.reason }}
Review in Settings
{% endif %} {# ── Stat Cards (incl. budget banner) — live-update ── #} {# Two refresh sources, no polling: #} {# • dashboard-refresh (from:body) — fired by sync/batch-scoring/scan #} {# completion in THIS tab (see _BATCH_HX_TRIGGER in batch_scoring). #} {# • sse:* — pushed by the /events stream when a background scheduler job #} {# mutates the underlying data (see job_finder/web/live_events.py). #} {# Covers every datum on the cards: counts (jobs), today's cost (costs), #} {# in-review (pipeline), pending-review (detections). #}
{% include "dashboard/_stats_cards.html" %}

Quick Actions

{# Dynamic actions — auto-refresh on dashboard-refresh. 5s delay gives #} {# the backend time to finalize session state (status='done', counts). #} {# class="contents" makes the wrapper layout-transparent so its children #} {# participate in the parent flex row. #}
{% include "dashboard/_quick_actions.html" %}
{# Static action — does not need to refresh on dashboard-refresh. #}

Add job from listing

Paste the job posting URL. Optional fields override or supplement what we fetch from the page.

{# ── Parser Health widget — live-update ── #} {# Re-fetches on dashboard-refresh (this tab's sync/scoring runs) and on #} {# sse:jobs-changed so a source newly flagged DEGRADED surfaces without a #} {# full page reload. Inline render on full page load avoids a layout shift. #}
{% include "dashboard/_degraded_sources.html" %}
{# ── Heal Activity panel (autoheal Phase D / D5) ── #} {# Server-rendered on full page load only: heal events are post-ingestion / #} {# daily-sweep cadence, so a live refetch hook would be noise. #}
{% include "dashboard/_heal_activity.html" %}
{# The container refetches on sse:detections-changed so detections surfaced #} {# by the background pipeline-detection job appear live. It deliberately #} {# does NOT listen to dashboard-refresh: user confirm/dismiss has its own #} {# card-level swap + "confirmed!" animation (see blueprints/detections.py), #} {# and a competing innerHTML refetch would blow that animation away. The #} {# fragment also OOB-refreshes the header badge below. #}
{% include "dashboard/_pipeline_review_header.html" %}
{% include "dashboard/_review_queue.html" %}
{# ── History block (pipeline summary + activity tables) — live-update ── #} {# Re-fetches on dashboard-refresh (this tab's sync/scoring/scan runs) and #} {# on the SSE events whose data these tables surface — no polling. Partial #} {# is shared with the full-page render above and dashboard.history_fragment. #}
{% include "dashboard/_dashboard_history.html" %}
{% endblock %}