{# =========================================================================== jobs/_row_expanded.html — TRIAGE TIER (tier 1 of 2) =========================================================================== Inserted into the placeholder when a compact row is clicked (GET /jobs//expand → outerHTML swap of nextElementSibling). DESIGN DECISION #1 — "two expansion tiers, distinct (no content overlap)": The OLD accordion crammed EVERYTHING in here (full JD, full AI analysis incl. talking points + resume skills, all sources, every action). The redesign splits triage from deep-dive: • THIS tier answers ONE question — "is this worth my time?" — and shows only: low-fit nudge · fit breakdown · tight strengths/gaps (≤4 each) · a decision rail (status + apply + overflow) · reference dates. • The DEEP DIVE (jobs/_row_detail.html, reached via "View Full Detail") owns the full JD, full AI analysis (+ talking points + resume skills), pipeline timeline, notes, and all sources. Talking points, resume skills, JD body, timeline, notes, source list were all MOVED OUT of this file ON PURPOSE — they are NOT deleted, they live one tier down. Do not re-add them here. PRESERVED behaviors / contracts (keep exact): • "View Full Detail" -> hx-get jobs.detail_inline, target closest tr, outerHTML. • "Re-score" -> hx-post jobs.rescore, target closest tr, outerHTML (its response carries the OOB score-cell swap that drives the client re-sort — unchanged). • "Archive" -> hx-post jobs.update_status hx-vals pipeline_status=archived, target #status-cell-, hx-on::after-request="archiveRow(this)". • "Collapse" -> hx-get jobs.collapse, target closest tr, outerHTML. • Unscored job -> the JD paste/save/score form is KEPT (a job can only be triaged once scored) — see the {% else %} branch. #} {% import "components/_score_macros.html" as sm %} {% set composite = sm.composite(job.sub_scores_json) | int %} {% set scored = job.sub_scores_json is not none and job.sub_scores_json != '' %} {% set sub_scores = job.sub_scores_json | from_json if job.sub_scores_json else {} %} {% set analysis = job.fit_analysis | from_json if job.fit_analysis else {} %} {# Band — same 3-band split as the score chip (⚑ see _score_cell.html FLAG). #} {% set band = 'success' if composite >= 24 else ('warning' if composite >= 17 else 'error') %} {# Apply target — apply_url_for() (Jinja global) enforces the precedence: strict direct_url (verified company posting) > first source_url (aggregator). #} {% set apply_url = apply_url_for(job) %} {% set first_seen_date = (job.first_seen | local_date) if job.first_seen else "—" %} {% set last_seen_date = (job.last_seen | local_date) if job.last_seen else "—" %} {% set _pd_approx = job.posted_date and job.posted_date_precision in ('approximate', 'proxy') %} {% set posted_date = (('~' if _pd_approx else '') ~ (job.posted_date | local_date)) if job.posted_date else "—" %} {% set jd_key_safe = job.dedup_key | urlencode | replace('%', '') %} {# DESIGN DECISION: the expanded panel sits on slate-850 (one plane lighter than the table's slate-800) so the open row reads as a distinct surface. #} {# Error banner (from paste_jd / rescore) — KEPT. #} {% if error %}
{{ error }}
{% endif %} {# Low-fit nudge — DESIGN DECISION: now driven by the SCORE BAND (weak = ≤16), not job.classification, so the triage signal matches the chip the user just saw. Only shown for scored + weak rows. #} {% if scored and band == 'error' %}

Low fit ({{ composite }}/30). Likely a skip — scan the gaps below before spending time here.

{% endif %}
{# ===================== LEFT — "the read" ===================== #}
{% if scored %} {# Fit breakdown — 6 sub-scores as thin tracks. DESIGN DECISION: 3 fill colours (≥4 emerald / =3 amber / ≤2 red); the old extra orange-for-2 tier was dropped to match the chip's 3-band language. #}

Fit breakdown

{% for key, label in [ ('title_fit','Title fit'), ('location_fit','Location'), ('comp_fit','Compensation'), ('domain_match','Domain'), ('seniority_match','Seniority'), ('skills_match','Skills') ] %} {% set v = sub_scores.get(key, 0) | int %} {% set fill = 'bg-emerald-500' if v >= 4 else ('bg-amber-500' if v == 3 else 'bg-red-500') %}
{{ label }} {{ v }}
{% endfor %}
{# Strengths / Gaps — DESIGN DECISION: capped at 4 each (triage glance). Talking points + resume skills intentionally NOT here (deep-dive tier). #} {% if analysis.strengths or analysis.gaps %}

Strengths

    {% for s in (analysis.strengths or [])[:4] %}
  • +{{ s }}
  • {% endfor %}

Gaps

    {% for g in (analysis.gaps or [])[:4] %}
  • {{ g }}
  • {% endfor %}
{% endif %} {% else %} {# ---- UNSCORED branch — KEPT from the original accordion ---- A row can't be triaged until it's scored, so the JD paste/save/score affordance lives here (NOT deleted). Routes + targets preserved: save_jd / paste_jd -> target "closest tr", outerHTML. #}

Not scored yet

Paste the job description to score this listing, then expand again to triage it.

{% endif %}
{# ===================== RIGHT — "the decision rail" ===================== #}