diff --git a/booth/app.py b/booth/app.py index 8937cfb..a0311e7 100644 --- a/booth/app.py +++ b/booth/app.py @@ -261,29 +261,50 @@ def human_dur(seconds: float) -> str: _MONTHS = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") +# NEVER RAISE: the Desk renders every row in ONE response, and a clock a fleet +# session can set (content mtimes) or a filesystem can report (birth time) may +# lie outside the calendar's range — which raised through the template and +# 500'd the index for every booth (heid bug-hunt, 4/4). A date that cannot be +# rendered is "" and the macro renders nothing, as for an unknown birth time. +_BAD_DATE = (OverflowError, OSError, ValueError) + + def date_iso(epoch: float) -> str: - return datetime.fromtimestamp(epoch, tz=timezone.utc).isoformat(timespec="seconds") + try: + return datetime.fromtimestamp(epoch, tz=timezone.utc).isoformat(timespec="seconds") + except _BAD_DATE: + return "" def date_stamp(epoch: float) -> str: - return time.strftime("%Y-%m-%d %H:%M", time.localtime(epoch)) + try: + return time.strftime("%Y-%m-%d %H:%M", time.localtime(epoch)) + except _BAD_DATE: + return "" def date_day(epoch: float, now: float | None = None) -> str: - """'12 Sep', with the year only when it is not this year's.""" - t, n = time.localtime(epoch), time.localtime(time.time() if now is None else now) + """'12 Sep', with the year only when it is not this year's; '' when the + calendar cannot hold it.""" + try: + t, n = time.localtime(epoch), time.localtime(time.time() if now is None else now) + except _BAD_DATE: + return "" day = f"{t.tm_mday} {_MONTHS[t.tm_mon - 1]}" return day if t.tm_year == n.tm_year else f"{day} {t.tm_year}" def date_ago(seconds: float) -> str: - """An age in its largest whole unit: 'just now', '12m ago', '5h ago', '3d ago'.""" + """An age in its largest whole unit: 'just now', '12m ago', '5h ago', + '1d ago'. A day is 24h (hours used to run on to 47h, so '1d ago' never + appeared). The caller never passes a clock AHEAD of now: that is said as a + date, not an age.""" s = int(seconds) if s < 60: return "just now" if s < 3600: return f"{s // 60}m ago" - if s < 2 * 86400: + if s < 86400: return f"{s // 3600}h ago" return f"{s // 86400}d ago" diff --git a/booth/static/embed.js b/booth/static/embed.js index 24da2c7..15d2f36 100644 --- a/booth/static/embed.js +++ b/booth/static/embed.js @@ -348,12 +348,19 @@ choice (the same localStorage key the Booth's toggle writes — same origin). Absent or unreadable = follow the OS, as before. The host page's own is the author's and is never touched. */ + var ours = []; // every element this script mounted function bkTheme() { var t = null; try { t = localStorage.getItem("booth.theme"); } catch (e) {} var forced = t === "light" || t === "dark"; - Array.prototype.forEach.call(document.querySelectorAll(".bk-ask"), function (el) { - if (forced) el.setAttribute("data-bk-theme", t); else el.removeAttribute("data-bk-theme"); + /* OUR fragments only (heid bug-hunt): an author's own `.bk-ask` in the + host page is theirs, and is never marked. */ + ours.forEach(function (root) { + var els = [root].concat(Array.prototype.slice.call(root.querySelectorAll(".bk-ask"))); + els.forEach(function (el) { + if (!el.classList.contains("bk-ask")) return; + if (forced) el.setAttribute("data-bk-theme", t); else el.removeAttribute("data-bk-theme"); + }); }); } /* A choice made in another tab moves an open report live. */ @@ -376,6 +383,8 @@ if (!data) return; favicon(data.favicon); var mounted = place(data.marks || []); + ours = []; + Object.keys(mounted).forEach(function (id) { ours = ours.concat(mounted[id]); }); bkTheme(); reassociate(); asksChip(data.open || [], mounted); diff --git a/booth/templates/_dates.html b/booth/templates/_dates.html index efbcb79..0dd92bf 100644 --- a/booth/templates/_dates.html +++ b/booth/templates/_dates.html @@ -1,11 +1,21 @@ {# r2b D1b — a booth's two dates, defined ONCE for the Desk row and the booth header. Created is the day it began (the filesystem's birth time); updated is how recently its CONTENT moved (`landed_at`, the clock "new since you - looked" reads). Each is a