openvibers/openvibe.live Commits

Live docs: search titles and descriptions fit (sentences up to 160 characters; long headings without a suffix) (#132)

OP OpenVibers committed 29 days ago · 9e03939c99 · 2 files, +24 −2

Live's docs pages took their whole first paragraph as the meta description (up to 300 characters) and always added " — OpenVibe.Live Docs" to the title. The go-live guide, the headline page, got an 89-character title and a 300-character description, both cut off in search results (a metadata audit of every site).

- **Description:** the first paragraph's sentences up to 160 characters, or cut at a word with an ellipsis. The go-live guide's is its first sentence (142).
- **Title:** a heading over 45 characters is the whole title; shorter ones keep the suffix. The go-live guide's title is its heading (68).

`test/docs-render.test.js` checks both.

Changes

ddiff --git a/server/docs/routes.js b/server/docs/routes.js
iindex bba199d9..03059a8a 100644
---- a/server/docs/routes.js
++++ b/server/docs/routes.js
@@@ -225,11 +225,20 @@ function publicBase() {
return b.replace(/\/+$/, '');
}
++/** A search-result summary: the text when it fits 160 characters, else its sentences up to that, else cut at a word. */
++function summaryOf(text, max = 160) {
++ if (text.length <= max) return text;
++ const cut = text.slice(0, max);
++ const end = Math.max(cut.lastIndexOf('. '), cut.lastIndexOf('! '), cut.lastIndexOf('? '), cut.endsWith('.') ? max - 1 : -1);
++ return end >= 60 ? cut.slice(0, end + 1) : `${cut.slice(0, cut.lastIndexOf(' '))}…`;
++}
++
function renderPage({ name, title, html, headings }) {
-- const pageTitle = title ? `${title} — OpenVibe.Live Docs` : 'OpenVibe.Live Docs';
++ // A long heading is the whole title (search results cut a title near 60 characters, so no suffix after it).
++ const pageTitle = title ? (title.length > 45 ? title : `${title} — OpenVibe.Live Docs`) : 'OpenVibe.Live Docs';
// The first paragraph is the page's summary for search engines and link previews.
const firstP = /<p>([\s\S]*?)<\/p>/.exec(html);
-- const description = firstP ? firstP[1].replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/\s+/g, ' ').trim().slice(0, 300) : '';
++ const description = firstP ? summaryOf(firstP[1].replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/\s+/g, ' ').trim()) : '';
const canonical = `${publicBase()}/docs${name === 'README' ? '' : `/${name}`}`;
const toc = headings.length
? `<nav class="toc" aria-label="On this page">${headings.map(h => `<a href="#${h.id}">${escapeHtml(h.text)}</a>`).join('')}</nav>`
ddiff --git a/test/docs-render.test.js b/test/docs-render.test.js
iindex 7cd4a6fc..62dbc0ca 100644
---- a/test/docs-render.test.js
++++ b/test/docs-render.test.js
@@@ -99,4 +99,17 @@ assert.ok(shell.includes('<link rel="icon" type="image/svg+xml" href="/assets/lo
assert.ok(fs.existsSync(path.join(__dirname, '..', 'public', 'assets', 'logo.svg')), 'the icon file exists');
console.log('✅ page shell: canonical and icon');
++// Search results: a long heading is the whole <title>, and the description is the first paragraph's sentences up to 160
++// characters (the go-live guide's paragraph is 300).
++{
++ const long = 'A very long first sentence that explains the page in plain words for people who search for it. ' + 'And then more words follow. '.repeat(8);
++ const page = renderPage({ name: 'x', title: 'Go live from your browser: no OBS, no downloads, no follower minimum', html: `<p>${long}</p>`, headings: [] });
++ assert.ok(page.includes('<title>Go live from your browser: no OBS, no downloads, no follower minimum</title>'), 'a long title has no suffix');
++ const d = (/<meta name="description" content="([^"]*)"/.exec(page) || [])[1];
++ assert.ok(d.length <= 160 && d.endsWith('.'), `description is whole sentences within 160: ${d}`);
++ const short = renderPage({ name: 'y', title: 'WHIP', html: '<p>Short.</p>', headings: [] });
++ assert.ok(short.includes('<title>WHIP — OpenVibe.Live Docs</title>') && short.includes('content="Short."'));
++ console.log('✅ page shell: search title and description');
++}
++
console.log('\n✅ All docs renderer tests passed');