{% extends "base.html" %} {% block title %}Job Board — Job Cannon{% endblock %} {# ============================================================================ jobs/index.html — Job Board page (redesign) ============================================================================ DESIGN DECISION #2 — "filter bar streamlined, NOTHING deleted": The old bar exposed every control at once (a full-width row of ~14 status pills + a second row of 9 more controls). The redesign TIERS it: TIER 1 (always visible): Status multiselect dropdown · Posted-within · Sort + direction · a "Filters" disclosure button. TIER 2 (inside the Filters popover): Location, Country, Workplace, Review-needed, the two Biz-day freshness toggles, Hide stale, Show hidden, and the From/To date range. EVERY input is still present, with its ORIGINAL `name` attribute and HTMX wiring — they just moved into popovers. The status pills + their toggle JS (status-pill-cb / status-pill-label / toggleAllStatus) are kept VERBATIM, only relocated into the Status dropdown. localStorage persistence + all the filter-form contracts below are unchanged. PRESERVED contracts (load-bearing — keep exact): • #filter-form: hx-get jobs.table, hx-target #job-table-body, hx-trigger "change from:select, change from:input, input from:#filter-date-from, input from:#filter-date-to", hx-swap innerHTML. • tbody#job-table-body: jobs-updated / sse:jobs-changed live refresh, hx-include #filter-form. • All filter input `name`s: status, location, country, workplace_type, unresolved, posted_within, freshness, sort_by, sort_dir, hide_stale, show_hidden, date_from, date_to. • The whole {% block extra_scripts %} (re-sort, archive, freshness, persistence, collapse scroll-back) is carried over unchanged except for two added popover toggles. #} {% block content %} {# Computed server-side: how many active statuses, and how many TIER-2 advanced filters are set (drives the two count badges). DESIGN DECISION: counts live in the template, not JS, so they're correct on first paint. #} {% set active_statuses = filters.getlist('status') if filters.getlist is defined else ([filters.get('status')] if filters.get('status') else []) %} {% set adv_active_count = ( (1 if filters.get('location') else 0) + (1 if filters.get('country') else 0) + (1 if filters.get('workplace_type') else 0) + (1 if filters.get('unresolved', 'hide') != 'hide' else 0) + (1 if filters.get('freshness') else 0) + (1 if filters.get('hide_stale', 'on') == 'on' else 0) + (1 if filters.get('show_hidden') == 'on' else 0) + (1 if filters.get('date_from') else 0) + (1 if filters.get('date_to') else 0) ) %}

Job Board

Triage by fit — expand a row to decide, open full detail to dig in.

{{ jobs | length }} shown
{# No-provider banner (WP3) — compact mirror of the dashboard quick-actions banner. Full-page render only (lives outside the #job-table-body fragment swap, so a table refresh can never make it vanish or appear stale). #} {% if not scoring_available and unscored_count > 0 %}
{{ unscored_count }} job{{ '' if unscored_count == 1 else 's' }} not yet AI-scored — no AI provider is configured. Set up a provider
{% endif %}
{# DESIGN: header labels 11px uppercase tracking-wider slate-500. The "what is 6–30?" legend now lives on this Score (i) tooltip instead of re-explaining on every row. #} {# Live-refresh — UNCHANGED. innerHTML swap collapses any open accordion; acceptable given background events are infrequent (see original note). #} {% include "jobs/_table.html" %}
Score Title Company Location Salary Posted Status
{% if archived_count > 0 %}
{% endif %}
{% endblock %} {% block extra_scripts %} {% endblock %}