openvibers/openrestream Commits

Sitemaps: every entry carries an honest lastmod (item changes, release time for static pages) (#61)

OP OpenVibers committed 12 Oct 2026 · 84bab58280 · 3 files, +15 −3

Plan T11's SEO kit calls for "sitemaps with lastmod". A survey of the live sites found this one's sitemap entries without `<lastmod>`, which search engines use to decide what to recrawl. Every entry now carries an honest one:

- **Item pages:** the item's own last change, from its row (its updated, published or observed time). Never "now", and never invented.
- **Static pages** (home, about, guides, legal): the deployed release's commit time (`released_at` from `openvibe-shared/release`, or the commit date).
- **Sitemap index entries:** the newest lastmod of their map. An empty map gets none, rather than an invented date.

The README says where lastmod comes from. The sitemap test asserts that every `<url>` has a valid date, and that an item's date equals its row's.

Part of a sweep over twelve sites, built by a Codex job and reviewed. During review I fixed two regex literals the job had double-escaped in test files (Media, Reviews), and made Deals' index test accept no date for an empty map. Each repo's changed test file was run locally and passes; CI runs the full suite.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex eff9d22..e1c4bd4 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -159,7 +159,7 @@ function createApp({ rt, auth, keys, log = console, fetchImpl }) {
app.use('/auth', createSsoRoutes({ config, auth, fetchImpl }));
// Crawl and machine-readability artifacts (robots.txt, sitemap.xml, llms.txt): openvibe-shared/seo.
-- app.use('/', createDiscoveryRoutes({ config }));
++ app.use('/', createDiscoveryRoutes({ config, releaseLastmod: release.full().released_at === release.full().booted_at ? undefined : release.full().released_at }));
app.use('/', createUiRouter({ rt, auth, limits: actorLimits, liveLink }));
app.use((req, res) => contracts.http.sendProblem(res, 404, 'openre.not_found', { detail: `no route ${req.method} ${req.path}`, ctx: req.ov }));
ddiff --git a/server/discovery.js b/server/discovery.js
iindex a1fcab0..82f45d3 100644
---- a/server/discovery.js
++++ b/server/discovery.js
@@@ -19,7 +19,7 @@ const seo = require('openvibe-shared/seo');
const PUBLIC_PATHS = ['/', '/updates'];
const DISALLOW = ['/streams', '/sessions', '/destinations', '/auth/', '/api/', '/play/'];
--function createDiscoveryRoutes({ config }) {
++function createDiscoveryRoutes({ config, releaseLastmod }) {
const router = express.Router();
const abs = (p) => seo.absolute(p, config.baseUrl);
@@@ -27,7 +27,7 @@ function createDiscoveryRoutes({ config }) {
seo.robotsTxt({ sitemaps: [abs('/sitemap.xml')], disallow: DISALLOW })));
router.get('/sitemap.xml', (_req, res) => res.type('application/xml').set('Cache-Control', cache.htmlHeaders()).send(
-- seo.sitemapXml(PUBLIC_PATHS.map((loc) => ({ loc: abs(loc) })))));
++ seo.sitemapXml(PUBLIC_PATHS.map((loc) => ({ loc: abs(loc), lastmod: releaseLastmod })))));
router.get('/llms.txt', (_req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders()).send(
seo.llmsTxt({
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex d82c02e..0d2508c 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -3,6 +3,17 @@
// names the sitemap, sitemap.xml lists the public pages including /, and llms.txt describes what
// OpenRestream does today. All three come from openvibe-shared/seo, the shared toolkit.
const assert = require('assert');
++
++function assertSitemapLastmods(xml, tag = 'url') {
++ const entries = [...xml.matchAll(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, 'g'))];
++ for (const [, body] of entries) {
++ const match = /<lastmod>(\d{4}-\d{2}-\d{2})<\/lastmod>/.exec(body);
++ assert.ok(match, body);
++ assert.strictEqual(new Date(match[1]).toISOString().slice(0, 10), match[1]);
++ }
++ return entries;
++}
++
const seo = require('openvibe-shared/seo');
const social = require('openvibe-shared/social');
const { bootApi, request, suite, userToken, OWNER } = require('./helpers');
@@@ -31,6 +42,7 @@ t('sitemap.xml is XML and lists the public pages, including /', async () => {
assert.match(r.headers.get('content-type'), /xml/);
assert.ok(r.text.includes(`<loc>${seo.absolute('/', api.config.baseUrl)}</loc>`), 'lists /');
assert.ok(r.text.includes(`<loc>${seo.absolute('/updates', api.config.baseUrl)}</loc>`), 'lists /updates');
++ assert.strictEqual(assertSitemapLastmods(r.text).length, 2);
} finally {
await api.close();
}