openvibers/openvibe.shared Commits

4.108.1: a page named after a domain keeps its Markdown twin in llms.txt (#284)

OP OpenVibers committed 10 days ago · f251634261 · 7 files, +30 −7

4.108.0 counted any dotted last segment as a file: Help's /sites/openvibe.blog and Host's /catalog/domains/co.uk kept
their HTML URLs while /sites/openvibe.community (an ending over eight letters) got its twin. Only a real file ending
makes a file now.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex b56a920..26ac7e0 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,13 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.108.1 — 2026-11-03
++
++- **A page named after a domain keeps its Markdown twin in llms.txt.** 4.108.0 counted any last segment with a dot as a
++ file, so Help's `/sites/openvibe.blog` and Host's `/catalog/domains/co.uk` kept their HTML URLs.
++ `/sites/openvibe.community` got its twin only because ".community" is longer than eight letters. Now only a real
++ file ending (json, xml, txt, png, html, …) makes a file. `test/llms-markdown-links.test.js`.
++
## 4.108.0 — 2026-11-03
- **llms.txt can link a site's own pages at their Markdown twins, and only those** (AGT-5). `seo.llmsTxt({ markdown:
ddiff --git a/README.md b/README.md
iindex b5a24f5..01314a7 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.108.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.108.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex d013c71..49a4020 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.108.0",
++ "version": "4.108.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/package-lock.json b/package-lock.json
iindex b19600c..38de591 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "4.108.0",
++ "version": "4.108.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "4.108.0",
++ "version": "4.108.1",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex a21432a..1bd7e27 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.108.0",
++ "version": "4.108.1",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/seo.js b/seo.js
iindex 8f7cfd6..7947a92 100644
---- a/seo.js
++++ b/seo.js
@@@ -276,9 +276,16 @@ function robotsTxt({ sitemaps = [], disallow = [], allow = ['/'], allowAI = true
return lines.join('\n') + '\n';
}
++// The endings that make a path a file rather than a page (4.108.1). Any dot used to count, so a page named after a
++// domain (Help's /sites/openvibe.blog, Host's /catalog/domains/co.uk) lost its twin, unless its ending was longer
++// than eight letters (/sites/openvibe.community kept it).
++const FILE_EXT = new Set(('txt md json jsonl xml rss atom csv tsv yaml yml toml ics vcf webmanifest html htm js mjs cjs css map wasm '
++ + 'png jpg jpeg gif webp avif svg ico bmp tif tiff heic pdf zip gz tgz tar bz2 xz 7z rar dmg exe msi deb rpm apk '
++ + 'mp3 mp4 m4a m4v webm ogg oga ogv wav flac mov avi mkv m3u8 ts srt vtt woff woff2 ttf otf eot sh ps1 py').split(' '));
++
/**
* Whether llms.txt's markdown mode links `url` at its Markdown twin: one of the site's own pages. A file (a last path
-- * segment with an extension: /openapi.json, /feed.xml, /llms-full.txt), an /api/ or /.well-known/ path and, when
++ * segment with a file ending: /openapi.json, /feed.xml, /llms-full.txt), an /api/ or /.well-known/ path and, when
* `base` is given, another origin's link keep their URL (4.108.0; before, /openapi.json became /openapi.json.md).
*/
function twinnable(url, base) {
@@@ -287,7 +294,9 @@ function twinnable(url, base) {
if (!/^https?:$/.test(u.protocol)) return false;
if (base && /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(String(url)) && u.origin !== new URL(base).origin) return false;
if (/^\/(api|\.well-known)(\/|$)/.test(u.pathname)) return false;
-- return !/\.[a-z0-9]{1,8}$/i.test(u.pathname.split('/').pop());
++ const last = u.pathname.split('/').pop();
++ const dot = last.lastIndexOf('.');
++ return dot < 0 || !FILE_EXT.has(last.slice(dot + 1).toLowerCase());
}
/**
ddiff --git a/test/llms-markdown-links.test.js b/test/llms-markdown-links.test.js
iindex c3b6fae..1421380 100644
---- a/test/llms-markdown-links.test.js
++++ b/test/llms-markdown-links.test.js
@@@ -36,4 +36,11 @@ const old = seo.llmsTxt({ name: 'X', markdown: true, sections: [{ title: 'P', li
assert.deepStrictEqual(links(old), ['https://example.com/index.md', 'https://example.com/openapi.json']);
// Without markdown nothing changes.
assert.deepStrictEqual(links(seo.llmsTxt({ name: 'X', sections: [{ title: 'P', links: [{ title: 'Hot', url: '/hot' }] }] })), ['/hot']);
++// 4.108.1: a page named after a domain is a page; only a real file ending makes a file.
++const domains = seo.llmsTxt({ name: 'Help', markdown: true, base: 'https://openvibe.help', sections: [{ title: 'Sites', links: [
++ { title: 'Blog', url: 'https://openvibe.help/sites/openvibe.blog' }, { title: 'Community', url: 'https://openvibe.help/sites/openvibe.community' },
++ { title: 'UK', url: '/catalog/domains/co.uk' }, { title: 'Data', url: '/export.CSV' }, { title: 'Old page', url: '/legacy/index.html' }, { title: 'v2.1', url: '/releases/v2.1' },
++] }] });
++assert.deepStrictEqual(links(domains), ['https://openvibe.help/sites/openvibe.blog.md', 'https://openvibe.help/sites/openvibe.community.md',
++ '/catalog/domains/co.uk.md', '/export.CSV', '/legacy/index.html', '/releases/v2.1.md']);
console.log('llms markdown links: ok');