From b2ccc1d777e9de3041e82a0c4ec01bb46ddabbc9 Mon Sep 17 00:00:00 2001 From: lszoszk Date: Tue, 13 Oct 2026 23:54:55 +0200 Subject: [PATCH] Update log under About > Methodology, and a footer chip with the last data update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Footer: "Data updated 13 Oct 2026 · +8 new" — the later of the API build date and the newest log entry, and the documents added in the last 30 days. It opens the log. - About > Methodology: "Update log", a scrollable list, newest first, of every document added since 13 October 2026 (date, symbol linking to the reader, title, body) and every document reviewed and not added, with the reason. Read from docs/updates/log.json, which the pipeline writes with the feeds. - app.js / app.css ?v=20261013-log. Co-Authored-By: Claude Opus 5.5 --- docs/assets/app.css | 21 ++++++++++ docs/assets/app.js | 52 +++++++++++++++++++++++++ docs/feeds/index.json | 2 +- docs/index.html | 14 ++++++- docs/updates/log.json | 89 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 docs/updates/log.json diff --git a/docs/assets/app.css b/docs/assets/app.css index 80674d1d..a3c2ce84 100644 --- a/docs/assets/app.css +++ b/docs/assets/app.css @@ -7191,3 +7191,24 @@ mark.ck ins { color: var(--status-good, #2f6b3a); text-decoration: none; font-st border: 1px solid var(--rule, #ddd); border-radius: 999px; font-size: 11.5px; color: var(--ink-3); cursor: help; } .data-asof::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .55; } + +/* Update log (About › Methodology) and its footer chip (paintUpdateLog) */ +.foot-updated { + display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px; border: 1px solid var(--rule); + border-radius: 999px; color: var(--ink-2); text-decoration: none; font-size: 11px; white-space: nowrap; +} +.foot-updated::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--garnet); opacity: .7; } +.foot-updated:hover, .foot-updated:focus-visible { border-color: var(--garnet); color: var(--ink); } +.update-log { + list-style: none; margin: 8px 0 16px; padding: 0; max-height: 320px; overflow-y: auto; + border: 1px solid var(--rule); border-radius: 4px; font-size: 13px; +} +.update-log-row { display: grid; grid-template-columns: 92px 78px 1fr; gap: 10px; padding: 7px 10px; border-bottom: 1px solid var(--rule); } +.update-log-row:last-child { border-bottom: 0; } +.update-log-row time { color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; } +.update-log-status { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--garnet); } +.update-log-row.is-not-added .update-log-status { color: var(--ink-3); } +.update-log-note { display: block; color: var(--ink-3); font-size: 12px; margin-top: 2px; } +@media (max-width: 640px) { + .update-log-row { grid-template-columns: 1fr; gap: 2px; } +} diff --git a/docs/assets/app.js b/docs/assets/app.js index cd1849c5..a2533ee8 100644 --- a/docs/assets/app.js +++ b/docs/assets/app.js @@ -275,6 +275,7 @@ async function pingApi() { state.apiStats = r; paintApiBadge(true, ms); paintDataAsOf(); + paintUpdateLog(); return; } catch (e) { console.warn(`[unhrdb-api] unreachable (${timeoutMs} ms budget):`, e.message); @@ -1495,6 +1496,55 @@ function mergeJurFacets(base) { }; } +// ─────────── Update log (About › Methodology) and the footer chip ─────────── +// docs/updates/log.json is written by the pipeline (build_feeds.py): every document added since the log +// started, and the ones reviewed and left out, with the reason. The footer chip says when the data last +// changed and how many documents arrived in the last 30 days; it opens the log. +const _logDay = d => new Date(d + (d.length === 10 ? 'T00:00:00Z' : '')).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); +let _updateLog = null; +async function paintUpdateLog() { + try { + if (!_updateLog) { + const r = await fetch('updates/log.json', { cache: 'no-cache' }); + if (!r.ok) return; + _updateLog = await r.json(); + } + } catch (e) { return; } + const log = _updateLog, entries = log.entries || []; + const since = $('#update-log-since'); + if (since && log.since) since.textContent = _logDay(log.since); + const list = $('#update-log-list'); + if (list) { + list.innerHTML = entries.length ? entries.map(e => { + const added = e.status === 'added'; + const sym = added && e.docId + ? `${escape(e.symbol || e.docId)}` + : `${escape(e.symbol)}`; + return `
  • ` + + `${added ? 'Added' : 'Not added'}` + + `${sym} · ${escape(e.title)}${e.body ? ` — ${escape(e.body)}` : ''}` + + `${e.note ? `${escape(e.note)}` : ''}
  • `; + }).join('') : '
  • No changes since the log started.
  • '; + } + const chip = $('#foot-updated'); + if (chip) { + const built = state.apiStats?.manifest?.builtAt || state.manifest?.builtAt || ''; + const latest = [built.slice(0, 10), entries[0]?.date || ''].sort().pop(); + const cutoff = new Date(Date.now() - 30 * 864e5).toISOString().slice(0, 10); + const recent = entries.filter(e => e.status === 'added' && e.date >= cutoff).length; + if (latest) { + chip.textContent = `Data updated ${_logDay(latest)}` + (recent ? ` · +${recent} new` : ''); + chip.title = (recent ? `${recent} ${recent === 1 ? 'document' : 'documents'} added in the last 30 days. ` : '') + 'Open the update log.'; + chip.hidden = false; + } + } +} +function openUpdateLog(ev) { + ev.preventDefault(); + window.location.hash = 'about'; + setTimeout(() => $('#update-log')?.scrollIntoView({ block: 'start', behavior: 'smooth' }), 60); +} + // ─────────── "Data as of" chip under the Source list ─────────── // When the corpus behind the selected source last changed. UNHRDB: the live API's build date (the static // manifest when the API is offline). Recommendations: the newest UHRI recommendation and the last check of @@ -1561,6 +1611,8 @@ function paintMastFolio(m) { if (q) q.placeholder = `Search ${totParas} paragraphs across ${totDocs} UN documents…`; paintFreshnessCard(m); paintDataAsOf(); + paintUpdateLog(); + $('#foot-updated')?.addEventListener('click', openUpdateLog); } // Freshness card — green/amber/red traffic light on the About tab. diff --git a/docs/feeds/index.json b/docs/feeds/index.json index feb1098d..d77c8f53 100644 --- a/docs/feeds/index.json +++ b/docs/feeds/index.json @@ -1,5 +1,5 @@ { - "builtAt": "2026-10-13T21:25:20+00:00", + "builtAt": "2026-10-13T21:48:29+00:00", "feeds": [ { "id": "all", diff --git a/docs/index.html b/docs/index.html index 270f30b1..cd02dcb4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -156,7 +156,7 @@ - + + · App by Łukasz Szoszkiewicz & Zuzanna Kowalska. · @@ -1753,7 +1763,7 @@

    What did you spot?

    - + diff --git a/docs/updates/log.json b/docs/updates/log.json new file mode 100644 index 00000000..f5b59305 --- /dev/null +++ b/docs/updates/log.json @@ -0,0 +1,89 @@ +{ + "since": "2026-10-13", + "builtAt": "2026-10-13T21:48:29+00:00", + "added": 8, + "entries": [ + { + "date": "2026-10-13", + "status": "added", + "type": "gc", + "docId": "cerd-c-gc-40", + "symbol": "CERD/C/GC/40", + "title": "General Recommendation No. 40 (2026) on reparatory justice for the harms and continuing consequences of colonialism, the trafficking in enslaved Africans across the Atlantic and other routes, and racialized chattel slavery", + "body": "Committee on the Elimination of Racial Discrimination" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-hrc-62-48", + "symbol": "A/HRC/62/48", + "title": "Women's and girls' rights and artificial intelligence and related digital technologies", + "body": "WG Discrimination against Women and Girls" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-81-133", + "symbol": "A/81/133", + "title": "Violence against older women", + "body": "SR Violence against Women" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-81-132", + "symbol": "A/81/132", + "title": "Strategic vision, thematic priorities and methods of work of the Special Rapporteur", + "body": "SR Sale of Children" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-81-130", + "symbol": "A/81/130", + "title": "Emerging technologies and transitional justice", + "body": "SR Truth Justice and Reparation" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-81-128", + "symbol": "A/81/128", + "title": "The psychosocial impact of colour-based discrimination on the enjoyment of human rights by persons with albinism", + "body": "IE Albinism" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-80-341", + "symbol": "A/80/341", + "title": "Threats to freedom of expression online in turbulent times", + "body": "SR Freedom of Expression" + }, + { + "date": "2026-10-13", + "status": "added", + "type": "sp", + "docId": "a-80-139", + "symbol": "A/80/139", + "title": "Access delayed, rights denied: bottlenecks in the leprosy drug supply chain and their daily impact on affected persons", + "body": "SR Leprosy" + }, + { + "date": "2026-10-13", + "status": "not-added", + "type": "sp", + "docId": null, + "symbol": "A/80/115", + "title": "Reports of the Working Group on discrimination against women and girls — note by the Secretariat", + "body": "WG Discrimination against Women and Girls", + "note": "A note of the Secretariat that points to the Working Group's reports to the Human Rights Council; the reports themselves are in the database." + } + ] +}