convey: canonical relative-time helper (Python + JS) and audited rollout
Add `relative_time(seconds)` to `convey/utils.py` and a JS counterpart at
`convey/static/relative-time.js` exposing `window.relativeTime(ms)`. Both
implement the canon ladder from Founder UX review T4 (≤60s seconds; ≤1h
minutes; ≤1d hours; ≤1w days; ≤4w weeks; 4w–2mo "1 month"; ≥2mo months).
Convert the audited rolled-up read sites — observer last-seen, support
ticket ages, stats recency, home routine last-run, paired device ages,
status_pane wsLastMessageRaw, health connection-health indicators — to
route through the canonical helpers. Delete now-redundant per-file
helpers (`formatTimeAgo`, `fmtRelativeTime`, `timeAgo`). The existing
`time_since(epoch)` is preserved as a thin wrapper around
`relative_time`; the `apps/link/call.py` `_relative_time(iso)` wrapper
likewise preserves its public signature and "never" / parse-fallback
semantics, but drops the non-canon "just now" tier.
Live-operation duration counters (active agent / import elapsed,
ws uptime, app.js notification helper, status_pane "connected for X"
strings) are explicitly out of scope and untouched.
Test plan:
- `.venv/bin/pytest tests/test_convey_utils.py -v` — 5 passed (existing
`test_time_since` plus new `test_relative_time` covering full canon
table including 28-day and 60-day boundary triplets).
- `.venv/bin/ruff check .` and `.venv/bin/ruff format --check .` pass.
- `scripts/check_layer_hygiene.py` and `scripts/gate_agents_rename.py`
pass.
- `grep -rn 'formatTimeAgo|fmtRelativeTime|\btimeAgo\b' apps/ convey/`
is empty.
- Manual JS test page at `convey/static/tests/relative-time.html`
mirrors the canon table for the JS helper.
Note: `make ci` blocked by pre-existing `onnxruntime` import in
`observe.transcribe.overlap` — unrelated to this change. Component
gates run directly all pass.