openvibers/openvibe.design Commits
Serve the Markdown twins llms.txt links for fonts, makers and forms (#197)
Changes
@@@ -12,7 +12,7 @@ const express = require('express');const agentLayer = require('openvibe-shared/agent-layer');// /developers (the theme API) is on the same page router: an agent's most useful page here (11-03).--const TWIN_PATH_RE = /^\/(?:arena|brand|components|developers|kit|loading|look|motion|studio|tokens|t\/[A-Za-z0-9_-]+)?$/;++const TWIN_PATH_RE = /^\/(?:arena|brand|components|developers|fonts(?:\/make)?|forms|kit|loading|look|motion|studio|tokens|vectors\/make|t\/[A-Za-z0-9_-]+)?$/;/** A response the page code can write to; the first body written is the page's HTML. */function mockRes() {@@@ -285,15 +285,28 @@ async function createApp(opts = {}) {app.use('/api/v1/brand', require('./http/brand-api').createBrandApi(ctx));app.get('/api/v1/prompts', (req, res) => { res.set('Cache-Control', 'public, max-age=300'); res.json({ prompts: require('./views/prompts').list() }); });app.use(createIconRoutes(ctx));++ // The public page routers are built before they are mounted: the Markdown twins render through the same routers++ // (server/agent-twins.js), so a twin cannot drift from the page a browser gets.++ const { createTypefaceRoutes, createVectorRoutes } = require('./http/typefaces');++ const typefaceRoutes = createTypefaceRoutes(ctx);++ const vectorRoutes = createVectorRoutes(ctx);++ const fontRoutes = createFontRoutes(ctx);++ const pageRoutes = createPageRoutes(ctx);++ const twinRouter = express.Router();++ twinRouter.use(typefaceRoutes);++ twinRouter.use(vectorRoutes);++ twinRouter.use(fontRoutes);++ twinRouter.use(pageRoutes);++ // The Markdown twins of the public pages, and the head link and Link: header that name them (SEO-1,++ // server/agent-twins.js): a twin is the page rendered signed out. Mounted before every page router so a++ // page that answers 200 names its own twin.++ require('./agent-twins').mountTwins(app, { router: twinRouter, descriptor: agentDescriptor, baseUrl: ctx.config.baseUrl });// The typefaces and vectors a person makes with the model (plan R1 item 8): the makers, the// specimens and the API for agents. These come before the font library's /fonts/:family, so// /fonts/make is the maker and not a family called "make".-- {-- const { createTypefaceRoutes, createVectorRoutes } = require('./http/typefaces');-- app.use(createTypefaceRoutes(ctx));-- app.use(createVectorRoutes(ctx));-- }-- app.use(createFontRoutes(ctx));++ app.use(typefaceRoutes);++ app.use(vectorRoutes);++ app.use(fontRoutes);app.use('/api', createApi(ctx));// GET /openapi.json (SEO-3): the API as OpenAPI 3.1. It is not in openvibe-contracts, so the document is read from// the routes this app mounts under /api/, with the summaries the API files' header comments give them@@@ -319,10 +332,6 @@ async function createApp(opts = {}) {app.use('/tokens', createTokenFileRoutes(ctx));// ── Pages ───────────────────────────────────────────────-- const pageRoutes = createPageRoutes(ctx);-- // The Markdown twins of the public pages, and the head link and Link: header that name them (SEO-1,-- // server/agent-twins.js): a twin is the page rendered signed out.-- require('./agent-twins').mountTwins(app, { router: pageRoutes, descriptor: agentDescriptor, baseUrl: ctx.config.baseUrl });app.use(pageRoutes);// eslint-disable-next-line no-unused-vars@@@ -37,6 +37,27 @@ const PAGES = ['/', '/brand', '/components', '/tokens', '/motion', '/kit', '/devassert.ok(!/<(?:script|style)\b/i.test(prose) && twin.text.length > 150, `${twinPath} is the page's words`);}});++ // The font library, the typeface and vector makers, and the form controls are mounted by their own routers;++ // a twin still renders through the same page code, signed out, and cannot drift from the page.++ const MORE_PAGES = [++ ['/fonts', 'Find a face for your idea.'],++ ['/fonts/make', 'Describe a typeface.'],++ ['/forms', 'Controls that look like OpenVibe, on every site'],++ ['/vectors/make', 'Describe a drawing.'],++ ];++ await check('the font, typeface, form and vector pages have twins that name the page heading', async () => {++ for (const [p, heading] of MORE_PAGES) {++ assert.ok(TWIN_PATH_RE.test(p), `${p} is a twin path`);++ const page = await t.get(p);++ assert.strictEqual(page.status, 200, p);++ const twinPath = `${p}.md`;++ assert.ok(page.text.includes(`<link rel="alternate" type="text/markdown" href="${origin}${twinPath}">`), `${p} names its twin in the head`);++ const twin = await t.get(twinPath);++ assert.strictEqual(twin.status, 200, twinPath);++ assert.match(String(twin.headers.get('content-type') || ''), /^text\/markdown/, `${twinPath} is Markdown`);++ assert.ok(twin.text.includes(`# ${heading}`), `${twinPath} names the page heading "${heading}"`);++ }++ });await check('a path with no twin answers a Markdown 404, and a missing page names no twin', async () => {const twin = await t.get('/no-such-page-here.md');assert.strictEqual(twin.status, 404);
@@ -12,7 +12,7 @@ const express = require('express');
@@ -12,7 +12,7 @@ const express = require('express');
const agentLayer = require('openvibe-shared/agent-layer');
const agentLayer = require('openvibe-shared/agent-layer');
// /developers (the theme API) is on the same page router: an agent's most useful page here (11-03).
// /developers (the theme API) is on the same page router: an agent's most useful page here (11-03).
-const TWIN_PATH_RE = /^\/(?:arena|brand|components|developers|kit|loading|look|motion|studio|tokens|t\/[A-Za-z0-9_-]+)?$/;
+const TWIN_PATH_RE = /^\/(?:arena|brand|components|developers|fonts(?:\/make)?|forms|kit|loading|look|motion|studio|tokens|vectors\/make|t\/[A-Za-z0-9_-]+)?$/;
/** A response the page code can write to; the first body written is the page's HTML. */
/** A response the page code can write to; the first body written is the page's HTML. */
function mockRes() {
function mockRes() {
@@ -285,15 +285,28 @@ async function createApp(opts = {}) {
@@ -285,15 +285,28 @@ async function createApp(opts = {}) {
app.use('/api/v1/brand', require('./http/brand-api').createBrandApi(ctx));
app.use('/api/v1/brand', require('./http/brand-api').createBrandApi(ctx));
app.get('/api/v1/prompts', (req, res) => { res.set('Cache-Control', 'public, max-age=300'); res.json({ prompts: require('./views/prompts').list() }); });
app.get('/api/v1/prompts', (req, res) => { res.set('Cache-Control', 'public, max-age=300'); res.json({ prompts: require('./views/prompts').list() }); });
app.use(createIconRoutes(ctx));
app.use(createIconRoutes(ctx));
+ // The public page routers are built before they are mounted: the Markdown twins render through the same routers
+ // (server/agent-twins.js), so a twin cannot drift from the page a browser gets.
+ const { createTypefaceRoutes, createVectorRoutes } = require('./http/typefaces');
+ const typefaceRoutes = createTypefaceRoutes(ctx);
+ const vectorRoutes = createVectorRoutes(ctx);
+ const fontRoutes = createFontRoutes(ctx);
+ const pageRoutes = createPageRoutes(ctx);
+ const twinRouter = express.Router();
+ twinRouter.use(typefaceRoutes);
+ twinRouter.use(vectorRoutes);
+ twinRouter.use(fontRoutes);
+ twinRouter.use(pageRoutes);
+ // The Markdown twins of the public pages, and the head link and Link: header that name them (SEO-1,
+ // server/agent-twins.js): a twin is the page rendered signed out. Mounted before every page router so a
+ // page that answers 200 names its own twin.
+ require('./agent-twins').mountTwins(app, { router: twinRouter, descriptor: agentDescriptor, baseUrl: ctx.config.baseUrl });
// The typefaces and vectors a person makes with the model (plan R1 item 8): the makers, the
// The typefaces and vectors a person makes with the model (plan R1 item 8): the makers, the
// specimens and the API for agents. These come before the font library's /fonts/:family, so
// specimens and the API for agents. These come before the font library's /fonts/:family, so
// /fonts/make is the maker and not a family called "make".
// /fonts/make is the maker and not a family called "make".
- {
- const { createTypefaceRoutes, createVectorRoutes } = require('./http/typefaces');
- app.use(createTypefaceRoutes(ctx));
- app.use(createVectorRoutes(ctx));
- }
- app.use(createFontRoutes(ctx));
+ app.use(typefaceRoutes);
+ app.use(vectorRoutes);
+ app.use(fontRoutes);
app.use('/api', createApi(ctx));
app.use('/api', createApi(ctx));
// GET /openapi.json (SEO-3): the API as OpenAPI 3.1. It is not in openvibe-contracts, so the document is read from
// GET /openapi.json (SEO-3): the API as OpenAPI 3.1. It is not in openvibe-contracts, so the document is read from
// the routes this app mounts under /api/, with the summaries the API files' header comments give them
// the routes this app mounts under /api/, with the summaries the API files' header comments give them
@@ -319,10 +332,6 @@ async function createApp(opts = {}) {
@@ -319,10 +332,6 @@ async function createApp(opts = {}) {
app.use('/tokens', createTokenFileRoutes(ctx));
app.use('/tokens', createTokenFileRoutes(ctx));
// ── Pages ───────────────────────────────────────────────
// ── Pages ───────────────────────────────────────────────
- const pageRoutes = createPageRoutes(ctx);
- // The Markdown twins of the public pages, and the head link and Link: header that name them (SEO-1,
- // server/agent-twins.js): a twin is the page rendered signed out.
- require('./agent-twins').mountTwins(app, { router: pageRoutes, descriptor: agentDescriptor, baseUrl: ctx.config.baseUrl });
app.use(pageRoutes);
app.use(pageRoutes);
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-unused-vars
@@ -37,6 +37,27 @@ const PAGES = ['/', '/brand', '/components', '/tokens', '/motion', '/kit', '/dev
@@ -37,6 +37,27 @@ const PAGES = ['/', '/brand', '/components', '/tokens', '/motion', '/kit', '/dev
assert.ok(!/<(?:script|style)\b/i.test(prose) && twin.text.length > 150, `${twinPath} is the page's words`);
assert.ok(!/<(?:script|style)\b/i.test(prose) && twin.text.length > 150, `${twinPath} is the page's words`);
}
}
});
});
+ // The font library, the typeface and vector makers, and the form controls are mounted by their own routers;
+ // a twin still renders through the same page code, signed out, and cannot drift from the page.
+ const MORE_PAGES = [
+ ['/fonts', 'Find a face for your idea.'],
+ ['/fonts/make', 'Describe a typeface.'],
+ ['/forms', 'Controls that look like OpenVibe, on every site'],
+ ['/vectors/make', 'Describe a drawing.'],
+ ];
+ await check('the font, typeface, form and vector pages have twins that name the page heading', async () => {
+ for (const [p, heading] of MORE_PAGES) {
+ assert.ok(TWIN_PATH_RE.test(p), `${p} is a twin path`);
+ const page = await t.get(p);
+ assert.strictEqual(page.status, 200, p);
+ const twinPath = `${p}.md`;
+ assert.ok(page.text.includes(`<link rel="alternate" type="text/markdown" href="${origin}${twinPath}">`), `${p} names its twin in the head`);
+ const twin = await t.get(twinPath);
+ assert.strictEqual(twin.status, 200, twinPath);
+ assert.match(String(twin.headers.get('content-type') || ''), /^text\/markdown/, `${twinPath} is Markdown`);
+ assert.ok(twin.text.includes(`# ${heading}`), `${twinPath} names the page heading "${heading}"`);
+ }
+ });
await check('a path with no twin answers a Markdown 404, and a missing page names no twin', async () => {
await check('a path with no twin answers a Markdown 404, and a missing page names no twin', async () => {
const twin = await t.get('/no-such-page-here.md');
const twin = await t.get('/no-such-page-here.md');
assert.strictEqual(twin.status, 404);
assert.strictEqual(twin.status, 404);