@polycode-projects/the-mechanical-code-talker 6.0.16 → 6.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; indexes a repo on request (tmct index) or reads any producer's graph.",
|
|
@@ -260,16 +260,24 @@ async function fetchFeedFormat(record, gate, { format, now }) {
|
|
|
260
260
|
* carries `wikibaseItem` when the article names one, past normalizeFeedItems
|
|
261
261
|
* (which only knows the snapshot's own fixed fields) so the news
|
|
262
262
|
* enrichment loop can short-circuit straight to the Wikidata KB source with
|
|
263
|
-
* the Q-id already in hand, no lookup needed.
|
|
263
|
+
* the Q-id already in hand, no lookup needed.
|
|
264
|
+
*
|
|
265
|
+
* Neither `news` nor `mostread` carries a per-article timestamp in the raw
|
|
266
|
+
* payload — the only date this feed exposes is the UTC calendar day the
|
|
267
|
+
* request itself names (the `/YYYY/MM/DD/` path Wikimedia selected these
|
|
268
|
+
* articles for), so `publishedAt` is stamped from whichever day's page
|
|
269
|
+
* actually answered (the primary day, or the prior day on a 404 retry),
|
|
270
|
+
* never from the fetch's own clock. */
|
|
264
271
|
async function fetchWikimediaFeed(record, gate, { now }) {
|
|
265
|
-
|
|
266
|
-
let body = await pacedFetchJson(gate,
|
|
272
|
+
let feedDay = now;
|
|
273
|
+
let body = await pacedFetchJson(gate, wikimediaFeedUrl(record.url, feedDay));
|
|
267
274
|
if (body === null) {
|
|
268
275
|
// A 404 means the day's page is not yet published; retry once against
|
|
269
276
|
// the previous UTC day before giving up.
|
|
270
277
|
const prevDay = new Date(now);
|
|
271
278
|
prevDay.setUTCDate(prevDay.getUTCDate() - 1);
|
|
272
|
-
|
|
279
|
+
feedDay = prevDay.toISOString();
|
|
280
|
+
body = await pacedFetchJson(gate, wikimediaFeedUrl(record.url, feedDay));
|
|
273
281
|
if (body === null) return null;
|
|
274
282
|
}
|
|
275
283
|
if (isNotModified(body)) return { items: [], bytes: 0, notModified: true };
|
|
@@ -280,12 +288,14 @@ async function fetchWikimediaFeed(record, gate, { now }) {
|
|
|
280
288
|
? body.mostread.articles
|
|
281
289
|
: [];
|
|
282
290
|
|
|
291
|
+
const { yyyy, mm, dd } = utcDateParts(feedDay);
|
|
292
|
+
const feedDayIso = `${yyyy}-${mm}-${dd}T00:00:00.000Z`;
|
|
283
293
|
const raw = articles.map((a) => ({
|
|
284
294
|
guid: a?.wikibase_item || a?.normalizedtitle || a?.title || "",
|
|
285
295
|
title: stripMarkup(a?.normalizedtitle || a?.displaytitle || a?.title || ""),
|
|
286
296
|
url: a?.content_urls?.desktop?.page || "",
|
|
287
297
|
summary: stripMarkup(a?.extract || ""),
|
|
288
|
-
publishedAt:
|
|
298
|
+
publishedAt: feedDayIso,
|
|
289
299
|
wikibaseItem: a?.wikibase_item || "",
|
|
290
300
|
}));
|
|
291
301
|
|
package/src/domain/news-feed.mjs
CHANGED
|
@@ -548,7 +548,11 @@ function collectSources(subgraphRows, sourcesByFactId) {
|
|
|
548
548
|
const key = src.url || src.title || "";
|
|
549
549
|
if (!key || seen.has(key)) continue;
|
|
550
550
|
seen.add(key);
|
|
551
|
-
|
|
551
|
+
const entry = { title: src.title || "", url: src.url || "", name: src.name || "" };
|
|
552
|
+
// Carried through only when the source snapshot actually has one — a
|
|
553
|
+
// card for an undated snapshot shows no date rather than a blank field.
|
|
554
|
+
if (src.publishedAt) entry.publishedAt = src.publishedAt;
|
|
555
|
+
sources.push(entry);
|
|
552
556
|
}
|
|
553
557
|
return sources;
|
|
554
558
|
}
|
|
@@ -657,8 +661,9 @@ export function renderNewsParagraph(hub, subgraphRows, { reportedIds = null } =
|
|
|
657
661
|
|
|
658
662
|
/** newsworthyHubs -> one item per hub (PLAN_NEWS_FEED.md section 6.6),
|
|
659
663
|
* paragraph included, sorted builtAt desc then id asc. `sourcesByFactId`
|
|
660
|
-
* maps fact ids to snapshot source links ({ title, url, name
|
|
661
|
-
*
|
|
664
|
+
* maps fact ids to snapshot source links ({ title, url, name, publishedAt?
|
|
665
|
+
* }); publishedAt is present only when the source snapshot carried one.
|
|
666
|
+
* The gate (PLAN_NEWS_FEED.md section 17): `reportedRows` replaces `newsWindowRows`
|
|
662
667
|
* and `newsworthyHubs` replaces `scoreHubs` as this function's own inputs —
|
|
663
668
|
* both keep their prior behaviour for every other caller. Each item's
|
|
664
669
|
* two-hop sub-graph then splits into its own `reported`/`background` rows,
|
|
@@ -151,6 +151,7 @@ ${THEME_TOKENS_CSS}
|
|
|
151
151
|
.item .newtag { font-family: ${MONO_STACK}; font-size: .64rem; color: var(--taught); margin-left: .5rem; }
|
|
152
152
|
.item .paragraph { margin: .4rem 0; }
|
|
153
153
|
.item .sources-links { font-size: .74rem; color: var(--muted); }
|
|
154
|
+
.item .sourcedate { font-family: ${MONO_STACK}; }
|
|
154
155
|
.item details.facts summary { font-family: ${MONO_STACK}; font-size: .68rem; color: var(--corpus); cursor: pointer; }
|
|
155
156
|
.item details.background summary { font-family: ${MONO_STACK}; font-size: .68rem; color: var(--muted); cursor: pointer; }
|
|
156
157
|
.item details.background p { margin: .35rem 0 0; color: var(--muted); }
|
|
@@ -482,12 +483,29 @@ ${NEWS_STYLE}
|
|
|
482
483
|
});
|
|
483
484
|
}
|
|
484
485
|
|
|
486
|
+
// The earliest publication date among a card's sources — a lexicographic
|
|
487
|
+
// min over ISO-8601 UTC strings sorts chronologically without parsing, and
|
|
488
|
+
// "earliest" reads as when the reported event actually happened rather
|
|
489
|
+
// than whichever source snapshot the subgraph walk reached last. A source
|
|
490
|
+
// with no publishedAt (its feed never carries one) never contributes here,
|
|
491
|
+
// so a card built entirely from undated sources shows no date at all.
|
|
492
|
+
function earliestSourceDate(sources) {
|
|
493
|
+
let earliest = null;
|
|
494
|
+
for (const s of sources || []) {
|
|
495
|
+
if (!s.publishedAt) continue;
|
|
496
|
+
if (earliest === null || s.publishedAt < earliest) earliest = s.publishedAt;
|
|
497
|
+
}
|
|
498
|
+
return earliest;
|
|
499
|
+
}
|
|
500
|
+
|
|
485
501
|
function cardHtml(item) {
|
|
486
502
|
const factLines = item.factLines || [];
|
|
487
503
|
const factsHtml = factLines.map(function (line) { return '<div class="factrow">' + esc(line) + '</div>'; }).join("");
|
|
488
504
|
const moreCount = (item.factCount || 0) - factLines.length;
|
|
489
505
|
const moreHtml = moreCount > 0 ? '<div class="factrow factmore">…and ' + moreCount + ' more</div>' : "";
|
|
490
506
|
const sourcesText = (item.sources || []).map(function (s) { return esc(s.title || s.url || ""); }).filter(Boolean).join(", ");
|
|
507
|
+
const sourceDate = earliestSourceDate(item.sources);
|
|
508
|
+
const dateText = sourceDate ? ' <span class="sourcedate">(' + esc(sourceDate.slice(0, 10)) + ')</span>' : "";
|
|
491
509
|
const background = item.backgroundParagraph
|
|
492
510
|
? '<details class="background"><summary>what the graph already knew</summary><p>' + esc(item.backgroundParagraph) + '</p></details>'
|
|
493
511
|
: "";
|
|
@@ -496,7 +514,7 @@ ${NEWS_STYLE}
|
|
|
496
514
|
+ '<span class="hub">' + esc(item.hub) + '</span><span class="tier">' + esc(item.tier || "unranked") + '</span>' + newTag
|
|
497
515
|
+ '<p class="paragraph">' + esc(item.paragraph) + '</p>'
|
|
498
516
|
+ background
|
|
499
|
-
+ (sourcesText ? '<p class="sources-links">sources: ' + sourcesText + '</p>' : "")
|
|
517
|
+
+ (sourcesText ? '<p class="sources-links">sources: ' + sourcesText + dateText + '</p>' : "")
|
|
500
518
|
+ '<details class="facts"><summary>' + (item.factCount || 0) + ' fact' + (item.factCount === 1 ? "" : "s") + '</summary>' + factsHtml + moreHtml + '</details>'
|
|
501
519
|
+ '</div>';
|
|
502
520
|
}
|
package/src/services/news.mjs
CHANGED
|
@@ -288,6 +288,10 @@ function buildSourcesByFactId(items) {
|
|
|
288
288
|
for (const snap of items || []) {
|
|
289
289
|
const record = recordsById.get(snap.sourceId);
|
|
290
290
|
const src = { title: snap.title || "", url: snap.url || "", name: record?.name || snap.sourceId || "" };
|
|
291
|
+
// A snapshot with no publication timestamp (a source whose own feed never
|
|
292
|
+
// carries one) leaves the key off entirely — never an invented or blank
|
|
293
|
+
// date, and never the fetch's own clock standing in for it.
|
|
294
|
+
if (snap.publishedAt) src.publishedAt = snap.publishedAt;
|
|
291
295
|
for (const factId of snap.factIds || []) map.set(factId, src);
|
|
292
296
|
}
|
|
293
297
|
return map;
|