openvibers/openvibe.tools Commits

Home headline: the doing-word check at module scope, tested (the home answered 500) (#286)

OP OpenVibers committed 3 days ago · 9e72228b4a · 2 files, +25 −13

#283 put the list of doing words inside the page's CSS template (it was anchored on a comment that lives in the
CSS), so the fallback that reads it threw for any tool without a hand-written phrase and the home answered 500 on
production for about twelve minutes until the release was rolled back. The list and the fallback
(headlineClause) are now module functions, exported and tested directly: a noun phrase gives null, a doing phrase
is kept, "?" ends a clause; the headline test also checks that the home answers 200.

Changes

ddiff --git a/apps/gateway/server/pages/site.js b/apps/gateway/server/pages/site.js
iindex efa49b3..1eabf59 100644
---- a/apps/gateway/server/pages/site.js
++++ b/apps/gateway/server/pages/site.js
@@@ -21,6 +21,21 @@ const kit = require('openvibe-shared/kit');
const appIcon = require('openvibe-shared/app-icon');
const registry = require('../registry');
const { seoFor } = require('../seo/catalog');
++
++// The doing words a tool's tagline may start with to finish "Small tools that …" (see clause() in the home).
++const HEADLINE_VERBS = new Set(('add build calculate change check clean compare compress convert count create crop decode design detect draw edit '
++ + 'encode estimate explore extract find fix flip format generate get look make map measure merge minify mix pick plan play '
++ + 'preview read record remove rename resize roll rotate save scan search see send share shorten sign split strip test time '
++ + 'track translate trim turn validate verify watch write').split(' '));
++/** A tool's own words for the end of "Small tools that …": the first clause of its tagline, only when it starts with a
++ * doing word ("Look up DNS records for any domain: A, AAAA…" → "look up DNS records for any domain"); else null. */
++function headlineClause(t) {
++ let s = String((t && t.tagline) || '').split(/[:;.(?!]|, | — | - /)[0].trim();
++ if (s.split(/\s+/).length < 2 || s.length > 46) return null;
++ if (/^[A-Z][a-z]/.test(s)) s = s[0].toLowerCase() + s.slice(1);
++ return HEADLINE_VERBS.has(s.split(/\s+/)[0]) ? s : null;
++}
++
const guardLimits = require('../../../_shared/guard/limits');
const { foldBlock } = require('../short');
@@@ -121,11 +136,6 @@ details{border-bottom:1px solid var(--border,rgba(255,255,255,.08));padding:12px
/* Categories as folds (11-09): the icon and the name are the fold's title; its count sits beside them. */
.job .fold-block{margin-top:10px}.job .fold-h .sec-h{display:flex;align-items:center;gap:10px;flex:1 1 auto;min-width:0;margin:0}.job .fold-h h2{margin:0}
.job{border-top:1px solid var(--border,rgba(255,255,255,.08));padding-top:4px}
--// The doing words a tool's tagline may start with to finish "Small tools that …" (see clause() in the home).
--const HEADLINE_VERBS = new Set(('add build calculate change check clean compare compress convert count create crop decode design detect draw edit '
-- + 'encode estimate explore extract find fix flip format generate get look make map measure merge minify mix pick plan play '
-- + 'preview read record remove rename resize roll rotate save scan search see send share shorten sign split strip test time '
-- + 'track translate trim turn validate verify watch write').split(' '));
/* The headline's last words change: each phrase is a link to its tool, all stacked in one cell (as tall as the
tallest, so nothing below moves). The one showing rises in; the one leaving lifts away. Once through, then it rests. */
.hm-rot{display:grid}.hm-ph{grid-area:1/1;color:var(--accent-light,var(--accent,#60a5fa));text-decoration:none;opacity:0;visibility:hidden;transform:translateY(.35em);
@@@ -448,12 +458,7 @@ function pageIndex(top = [], activity = { available: false }) {
// Only a clause that starts with a doing word finishes the sentence (the owner, 2026-11-10, on "Small tools that one
// reputation report for a domain or IP": "the wording is kind of awkward"); a tagline that names a thing instead is
// skipped, never bent into the sentence. A question or an exclamation ends the clause too.
-- const clause = (t) => {
-- let s = String(t.tagline || '').split(/[:;.(?!]|, | — | - /)[0].trim();
-- if (s.split(/\s+/).length < 2 || s.length > 46) return null;
-- if (/^[A-Z][a-z]/.test(s)) s = s[0].toLowerCase() + s.slice(1);
-- return HEADLINE_VERBS.has(s.split(/\s+/)[0]) ? s : null;
-- };
++ const clause = headlineClause;
const spoken = quickTools.map(t => [t, phrases[t.id] || clause(t)]).filter(([, p]) => p).slice(0, 6);
// Every phrase is in the page as a link to its tool, stacked in one grid cell: the cell is as tall as the tallest,
// so the swap never moves the page, and each tool is a plain link a crawler reads. One shows at a time.
@@@ -801,4 +806,4 @@ function createSiteRouter({ opens = null, discovery = null } = {}) {
const renderNotFound = () => pageSearch('').replace('<h1>Search tools</h1>', '<h1>That page is not here</h1>').replace(/<title>[^<]*<\/title>/, '<title>Page not found | OpenVibe.Tools</title>');
--module.exports = { createSiteRouter, pageIndex, pageFamily, pageTool, pageAll, pageDevelopers, search, sitemapEntries, renderNotFound, SITE, NAME };
++module.exports = { headlineClause, createSiteRouter, pageIndex, pageFamily, pageTool, pageAll, pageDevelopers, search, sitemapEntries, renderNotFound, SITE, NAME };
ddiff --git a/apps/gateway/test/home-headline.test.js b/apps/gateway/test/home-headline.test.js
iindex 9667db3..0b30e2a 100644
---- a/apps/gateway/test/home-headline.test.js
++++ b/apps/gateway/test/home-headline.test.js
@@@ -16,7 +16,14 @@ const site = require('../server/pages/site');
const srv = http.createServer(app);
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
try {
-- const home = await (await fetch(`http://127.0.0.1:${srv.address().port}/`)).text();
++ // A tagline that names a thing never finishes the sentence; one that says what the tool does does (2026-11-10: the
++ // fallback once referred to a list it could not see and the home answered 500 on production).
++ assert.strictEqual(site.headlineClause({ tagline: 'One reputation report for a domain or IP.' }), null);
++ assert.strictEqual(site.headlineClause({ tagline: 'Look up DNS records for any domain: A, AAAA, MX' }), 'look up DNS records for any domain');
++ assert.strictEqual(site.headlineClause({ tagline: 'Check a domain\'s SPF and DMARC policy?' }), 'check a domain\'s SPF and DMARC policy');
++ const res = await fetch(`http://127.0.0.1:${srv.address().port}/`);
++ assert.strictEqual(res.status, 200, 'the home answers');
++ const home = await res.text();
const rot = home.match(/<h1>Small tools that <span class="hm-rot" id="tool-headline">(.*?)<\/span><\/h1>/s);
assert.ok(rot, 'the headline ends in the rotating phrases');
const links = Array.from(rot[1].matchAll(/<a class="hm-ph( is-on)?" href="([^"]+)"([^>]*)>([^<]+)<\/a>/g));