openvibers/openvibe.design Commits

Serve the Markdown twins llms.txt links for fonts, makers and forms (#197)

OP OpenVibers committed 4 days ago · 47c2272c9d · 3 files, +41 −11

llms.txt links /fonts.md, /fonts/make.md, /forms.md and /vectors/make.md,
but the agent twin router only knew the page router. Build the typeface,
vector and font routers once, compose them with the page router for the
twin renderer, and list the four paths in TWIN_PATH_RE. The test now asks
each twin for text/markdown and its page heading.

Changes

ddiff --git a/server/agent-twins.js b/server/agent-twins.js
iindex 12f4621..4fd1004 100644
---- a/server/agent-twins.js
++++ b/server/agent-twins.js
@@@ -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() {
ddiff --git a/server/app.js b/server/app.js
iindex 298b7c9..2ac745c 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -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
ddiff --git a/test/agent-twins.test.js b/test/agent-twins.test.js
iindex 014a659..be54f42 100644
---- a/test/agent-twins.test.js
++++ b/test/agent-twins.test.js
@@@ -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`);
}
});
++ // 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);